返回 presentation-ai
FontPickerField.tsx
1 import { FileText, Upload, X } from "lucide-react";
2 import { Controller, useWatch } from "react-hook-form";
3
4 import FontPicker from "@/components/ui/font-picker/components/FontPicker";
5 import {
6 Select,
7 SelectContent,
8 SelectItem,
9 SelectTrigger,
10 SelectValue,
11 } from "@/components/ui/select";
12 import { type FontPickerFieldProps } from "./types";
13
14 const FONT_WEIGHTS = [
15 { value: 300, label: "Light (300)" },
16 { value: 400, label: "Regular (400)" },
17 { value: 500, label: "Medium (500)" },
18 { value: 600, label: "Semi Bold (600)" },
19 { value: 700, label: "Bold (700)" },
20 { value: 800, label: "Extra Bold (800)" },
21 { value: 900, label: "Black (900)" },
22 ] as const;
23
24 export function FontPickerField({
25 label,
26 target,
27 control,
28 setValue,
29 isUploading,
30 onUpload,
31 getLocalCustomFonts,
32 }: FontPickerFieldProps) {
33 const fontKey = `fonts.${target}` as const;
34 const urlKey = `fonts.${target}Url` as const;
35 const weightKey = `fonts.${target}Weight` as const;
36
37 // Use useWatch to properly subscribe to form changes
38 const currentUrl = useWatch({ control, name: urlKey });
39 const currentFamily = useWatch({ control, name: fontKey });
40
41 const handleRemoveCustomFont = () => {
42 const options = { shouldDirty: true };
43 setValue(urlKey, undefined, options);
44 setValue(fontKey, "", options);
45 };
46
47 return (
48 <div className="space-y-3">
49 <div className="flex items-center justify-between">
50 <label className="text-xs font-semibold text-muted-foreground uppercase">
51 {label}
52 </label>
53 <button
54 type="button"
55 onClick={onUpload}
56 disabled={isUploading}
57 className="flex items-center gap-1 rounded-md bg-primary px-2 py-1 text-xs text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
58 >
59 <Upload className="size-3" />
60 {isUploading ? "Uploading..." : "Upload"}
61 </button>
62 </div>
63
64 {currentUrl ? (
65 // Show custom uploaded font display
66 <div className="flex items-center gap-2 rounded-lg border border-border bg-muted/50 p-3">
67 <FileText className="size-4 shrink-0 text-muted-foreground" />
68 <div className="min-w-0 flex-1">
69 <p
70 className="truncate text-sm font-medium"
71 style={{ fontFamily: currentFamily }}
72 >
73 {currentFamily}
74 </p>
75 <p className="text-xs text-muted-foreground">
76 Custom uploaded font
77 </p>
78 </div>
79 <button
80 type="button"
81 onClick={handleRemoveCustomFont}
82 className="rounded p-1 transition-colors hover:bg-muted"
83 title="Remove custom font"
84 >
85 <X className="size-4" />
86 </button>
87 </div>
88 ) : (
89 // Show normal font picker
90 <Controller
91 name={fontKey}
92 control={control}
93 render={({ field }) => {
94 const localFonts = getLocalCustomFonts();
95
96 return (
97 <FontPicker
98 defaultValue={field.value}
99 mode="combo"
100 selectClassName="h-10 w-full"
101 autoLoad={true}
102 value={(fontName: string) => {
103 setValue(urlKey, undefined, {
104 shouldDirty: true,
105 });
106 field.onChange(fontName);
107 }}
108 localFonts={localFonts}
109 />
110 );
111 }}
112 />
113 )}
114
115 <Controller
116 name={weightKey}
117 control={control}
118 render={({ field }) => (
119 <Select
120 value={String(field.value || 400)}
121 onValueChange={(val) => field.onChange(Number(val))}
122 >
123 <SelectTrigger className="w-full appearance-none rounded-lg border border-border bg-background px-3 py-2 text-sm transition-colors hover:bg-muted">
124 <SelectValue placeholder="Font Weight" />
125 </SelectTrigger>
126 <SelectContent className="z-10002">
127 {FONT_WEIGHTS.map(({ value, label }) => (
128 <SelectItem key={value} value={String(value)}>
129 {label}
130 </SelectItem>
131 ))}
132 </SelectContent>
133 </Select>
134 )}
135 />
136 </div>
137 );
138 }
139
139 lines Plain Text