File
Blob: src/components/TopBar.tsx
| 1 | import { useRef } from "react"; |
| 2 | import { Button } from "./ui"; |
| 3 | import { CloseIcon, CropIcon, ImagePlusIcon, ResetIcon } from "./icons"; |
| 4 | |
| 5 | interface TopBarProps { |
| 6 | hasImage: boolean; |
| 7 | onFile: (file: File) => void; |
| 8 | onResetEdits: () => void; |
| 9 | onClose: () => void; |
| 10 | } |
| 11 | |
| 12 | export function TopBar({ hasImage, onFile, onResetEdits, onClose }: TopBarProps) { |
| 13 | const inputRef = useRef<HTMLInputElement>(null); |
| 14 | |
| 15 | return ( |
| 16 | <header className="flex h-14 shrink-0 items-center justify-between border-b border-zinc-800/60 bg-zinc-900 px-4"> |
| 17 | <div className="group flex items-center gap-2.5"> |
| 18 | <span className="inline-grid size-9 place-items-center"> |
| 19 | <CropIcon |
| 20 | className="size-6 text-accent-400 transition-transform duration-200 group-hover:-rotate-6" |
| 21 | strokeWidth={2} |
| 22 | aria-hidden="true" |
| 23 | /> |
| 24 | </span> |
| 25 | <div className="leading-tight"> |
| 26 | <h1 className="text-sm font-semibold tracking-tight text-zinc-100">wimp</h1> |
| 27 | <p className="text-[0.65rem] text-zinc-500">web image manipulation</p> |
| 28 | </div> |
| 29 | </div> |
| 30 | |
| 31 | <div className="flex items-center gap-2"> |
| 32 | <span className="hidden items-center gap-1.5 rounded-full bg-zinc-800 px-2.5 py-1 text-[0.65rem] text-zinc-400 sm:flex"> |
| 33 | <span className="size-1.5 rounded-full bg-emerald-500" /> 100% local · nothing uploaded |
| 34 | </span> |
| 35 | {hasImage && ( |
| 36 | <> |
| 37 | <Button size="sm" variant="ghost" onClick={onResetEdits}> |
| 38 | <ResetIcon className="size-4" /> Reset |
| 39 | </Button> |
| 40 | <Button size="sm" variant="subtle" onClick={() => inputRef.current?.click()}> |
| 41 | <ImagePlusIcon className="size-4" /> New image |
| 42 | </Button> |
| 43 | <Button size="sm" variant="ghost" onClick={onClose} aria-label="Close image"> |
| 44 | <CloseIcon className="size-4" /> |
| 45 | </Button> |
| 46 | </> |
| 47 | )} |
| 48 | </div> |
| 49 | |
| 50 | <input |
| 51 | ref={inputRef} |
| 52 | type="file" |
| 53 | accept="image/*" |
| 54 | className="hidden" |
| 55 | onChange={(e) => { |
| 56 | const file = e.target.files?.[0]; |
| 57 | if (file) onFile(file); |
| 58 | e.target.value = ""; |
| 59 | }} |
| 60 | /> |
| 61 | </header> |
| 62 | ); |
| 63 | } |