diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-25 16:47:22 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-29 15:48:22 +0200 |
| commit | 8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch) | |
| tree | e9d0cafa93904ffcbec3188c37961bcc16c6422d /apps/web/src/components/ui/font-picker.tsx | |
| parent | 39ea298a9ccba3104c549c0ca079c231981811f5 (diff) | |
| download | opencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz | |
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'apps/web/src/components/ui/font-picker.tsx')
| -rw-r--r-- | apps/web/src/components/ui/font-picker.tsx | 107 |
1 files changed, 28 insertions, 79 deletions
diff --git a/apps/web/src/components/ui/font-picker.tsx b/apps/web/src/components/ui/font-picker.tsx index 09ba0a9a..77d8ab7f 100644 --- a/apps/web/src/components/ui/font-picker.tsx +++ b/apps/web/src/components/ui/font-picker.tsx @@ -1,13 +1,6 @@ "use client"; -import { - useState, - useMemo, - useRef, - useEffect, - useCallback, - type CSSProperties, -} from "react"; +import { useState, useMemo, useRef, useEffect, useCallback, type CSSProperties } from "react"; import { List, type RowComponentProps } from "react-window"; import { Popover, @@ -16,28 +9,24 @@ import { } from "@/components/ui/popover"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { - getCachedFontAtlas, - loadFullFont, - prefetchFontAtlas, - clearFontAtlasCache, -} from "@/lib/fonts/google-fonts"; -import type { FontAtlas, FontAtlasEntry } from "@/types/fonts"; +import { loadFullFont } from "@/lib/fonts/google-fonts"; +import { SYSTEM_FONTS } from "@/constants/font-constants"; +import type { FontAtlas, FontAtlasEntry } from "@/lib/fonts/types"; +import { useFontAtlas } from "@/hooks/use-font-atlas"; import { cn } from "@/utils/ui"; -import { ChevronDown, Search, Upload } from "lucide-react"; +import { ChevronDown, Search } from "lucide-react"; import { HugeiconsIcon } from "@hugeicons/react"; import { TextIcon } from "@hugeicons/core-free-icons"; const FONT_TABS = [ { key: "all", label: "All fonts" }, - { key: "favorites", label: "Favorites" }, { key: "my-fonts", label: "My fonts" }, + { key: "favorites", label: "Favorites" }, ] as const; type FontTab = (typeof FONT_TABS)[number]["key"]; const ROW_HEIGHT = 40; -const SPRITE_ROW_HEIGHT = 40; const PREVIEW_SCALE = 0.8; const LIST_WIDTH = 288; const MAX_LIST_HEIGHT = 288; @@ -57,18 +46,8 @@ export function FontPicker({ const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const [activeTab, setActiveTab] = useState<FontTab>("all"); - const [atlas, setAtlas] = useState<FontAtlas | null>(() => - getCachedFontAtlas(), - ); - const [status, setStatus] = useState<"idle" | "loading" | "error">(() => - getCachedFontAtlas() ? "idle" : "loading", - ); const searchInputRef = useRef<HTMLInputElement>(null); - - const fontNames = useMemo(() => { - if (!atlas) return []; - return Object.keys(atlas.fonts).sort(); - }, [atlas]); + const { atlas, status, fontNames, retry: handleRetry } = useFontAtlas({ open }); const filteredFonts = useMemo(() => { if (!search) return fontNames; @@ -83,32 +62,19 @@ export function FontPicker({ const handleSelect = useCallback( async ({ family }: { family: string }) => { - try { - await loadFullFont({ family }); - onValueChange?.(family); - } catch { - onValueChange?.(family); + if (!SYSTEM_FONTS.has(family)) { + try { + await loadFullFont({ family }); + } catch { + // ignore load failure, font will fall back to system default + } } + onValueChange?.(family); setOpen(false); }, [onValueChange], ); - // Load atlas on first open if cache is empty (fallback when prefetch hasn't completed) - useEffect(() => { - if (!open || atlas) return; - - setStatus("loading"); - prefetchFontAtlas().then((data) => { - if (data) { - setAtlas(data); - setStatus("idle"); - } else { - setStatus("error"); - } - }); - }, [open, atlas]); - useEffect(() => { if (!open) { setSearch(""); @@ -116,18 +82,8 @@ export function FontPicker({ } }, [open]); - const handleRetry = useCallback(() => { - clearFontAtlasCache(); - setStatus("loading"); - prefetchFontAtlas().then((data) => { - if (data) { - setAtlas(data); - setStatus("idle"); - } else { - setStatus("error"); - } - }); - }, []); + const activeTabLabel = + FONT_TABS.find((t) => t.key === activeTab)?.label.toLowerCase() ?? ""; return ( <Popover open={open} onOpenChange={setOpen}> @@ -164,11 +120,11 @@ export function FontPicker({ <Search className="absolute left-3 top-1/2 -translate-y-1/2 size-3.5 shrink-0 opacity-50" /> <Input ref={searchInputRef} - placeholder="Search fonts..." + placeholder={`Search ${activeTabLabel}...`} value={search} onChange={(event) => setSearch(event.target.value)} size="xs" - className="w-full pl-5 bg-transparent !border-none !shadow-none" + className="w-full pl-5 bg-transparent border-none! shadow-none!" /> </div> <div className="flex border-b px-3"> @@ -225,20 +181,6 @@ export function FontPicker({ style={{ height: listHeight, width: LIST_WIDTH }} /> )} - <div className="border-t p-1"> - <Button - variant="ghost" - size="sm" - className="w-full justify-start text-muted-foreground h-8 font-normal" - onClick={() => { - // TODO: Implement local font loading - console.log("Load local fonts clicked"); - }} - > - <Upload className="!size-3.5" /> - Load local fonts - </Button> - </div> </PopoverContent> </Popover> ); @@ -250,7 +192,7 @@ function FontSpritePreview({ entry }: { entry: FontAtlasEntry }) { className="shrink-0" style={{ width: entry.w, - height: SPRITE_ROW_HEIGHT, + height: ROW_HEIGHT, backgroundColor: "currentColor", WebkitMaskImage: `url(/fonts/font-chunk-${entry.ch}.avif)`, WebkitMaskPosition: `-${entry.x}px -${entry.y}px`, @@ -283,6 +225,7 @@ function FontRow({ const fontName = filteredFonts[index]; const entry = atlas.fonts[fontName]; const isSelected = fontName === selectedFont; + const isSystemFont = SYSTEM_FONTS.has(fontName); return ( <button @@ -302,7 +245,13 @@ function FontRow({ aria-label={fontName} > <div className="min-w-0 overflow-hidden"> - <FontSpritePreview entry={entry} /> + {isSystemFont ? ( + <span className="text-xl text-foreground/85" style={{ fontFamily: fontName }}> + {fontName} + </span> + ) : ( + <FontSpritePreview entry={entry} /> + )} </div> </button> ); |
