@proveanything/smartlinks-utils-ui 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/chunk-4Z46C4MJ.js +1090 -0
- package/dist/chunk-4Z46C4MJ.js.map +1 -0
- package/dist/{chunk-HLFNSOPD.js → chunk-DL2VRBE6.js} +3 -3
- package/dist/chunk-DL2VRBE6.js.map +1 -0
- package/dist/{chunk-V7JHAER7.js → chunk-WOCLZGRB.js} +64 -64
- package/dist/chunk-WOCLZGRB.js.map +1 -0
- package/dist/components/AssetPicker/index.js +1 -1
- package/dist/components/FontPicker/index.d.ts +134 -0
- package/dist/components/FontPicker/index.js +4 -0
- package/dist/components/FontPicker/index.js.map +1 -0
- package/dist/components/IconPicker/index.js +1 -1
- package/dist/index.css +139 -103
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -2
- package/package.json +5 -1
- package/dist/chunk-HLFNSOPD.js.map +0 -1
- package/dist/chunk-V7JHAER7.js.map +0 -1
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
import { cn } from './chunk-L7FQ52F5.js';
|
|
2
|
+
import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
3
|
+
import { Type, Search, Upload, Loader2, ChevronLeft, ChevronRight, X, CheckCircle2, AlertCircle, ChevronUp, ChevronDown, Trash2, Check } from 'lucide-react';
|
|
4
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/components/FontPicker/google-fonts.ts
|
|
7
|
+
var gUrl = (family, weights = [300, 400, 500, 600, 700]) => {
|
|
8
|
+
const encoded = family.replace(/ /g, "+");
|
|
9
|
+
const wStr = weights.join(";");
|
|
10
|
+
return `https://fonts.googleapis.com/css2?family=${encoded}:wght@${wStr}&display=swap`;
|
|
11
|
+
};
|
|
12
|
+
var FONT_DEFS = [
|
|
13
|
+
// Sans-Serif
|
|
14
|
+
{ family: "Inter", category: "sans-serif" },
|
|
15
|
+
{ family: "Roboto", category: "sans-serif" },
|
|
16
|
+
{ family: "Open Sans", category: "sans-serif" },
|
|
17
|
+
{ family: "Lato", category: "sans-serif" },
|
|
18
|
+
{ family: "Montserrat", category: "sans-serif" },
|
|
19
|
+
{ family: "Poppins", category: "sans-serif" },
|
|
20
|
+
{ family: "Nunito", category: "sans-serif" },
|
|
21
|
+
{ family: "Raleway", category: "sans-serif" },
|
|
22
|
+
{ family: "Work Sans", category: "sans-serif" },
|
|
23
|
+
{ family: "DM Sans", category: "sans-serif" },
|
|
24
|
+
{ family: "Manrope", category: "sans-serif" },
|
|
25
|
+
{ family: "Plus Jakarta Sans", category: "sans-serif" },
|
|
26
|
+
{ family: "Space Grotesk", category: "sans-serif" },
|
|
27
|
+
{ family: "Outfit", category: "sans-serif" },
|
|
28
|
+
{ family: "Sora", category: "sans-serif" },
|
|
29
|
+
{ family: "Figtree", category: "sans-serif" },
|
|
30
|
+
{ family: "Albert Sans", category: "sans-serif" },
|
|
31
|
+
{ family: "Urbanist", category: "sans-serif" },
|
|
32
|
+
{ family: "Lexend", category: "sans-serif" },
|
|
33
|
+
{ family: "Red Hat Display", category: "sans-serif" },
|
|
34
|
+
{ family: "Instrument Sans", category: "sans-serif" },
|
|
35
|
+
{ family: "Geist", category: "sans-serif", weights: [400, 500, 600, 700] },
|
|
36
|
+
{ family: "Onest", category: "sans-serif" },
|
|
37
|
+
{ family: "General Sans", category: "sans-serif", weights: [400, 500, 600, 700] },
|
|
38
|
+
{ family: "Satoshi", category: "sans-serif", weights: [400, 500, 700] },
|
|
39
|
+
{ family: "Cabinet Grotesk", category: "sans-serif", weights: [400, 500, 700, 800] },
|
|
40
|
+
{ family: "Switzer", category: "sans-serif", weights: [400, 500, 600, 700] },
|
|
41
|
+
{ family: "Overpass", category: "sans-serif" },
|
|
42
|
+
{ family: "Nunito Sans", category: "sans-serif" },
|
|
43
|
+
{ family: "Source Sans 3", category: "sans-serif" },
|
|
44
|
+
{ family: "PT Sans", category: "sans-serif", weights: [400, 700] },
|
|
45
|
+
{ family: "Rubik", category: "sans-serif" },
|
|
46
|
+
{ family: "Noto Sans", category: "sans-serif" },
|
|
47
|
+
{ family: "Karla", category: "sans-serif" },
|
|
48
|
+
{ family: "Mukta", category: "sans-serif" },
|
|
49
|
+
{ family: "Libre Franklin", category: "sans-serif" },
|
|
50
|
+
{ family: "Barlow", category: "sans-serif" },
|
|
51
|
+
{ family: "Josefin Sans", category: "sans-serif" },
|
|
52
|
+
{ family: "Quicksand", category: "sans-serif" },
|
|
53
|
+
{ family: "Exo 2", category: "sans-serif" },
|
|
54
|
+
{ family: "Mulish", category: "sans-serif" },
|
|
55
|
+
{ family: "Cabin", category: "sans-serif" },
|
|
56
|
+
{ family: "Archivo", category: "sans-serif" },
|
|
57
|
+
{ family: "Catamaran", category: "sans-serif" },
|
|
58
|
+
{ family: "Hind", category: "sans-serif" },
|
|
59
|
+
{ family: "Asap", category: "sans-serif" },
|
|
60
|
+
// Serif
|
|
61
|
+
{ family: "Playfair Display", category: "serif" },
|
|
62
|
+
{ family: "Merriweather", category: "serif" },
|
|
63
|
+
{ family: "Lora", category: "serif" },
|
|
64
|
+
{ family: "Source Serif 4", category: "serif" },
|
|
65
|
+
{ family: "PT Serif", category: "serif", weights: [400, 700] },
|
|
66
|
+
{ family: "Noto Serif", category: "serif" },
|
|
67
|
+
{ family: "Crimson Text", category: "serif", weights: [400, 600, 700] },
|
|
68
|
+
{ family: "Libre Baskerville", category: "serif", weights: [400, 700] },
|
|
69
|
+
{ family: "DM Serif Display", category: "serif", weights: [400] },
|
|
70
|
+
{ family: "DM Serif Text", category: "serif", weights: [400] },
|
|
71
|
+
{ family: "Cormorant Garamond", category: "serif" },
|
|
72
|
+
{ family: "EB Garamond", category: "serif" },
|
|
73
|
+
{ family: "Bitter", category: "serif" },
|
|
74
|
+
{ family: "Vollkorn", category: "serif" },
|
|
75
|
+
{ family: "Spectral", category: "serif" },
|
|
76
|
+
{ family: "Fraunces", category: "serif" },
|
|
77
|
+
{ family: "Newsreader", category: "serif" },
|
|
78
|
+
{ family: "Instrument Serif", category: "serif", weights: [400] },
|
|
79
|
+
{ family: "Literata", category: "serif" },
|
|
80
|
+
{ family: "Brygada 1918", category: "serif" },
|
|
81
|
+
{ family: "Bodoni Moda", category: "serif" },
|
|
82
|
+
{ family: "Cardo", category: "serif", weights: [400, 700] },
|
|
83
|
+
{ family: "Baskervville", category: "serif", weights: [400] },
|
|
84
|
+
{ family: "Cormorant", category: "serif" },
|
|
85
|
+
{ family: "Young Serif", category: "serif", weights: [400] },
|
|
86
|
+
// Display
|
|
87
|
+
{ family: "Bebas Neue", category: "display", weights: [400] },
|
|
88
|
+
{ family: "Oswald", category: "display" },
|
|
89
|
+
{ family: "Righteous", category: "display", weights: [400] },
|
|
90
|
+
{ family: "Alfa Slab One", category: "display", weights: [400] },
|
|
91
|
+
{ family: "Abril Fatface", category: "display", weights: [400] },
|
|
92
|
+
{ family: "Lobster", category: "display", weights: [400] },
|
|
93
|
+
{ family: "Comfortaa", category: "display" },
|
|
94
|
+
{ family: "Fredoka", category: "display" },
|
|
95
|
+
{ family: "Titan One", category: "display", weights: [400] },
|
|
96
|
+
{ family: "Bungee", category: "display", weights: [400] },
|
|
97
|
+
{ family: "Secular One", category: "display", weights: [400] },
|
|
98
|
+
{ family: "Lilita One", category: "display", weights: [400] },
|
|
99
|
+
{ family: "Passion One", category: "display", weights: [400, 700] },
|
|
100
|
+
{ family: "Fugaz One", category: "display", weights: [400] },
|
|
101
|
+
{ family: "Russo One", category: "display", weights: [400] },
|
|
102
|
+
{ family: "Paytone One", category: "display", weights: [400] },
|
|
103
|
+
{ family: "Black Ops One", category: "display", weights: [400] },
|
|
104
|
+
{ family: "Permanent Marker", category: "display", weights: [400] },
|
|
105
|
+
{ family: "Shrikhand", category: "display", weights: [400] },
|
|
106
|
+
{ family: "Chivo", category: "display" },
|
|
107
|
+
{ family: "Staatliches", category: "display", weights: [400] },
|
|
108
|
+
// Handwriting
|
|
109
|
+
{ family: "Dancing Script", category: "handwriting" },
|
|
110
|
+
{ family: "Pacifico", category: "handwriting", weights: [400] },
|
|
111
|
+
{ family: "Caveat", category: "handwriting" },
|
|
112
|
+
{ family: "Satisfy", category: "handwriting", weights: [400] },
|
|
113
|
+
{ family: "Great Vibes", category: "handwriting", weights: [400] },
|
|
114
|
+
{ family: "Sacramento", category: "handwriting", weights: [400] },
|
|
115
|
+
{ family: "Kalam", category: "handwriting" },
|
|
116
|
+
{ family: "Indie Flower", category: "handwriting", weights: [400] },
|
|
117
|
+
{ family: "Shadows Into Light", category: "handwriting", weights: [400] },
|
|
118
|
+
{ family: "Amatic SC", category: "handwriting", weights: [400, 700] },
|
|
119
|
+
{ family: "Handlee", category: "handwriting", weights: [400] },
|
|
120
|
+
{ family: "Patrick Hand", category: "handwriting", weights: [400] },
|
|
121
|
+
{ family: "Architects Daughter", category: "handwriting", weights: [400] },
|
|
122
|
+
{ family: "Courgette", category: "handwriting", weights: [400] },
|
|
123
|
+
{ family: "Cookie", category: "handwriting", weights: [400] },
|
|
124
|
+
// Monospace
|
|
125
|
+
{ family: "JetBrains Mono", category: "monospace" },
|
|
126
|
+
{ family: "Fira Code", category: "monospace" },
|
|
127
|
+
{ family: "Source Code Pro", category: "monospace" },
|
|
128
|
+
{ family: "Roboto Mono", category: "monospace" },
|
|
129
|
+
{ family: "Space Mono", category: "monospace", weights: [400, 700] },
|
|
130
|
+
{ family: "IBM Plex Mono", category: "monospace" },
|
|
131
|
+
{ family: "Inconsolata", category: "monospace" },
|
|
132
|
+
{ family: "Ubuntu Mono", category: "monospace", weights: [400, 700] },
|
|
133
|
+
{ family: "Red Hat Mono", category: "monospace" },
|
|
134
|
+
{ family: "Courier Prime", category: "monospace", weights: [400, 700] }
|
|
135
|
+
];
|
|
136
|
+
var DEFAULT_WEIGHTS = [300, 400, 500, 600, 700];
|
|
137
|
+
var GOOGLE_FONTS_CATALOG = FONT_DEFS.map((def) => {
|
|
138
|
+
const weights = def.weights || DEFAULT_WEIGHTS;
|
|
139
|
+
return {
|
|
140
|
+
family: def.family,
|
|
141
|
+
source: "google",
|
|
142
|
+
category: def.category,
|
|
143
|
+
weights,
|
|
144
|
+
loadUrl: gUrl(def.family, weights)
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
var getGoogleFontUrl = (family, weights) => gUrl(family, weights || DEFAULT_WEIGHTS);
|
|
148
|
+
var CATEGORY_LABELS = {
|
|
149
|
+
"sans-serif": "Sans Serif",
|
|
150
|
+
serif: "Serif",
|
|
151
|
+
display: "Display",
|
|
152
|
+
handwriting: "Handwriting",
|
|
153
|
+
monospace: "Monospace"
|
|
154
|
+
};
|
|
155
|
+
var CATEGORY_FALLBACKS = {
|
|
156
|
+
"sans-serif": "ui-sans-serif, system-ui, sans-serif",
|
|
157
|
+
serif: "ui-serif, Georgia, serif",
|
|
158
|
+
display: "ui-sans-serif, system-ui, sans-serif",
|
|
159
|
+
handwriting: "cursive",
|
|
160
|
+
monospace: "ui-monospace, monospace"
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/components/FontPicker/useFontSearch.ts
|
|
164
|
+
var SL = null;
|
|
165
|
+
var getSL = async () => {
|
|
166
|
+
if (!SL) SL = await import('@proveanything/smartlinks');
|
|
167
|
+
return SL;
|
|
168
|
+
};
|
|
169
|
+
var WEIGHT_PATTERNS = [
|
|
170
|
+
[/\b(hairline|thin)\b/i, 100],
|
|
171
|
+
[/\b(ultra\s?light|extra\s?light)\b/i, 200],
|
|
172
|
+
[/\blight\b/i, 300],
|
|
173
|
+
[/\b(regular|normal|book)\b/i, 400],
|
|
174
|
+
[/\bmedium\b/i, 500],
|
|
175
|
+
[/\b(semi\s?bold|demi\s?bold)\b/i, 600],
|
|
176
|
+
[/\bbold\b/i, 700],
|
|
177
|
+
[/\b(extra\s?bold|ultra\s?bold)\b/i, 800],
|
|
178
|
+
[/\b(black|heavy)\b/i, 900]
|
|
179
|
+
];
|
|
180
|
+
function detectWeightFromFilename(name) {
|
|
181
|
+
for (const [pattern, weight] of WEIGHT_PATTERNS) {
|
|
182
|
+
if (pattern.test(name)) return weight;
|
|
183
|
+
}
|
|
184
|
+
return 400;
|
|
185
|
+
}
|
|
186
|
+
function detectItalicFromFilename(name) {
|
|
187
|
+
return /\b(italic|oblique|ital)\b/i.test(name);
|
|
188
|
+
}
|
|
189
|
+
function fontFamilyFromFilename(name) {
|
|
190
|
+
let clean = name.replace(/\.(woff2?|ttf|otf|eot)$/i, "");
|
|
191
|
+
clean = clean.replace(/[-_]?(regular|bold|italic|oblique|ital|light|medium|semibold|semi-bold|demi-bold|thin|black|extra\s?bold|extra\s?light|ultra\s?light|ultra\s?bold|heavy|hairline|book|normal)/gi, "");
|
|
192
|
+
clean = clean.replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
|
|
193
|
+
if (!clean) return "Custom Font";
|
|
194
|
+
return clean.split(" ").map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
195
|
+
}
|
|
196
|
+
function customFamiliesToEntries(families) {
|
|
197
|
+
return families.map((fam) => {
|
|
198
|
+
const firstStyle = fam.fonts[0];
|
|
199
|
+
return {
|
|
200
|
+
family: fam.name,
|
|
201
|
+
source: "custom",
|
|
202
|
+
loadUrl: firstStyle?.url,
|
|
203
|
+
assetId: firstStyle?.assetId,
|
|
204
|
+
mimeType: firstStyle?.mimeType,
|
|
205
|
+
weights: fam.fonts.map((f) => f.weight)
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function useFontSearch(options = {}) {
|
|
210
|
+
const {
|
|
211
|
+
categories,
|
|
212
|
+
scope,
|
|
213
|
+
showCustomFonts = false,
|
|
214
|
+
admin = false,
|
|
215
|
+
pageSize = 50,
|
|
216
|
+
fontsConfigAppId = "customFonts"
|
|
217
|
+
} = options;
|
|
218
|
+
const [customFamilies, setCustomFamilies] = useState([]);
|
|
219
|
+
const [customFonts, setCustomFonts] = useState([]);
|
|
220
|
+
const [loadingCustom, setLoadingCustom] = useState(false);
|
|
221
|
+
const [activeTab, setActiveTab] = useState("google");
|
|
222
|
+
const [activeCategory, setActiveCategory] = useState(null);
|
|
223
|
+
const [query, setQuery] = useState("");
|
|
224
|
+
const [page, setPage] = useState(0);
|
|
225
|
+
const debounceRef = useRef();
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (!showCustomFonts || !scope?.collectionId) return;
|
|
228
|
+
let cancelled = false;
|
|
229
|
+
setLoadingCustom(true);
|
|
230
|
+
(async () => {
|
|
231
|
+
try {
|
|
232
|
+
const sl = await getSL();
|
|
233
|
+
const config = await sl.appConfiguration.getConfig({
|
|
234
|
+
collectionId: scope.collectionId,
|
|
235
|
+
appId: fontsConfigAppId,
|
|
236
|
+
...admin ? { admin: true } : {}
|
|
237
|
+
});
|
|
238
|
+
if (cancelled) return;
|
|
239
|
+
const fontsConfig = config || { fonts: [] };
|
|
240
|
+
const families = fontsConfig.fonts || [];
|
|
241
|
+
setCustomFamilies(families);
|
|
242
|
+
setCustomFonts(customFamiliesToEntries(families));
|
|
243
|
+
} catch (err) {
|
|
244
|
+
console.warn("[FontPicker] Failed to load custom fonts config:", err);
|
|
245
|
+
setCustomFamilies([]);
|
|
246
|
+
setCustomFonts([]);
|
|
247
|
+
} finally {
|
|
248
|
+
if (!cancelled) setLoadingCustom(false);
|
|
249
|
+
}
|
|
250
|
+
})();
|
|
251
|
+
return () => {
|
|
252
|
+
cancelled = true;
|
|
253
|
+
};
|
|
254
|
+
}, [showCustomFonts, scope?.collectionId, scope?.productId, admin, fontsConfigAppId]);
|
|
255
|
+
const saveCustomFonts = useCallback(async (families) => {
|
|
256
|
+
if (!scope?.collectionId) return;
|
|
257
|
+
try {
|
|
258
|
+
const sl = await getSL();
|
|
259
|
+
await sl.appConfiguration.setConfig({
|
|
260
|
+
collectionId: scope.collectionId,
|
|
261
|
+
appId: fontsConfigAppId,
|
|
262
|
+
config: { fonts: families },
|
|
263
|
+
...admin ? { admin: true } : {}
|
|
264
|
+
});
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.error("[FontPicker] Failed to save custom fonts config:", err);
|
|
267
|
+
}
|
|
268
|
+
}, [scope?.collectionId, admin, fontsConfigAppId]);
|
|
269
|
+
const setSearch = useCallback((q) => {
|
|
270
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
271
|
+
debounceRef.current = setTimeout(() => {
|
|
272
|
+
setQuery(q.trim().toLowerCase());
|
|
273
|
+
setPage(0);
|
|
274
|
+
}, 200);
|
|
275
|
+
}, []);
|
|
276
|
+
const source = activeTab === "custom" ? customFonts : GOOGLE_FONTS_CATALOG;
|
|
277
|
+
const availableCategories = useMemo(() => {
|
|
278
|
+
const set = /* @__PURE__ */ new Set();
|
|
279
|
+
for (const font of GOOGLE_FONTS_CATALOG) {
|
|
280
|
+
if (font.category) {
|
|
281
|
+
if (!categories || categories.includes(font.category)) {
|
|
282
|
+
set.add(font.category);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const order = ["sans-serif", "serif", "display", "handwriting", "monospace"];
|
|
287
|
+
return order.filter((c) => set.has(c));
|
|
288
|
+
}, [categories]);
|
|
289
|
+
const filtered = useMemo(() => {
|
|
290
|
+
let result = source;
|
|
291
|
+
if (activeCategory && activeTab === "google") {
|
|
292
|
+
result = result.filter((f) => f.category === activeCategory);
|
|
293
|
+
}
|
|
294
|
+
if (query) {
|
|
295
|
+
result = result.filter((f) => f.family.toLowerCase().includes(query));
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
298
|
+
}, [source, activeCategory, activeTab, query]);
|
|
299
|
+
const totalPages = Math.ceil(filtered.length / pageSize);
|
|
300
|
+
const pageFonts = useMemo(
|
|
301
|
+
() => filtered.slice(page * pageSize, (page + 1) * pageSize),
|
|
302
|
+
[filtered, page, pageSize]
|
|
303
|
+
);
|
|
304
|
+
const [uploading, setUploading] = useState(false);
|
|
305
|
+
const [uploadProgress, setUploadProgress] = useState(0);
|
|
306
|
+
const uploadFont = useCallback(async (file) => {
|
|
307
|
+
if (!scope?.collectionId) return null;
|
|
308
|
+
setUploading(true);
|
|
309
|
+
setUploadProgress(0);
|
|
310
|
+
try {
|
|
311
|
+
const sl = await getSL();
|
|
312
|
+
const scopeObj = scope.productId ? { type: "product", collectionId: scope.collectionId, productId: scope.productId } : { type: "collection", collectionId: scope.collectionId };
|
|
313
|
+
const result = await sl.asset.upload({
|
|
314
|
+
file,
|
|
315
|
+
scope: scopeObj,
|
|
316
|
+
name: file.name,
|
|
317
|
+
...admin ? { admin: true } : {},
|
|
318
|
+
onProgress: (pct) => setUploadProgress(pct)
|
|
319
|
+
});
|
|
320
|
+
const style = {
|
|
321
|
+
url: result.url,
|
|
322
|
+
assetId: result.id,
|
|
323
|
+
weight: detectWeightFromFilename(file.name),
|
|
324
|
+
italic: detectItalicFromFilename(file.name),
|
|
325
|
+
mimeType: file.type,
|
|
326
|
+
fileName: file.name
|
|
327
|
+
};
|
|
328
|
+
return style;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.error("[FontPicker] Upload failed:", err);
|
|
331
|
+
return null;
|
|
332
|
+
} finally {
|
|
333
|
+
setUploading(false);
|
|
334
|
+
setUploadProgress(0);
|
|
335
|
+
}
|
|
336
|
+
}, [scope?.collectionId, scope?.productId, admin]);
|
|
337
|
+
const addFontStyle = useCallback(async (familyName, style) => {
|
|
338
|
+
const updated = [...customFamilies];
|
|
339
|
+
const existing = updated.find((f) => f.name === familyName);
|
|
340
|
+
if (existing) {
|
|
341
|
+
existing.fonts.push(style);
|
|
342
|
+
} else {
|
|
343
|
+
updated.push({ name: familyName, fonts: [style] });
|
|
344
|
+
}
|
|
345
|
+
setCustomFamilies(updated);
|
|
346
|
+
setCustomFonts(customFamiliesToEntries(updated));
|
|
347
|
+
await saveCustomFonts(updated);
|
|
348
|
+
}, [customFamilies, saveCustomFonts]);
|
|
349
|
+
const updateFontFamily = useCallback(async (index, family) => {
|
|
350
|
+
const updated = [...customFamilies];
|
|
351
|
+
updated[index] = family;
|
|
352
|
+
setCustomFamilies(updated);
|
|
353
|
+
setCustomFonts(customFamiliesToEntries(updated));
|
|
354
|
+
await saveCustomFonts(updated);
|
|
355
|
+
}, [customFamilies, saveCustomFonts]);
|
|
356
|
+
const deleteFontFamily = useCallback(async (index) => {
|
|
357
|
+
const updated = customFamilies.filter((_, i) => i !== index);
|
|
358
|
+
setCustomFamilies(updated);
|
|
359
|
+
setCustomFonts(customFamiliesToEntries(updated));
|
|
360
|
+
await saveCustomFonts(updated);
|
|
361
|
+
}, [customFamilies, saveCustomFonts]);
|
|
362
|
+
const deleteFontStyle = useCallback(async (familyIndex, styleIndex) => {
|
|
363
|
+
const updated = [...customFamilies];
|
|
364
|
+
const family = { ...updated[familyIndex], fonts: [...updated[familyIndex].fonts] };
|
|
365
|
+
family.fonts.splice(styleIndex, 1);
|
|
366
|
+
if (family.fonts.length === 0) {
|
|
367
|
+
updated.splice(familyIndex, 1);
|
|
368
|
+
} else {
|
|
369
|
+
updated[familyIndex] = family;
|
|
370
|
+
}
|
|
371
|
+
setCustomFamilies(updated);
|
|
372
|
+
setCustomFonts(customFamiliesToEntries(updated));
|
|
373
|
+
await saveCustomFonts(updated);
|
|
374
|
+
}, [customFamilies, saveCustomFonts]);
|
|
375
|
+
return {
|
|
376
|
+
loading: loadingCustom,
|
|
377
|
+
query,
|
|
378
|
+
setSearch,
|
|
379
|
+
activeTab,
|
|
380
|
+
setActiveTab: useCallback((t) => {
|
|
381
|
+
setActiveTab(t);
|
|
382
|
+
setPage(0);
|
|
383
|
+
}, []),
|
|
384
|
+
hasCustomFonts: showCustomFonts && !!scope?.collectionId,
|
|
385
|
+
customFontCount: customFamilies.length,
|
|
386
|
+
customFamilies,
|
|
387
|
+
activeCategory,
|
|
388
|
+
setActiveCategory: useCallback((c) => {
|
|
389
|
+
setActiveCategory(c);
|
|
390
|
+
setPage(0);
|
|
391
|
+
}, []),
|
|
392
|
+
availableCategories,
|
|
393
|
+
filtered,
|
|
394
|
+
pageFonts,
|
|
395
|
+
page,
|
|
396
|
+
setPage,
|
|
397
|
+
totalPages,
|
|
398
|
+
totalCount: filtered.length,
|
|
399
|
+
categoryLabels: CATEGORY_LABELS,
|
|
400
|
+
uploading,
|
|
401
|
+
uploadProgress,
|
|
402
|
+
uploadFont,
|
|
403
|
+
addFontStyle,
|
|
404
|
+
updateFontFamily,
|
|
405
|
+
deleteFontFamily,
|
|
406
|
+
deleteFontStyle
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
var ACCEPTED_EXTENSIONS = ".woff,.woff2,.ttf,.otf";
|
|
410
|
+
function isValidFontFile(file) {
|
|
411
|
+
const ext = file.name.toLowerCase().split(".").pop();
|
|
412
|
+
return ["woff", "woff2", "ttf", "otf"].includes(ext || "");
|
|
413
|
+
}
|
|
414
|
+
var FontUploadZone = ({
|
|
415
|
+
uploading,
|
|
416
|
+
uploadProgress,
|
|
417
|
+
onUpload,
|
|
418
|
+
className
|
|
419
|
+
}) => {
|
|
420
|
+
const [dragOver, setDragOver] = useState(false);
|
|
421
|
+
const [status, setStatus] = useState("idle");
|
|
422
|
+
const [errorMsg, setErrorMsg] = useState("");
|
|
423
|
+
const inputRef = useRef(null);
|
|
424
|
+
const handleFile = useCallback(async (file) => {
|
|
425
|
+
if (!isValidFontFile(file)) {
|
|
426
|
+
setStatus("error");
|
|
427
|
+
setErrorMsg("Unsupported format. Use .woff2, .ttf, .otf, or .woff");
|
|
428
|
+
setTimeout(() => setStatus("idle"), 3e3);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
setStatus("idle");
|
|
432
|
+
setErrorMsg("");
|
|
433
|
+
const result = await onUpload(file);
|
|
434
|
+
if (result) {
|
|
435
|
+
setStatus("success");
|
|
436
|
+
setTimeout(() => setStatus("idle"), 2e3);
|
|
437
|
+
} else {
|
|
438
|
+
setStatus("error");
|
|
439
|
+
setErrorMsg("Upload failed");
|
|
440
|
+
setTimeout(() => setStatus("idle"), 3e3);
|
|
441
|
+
}
|
|
442
|
+
}, [onUpload]);
|
|
443
|
+
const onDrop = useCallback((e) => {
|
|
444
|
+
e.preventDefault();
|
|
445
|
+
setDragOver(false);
|
|
446
|
+
const file = e.dataTransfer.files[0];
|
|
447
|
+
if (file) handleFile(file);
|
|
448
|
+
}, [handleFile]);
|
|
449
|
+
const onDragOver = useCallback((e) => {
|
|
450
|
+
e.preventDefault();
|
|
451
|
+
setDragOver(true);
|
|
452
|
+
}, []);
|
|
453
|
+
const onDragLeave = useCallback(() => setDragOver(false), []);
|
|
454
|
+
const onInputChange = useCallback((e) => {
|
|
455
|
+
const file = e.target.files?.[0];
|
|
456
|
+
if (file) handleFile(file);
|
|
457
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
458
|
+
}, [handleFile]);
|
|
459
|
+
return /* @__PURE__ */ jsxs(
|
|
460
|
+
"div",
|
|
461
|
+
{
|
|
462
|
+
onDrop,
|
|
463
|
+
onDragOver,
|
|
464
|
+
onDragLeave,
|
|
465
|
+
onClick: () => !uploading && inputRef.current?.click(),
|
|
466
|
+
className: cn(
|
|
467
|
+
"relative flex flex-col items-center justify-center gap-2 p-6 rounded-lg border-2 border-dashed cursor-pointer transition-colors",
|
|
468
|
+
dragOver ? "border-primary bg-primary/5" : status === "error" ? "border-destructive/50 bg-destructive/5" : status === "success" ? "border-primary/50 bg-primary/5" : "border-border hover:border-muted-foreground/40 hover:bg-accent/30",
|
|
469
|
+
uploading && "pointer-events-none opacity-70",
|
|
470
|
+
className
|
|
471
|
+
),
|
|
472
|
+
children: [
|
|
473
|
+
/* @__PURE__ */ jsx(
|
|
474
|
+
"input",
|
|
475
|
+
{
|
|
476
|
+
ref: inputRef,
|
|
477
|
+
type: "file",
|
|
478
|
+
accept: ACCEPTED_EXTENSIONS,
|
|
479
|
+
onChange: onInputChange,
|
|
480
|
+
className: "hidden"
|
|
481
|
+
}
|
|
482
|
+
),
|
|
483
|
+
uploading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
484
|
+
/* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 animate-spin text-primary" }),
|
|
485
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
486
|
+
"Uploading\u2026 ",
|
|
487
|
+
uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ""
|
|
488
|
+
] }),
|
|
489
|
+
uploadProgress > 0 && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[200px] h-1.5 bg-muted rounded-full overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
490
|
+
"div",
|
|
491
|
+
{
|
|
492
|
+
className: "h-full bg-primary rounded-full transition-all duration-300",
|
|
493
|
+
style: { width: `${uploadProgress}%` }
|
|
494
|
+
}
|
|
495
|
+
) })
|
|
496
|
+
] }) : status === "success" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
497
|
+
/* @__PURE__ */ jsx(CheckCircle2, { className: "w-6 h-6 text-primary" }),
|
|
498
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-primary", children: "Font uploaded!" })
|
|
499
|
+
] }) : status === "error" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
500
|
+
/* @__PURE__ */ jsx(AlertCircle, { className: "w-6 h-6 text-destructive" }),
|
|
501
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: errorMsg })
|
|
502
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
503
|
+
/* @__PURE__ */ jsx(Upload, { className: "w-6 h-6 text-muted-foreground" }),
|
|
504
|
+
/* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
505
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-foreground font-medium", children: [
|
|
506
|
+
"Drop a font file or ",
|
|
507
|
+
/* @__PURE__ */ jsx("span", { className: "text-primary", children: "browse" })
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: ".woff2, .ttf, .otf, .woff" })
|
|
510
|
+
] })
|
|
511
|
+
] })
|
|
512
|
+
]
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
};
|
|
516
|
+
var WEIGHT_OPTIONS = [
|
|
517
|
+
{ value: 100, label: "Thin (100)" },
|
|
518
|
+
{ value: 200, label: "Extra Light (200)" },
|
|
519
|
+
{ value: 300, label: "Light (300)" },
|
|
520
|
+
{ value: 400, label: "Regular (400)" },
|
|
521
|
+
{ value: 500, label: "Medium (500)" },
|
|
522
|
+
{ value: 600, label: "Semi Bold (600)" },
|
|
523
|
+
{ value: 700, label: "Bold (700)" },
|
|
524
|
+
{ value: 800, label: "Extra Bold (800)" },
|
|
525
|
+
{ value: 900, label: "Black (900)" }
|
|
526
|
+
];
|
|
527
|
+
var FontUploadForm = ({
|
|
528
|
+
defaultFamilyName,
|
|
529
|
+
style,
|
|
530
|
+
existingFamilies,
|
|
531
|
+
onConfirm,
|
|
532
|
+
onCancel,
|
|
533
|
+
className
|
|
534
|
+
}) => {
|
|
535
|
+
const [familyName, setFamilyName] = useState(defaultFamilyName);
|
|
536
|
+
const [weight, setWeight] = useState(style.weight);
|
|
537
|
+
const [italic, setItalic] = useState(style.italic);
|
|
538
|
+
const handleConfirm = useCallback(() => {
|
|
539
|
+
if (!familyName.trim()) return;
|
|
540
|
+
onConfirm(familyName.trim(), { ...style, weight, italic });
|
|
541
|
+
}, [familyName, weight, italic, style, onConfirm]);
|
|
542
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border border-border bg-card p-4 space-y-3", className), children: [
|
|
543
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-foreground uppercase tracking-wide", children: "New Font Style" }),
|
|
544
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
545
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: "Font Family Name" }),
|
|
546
|
+
/* @__PURE__ */ jsx(
|
|
547
|
+
"input",
|
|
548
|
+
{
|
|
549
|
+
type: "text",
|
|
550
|
+
value: familyName,
|
|
551
|
+
onChange: (e) => setFamilyName(e.target.value),
|
|
552
|
+
placeholder: "e.g. Acme Sans",
|
|
553
|
+
className: "w-full px-2.5 py-1.5 text-sm rounded-md border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring",
|
|
554
|
+
list: "existing-families"
|
|
555
|
+
}
|
|
556
|
+
),
|
|
557
|
+
existingFamilies.length > 0 && /* @__PURE__ */ jsx("datalist", { id: "existing-families", children: existingFamilies.map((f) => /* @__PURE__ */ jsx("option", { value: f }, f)) }),
|
|
558
|
+
existingFamilies.includes(familyName.trim()) && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground", children: [
|
|
559
|
+
'Will add as a new style to existing family "',
|
|
560
|
+
familyName.trim(),
|
|
561
|
+
'"'
|
|
562
|
+
] })
|
|
563
|
+
] }),
|
|
564
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-end", children: [
|
|
565
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1", children: [
|
|
566
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: "Weight" }),
|
|
567
|
+
/* @__PURE__ */ jsx(
|
|
568
|
+
"select",
|
|
569
|
+
{
|
|
570
|
+
value: weight,
|
|
571
|
+
onChange: (e) => setWeight(Number(e.target.value)),
|
|
572
|
+
className: "w-full px-2.5 py-1.5 text-sm rounded-md border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring",
|
|
573
|
+
children: WEIGHT_OPTIONS.map((w) => /* @__PURE__ */ jsx("option", { value: w.value, children: w.label }, w.value))
|
|
574
|
+
}
|
|
575
|
+
)
|
|
576
|
+
] }),
|
|
577
|
+
/* @__PURE__ */ jsx(
|
|
578
|
+
"button",
|
|
579
|
+
{
|
|
580
|
+
onClick: () => setItalic(!italic),
|
|
581
|
+
className: cn(
|
|
582
|
+
"px-3 py-1.5 text-sm rounded-md border transition-colors shrink-0",
|
|
583
|
+
italic ? "border-primary bg-primary/10 text-primary font-medium italic" : "border-border text-muted-foreground hover:border-muted-foreground"
|
|
584
|
+
),
|
|
585
|
+
children: "Italic"
|
|
586
|
+
}
|
|
587
|
+
)
|
|
588
|
+
] }),
|
|
589
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground truncate", children: [
|
|
590
|
+
"File: ",
|
|
591
|
+
style.fileName || "unknown"
|
|
592
|
+
] }),
|
|
593
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end pt-1", children: [
|
|
594
|
+
/* @__PURE__ */ jsx(
|
|
595
|
+
"button",
|
|
596
|
+
{
|
|
597
|
+
onClick: onCancel,
|
|
598
|
+
className: "px-3 py-1.5 text-xs font-medium rounded-md text-muted-foreground hover:bg-accent transition-colors",
|
|
599
|
+
children: "Cancel"
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
/* @__PURE__ */ jsx(
|
|
603
|
+
"button",
|
|
604
|
+
{
|
|
605
|
+
onClick: handleConfirm,
|
|
606
|
+
disabled: !familyName.trim(),
|
|
607
|
+
className: "px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-40 transition-colors",
|
|
608
|
+
children: "Add Font"
|
|
609
|
+
}
|
|
610
|
+
)
|
|
611
|
+
] })
|
|
612
|
+
] });
|
|
613
|
+
};
|
|
614
|
+
var weightLabel = (w) => WEIGHT_OPTIONS.find((o) => o.value === w)?.label || `${w}`;
|
|
615
|
+
var FontFamilyCard = ({
|
|
616
|
+
family,
|
|
617
|
+
familyIndex,
|
|
618
|
+
onDeleteFamily,
|
|
619
|
+
onDeleteStyle,
|
|
620
|
+
className
|
|
621
|
+
}) => {
|
|
622
|
+
const [expanded, setExpanded] = useState(false);
|
|
623
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border border-border bg-card", className), children: [
|
|
624
|
+
/* @__PURE__ */ jsxs(
|
|
625
|
+
"button",
|
|
626
|
+
{
|
|
627
|
+
onClick: () => setExpanded(!expanded),
|
|
628
|
+
className: "w-full flex items-center gap-2 px-3 py-2.5 text-left",
|
|
629
|
+
children: [
|
|
630
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
631
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground truncate block", children: family.name }),
|
|
632
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
|
|
633
|
+
family.fonts.length,
|
|
634
|
+
" style",
|
|
635
|
+
family.fonts.length !== 1 ? "s" : ""
|
|
636
|
+
] })
|
|
637
|
+
] }),
|
|
638
|
+
expanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "w-3.5 h-3.5 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "w-3.5 h-3.5 text-muted-foreground shrink-0" })
|
|
639
|
+
]
|
|
640
|
+
}
|
|
641
|
+
),
|
|
642
|
+
expanded && /* @__PURE__ */ jsxs("div", { className: "border-t border-border px-3 py-2 space-y-1.5", children: [
|
|
643
|
+
family.fonts.map((style, idx) => /* @__PURE__ */ jsxs(
|
|
644
|
+
"div",
|
|
645
|
+
{
|
|
646
|
+
className: "flex items-center gap-2 text-xs text-muted-foreground",
|
|
647
|
+
children: [
|
|
648
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-1 truncate", children: [
|
|
649
|
+
weightLabel(style.weight),
|
|
650
|
+
style.italic ? ", Italic" : "",
|
|
651
|
+
style.fileName && /* @__PURE__ */ jsxs("span", { className: "ml-1 opacity-50", children: [
|
|
652
|
+
"\u2014 ",
|
|
653
|
+
style.fileName
|
|
654
|
+
] })
|
|
655
|
+
] }),
|
|
656
|
+
/* @__PURE__ */ jsx(
|
|
657
|
+
"button",
|
|
658
|
+
{
|
|
659
|
+
onClick: (e) => {
|
|
660
|
+
e.stopPropagation();
|
|
661
|
+
onDeleteStyle(familyIndex, idx);
|
|
662
|
+
},
|
|
663
|
+
className: "p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive transition-colors shrink-0",
|
|
664
|
+
title: "Remove style",
|
|
665
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "w-3 h-3" })
|
|
666
|
+
}
|
|
667
|
+
)
|
|
668
|
+
]
|
|
669
|
+
},
|
|
670
|
+
idx
|
|
671
|
+
)),
|
|
672
|
+
/* @__PURE__ */ jsx("div", { className: "pt-1 border-t border-border", children: /* @__PURE__ */ jsx(
|
|
673
|
+
"button",
|
|
674
|
+
{
|
|
675
|
+
onClick: (e) => {
|
|
676
|
+
e.stopPropagation();
|
|
677
|
+
onDeleteFamily(familyIndex);
|
|
678
|
+
},
|
|
679
|
+
className: "text-[11px] text-destructive hover:text-destructive/80 font-medium transition-colors",
|
|
680
|
+
children: "Delete entire family"
|
|
681
|
+
}
|
|
682
|
+
) })
|
|
683
|
+
] })
|
|
684
|
+
] });
|
|
685
|
+
};
|
|
686
|
+
var loadedFonts = /* @__PURE__ */ new Set();
|
|
687
|
+
function ensureFontLoaded(entry) {
|
|
688
|
+
if (!entry.loadUrl || loadedFonts.has(entry.family)) return;
|
|
689
|
+
loadedFonts.add(entry.family);
|
|
690
|
+
if (entry.source === "google") {
|
|
691
|
+
const link = document.createElement("link");
|
|
692
|
+
link.rel = "stylesheet";
|
|
693
|
+
link.href = entry.loadUrl;
|
|
694
|
+
link.dataset.fontPicker = entry.family;
|
|
695
|
+
document.head.appendChild(link);
|
|
696
|
+
} else if (entry.source === "custom") {
|
|
697
|
+
const style = document.createElement("style");
|
|
698
|
+
style.dataset.fontPicker = entry.family;
|
|
699
|
+
style.textContent = `@font-face { font-family: '${entry.family}'; src: url('${entry.loadUrl}'); font-display: swap; }`;
|
|
700
|
+
document.head.appendChild(style);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function buildSelection(entry) {
|
|
704
|
+
const fallback = CATEGORY_FALLBACKS[entry.category || "sans-serif"];
|
|
705
|
+
const cssFontFamily = `'${entry.family}', ${fallback}`;
|
|
706
|
+
let loadSnippet;
|
|
707
|
+
if (entry.source === "google" && entry.loadUrl) {
|
|
708
|
+
loadSnippet = `<link href="${entry.loadUrl}" rel="stylesheet">`;
|
|
709
|
+
} else if (entry.source === "custom" && entry.loadUrl) {
|
|
710
|
+
loadSnippet = `@font-face {
|
|
711
|
+
font-family: '${entry.family}';
|
|
712
|
+
src: url('${entry.loadUrl}');
|
|
713
|
+
font-display: swap;
|
|
714
|
+
}`;
|
|
715
|
+
} else {
|
|
716
|
+
loadSnippet = `/* Font "${entry.family}" \u2014 no load URL available */`;
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
family: entry.family,
|
|
720
|
+
source: entry.source,
|
|
721
|
+
loadUrl: entry.loadUrl,
|
|
722
|
+
category: entry.category,
|
|
723
|
+
weights: entry.weights,
|
|
724
|
+
fileUrl: entry.source === "custom" ? entry.loadUrl : void 0,
|
|
725
|
+
assetId: entry.assetId,
|
|
726
|
+
cssFontFamily,
|
|
727
|
+
loadSnippet
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
var FontItem = ({ entry, isSelected, previewText, showPreview, onClick }) => {
|
|
731
|
+
const ref = useRef(null);
|
|
732
|
+
useEffect(() => {
|
|
733
|
+
if (!ref.current) return;
|
|
734
|
+
const observer = new IntersectionObserver(
|
|
735
|
+
([e]) => {
|
|
736
|
+
if (e.isIntersecting) {
|
|
737
|
+
ensureFontLoaded(entry);
|
|
738
|
+
observer.disconnect();
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
{ rootMargin: "100px" }
|
|
742
|
+
);
|
|
743
|
+
observer.observe(ref.current);
|
|
744
|
+
return () => observer.disconnect();
|
|
745
|
+
}, [entry]);
|
|
746
|
+
return /* @__PURE__ */ jsxs(
|
|
747
|
+
"button",
|
|
748
|
+
{
|
|
749
|
+
ref,
|
|
750
|
+
onClick,
|
|
751
|
+
className: cn(
|
|
752
|
+
"w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-colors",
|
|
753
|
+
isSelected ? "bg-primary/10 ring-1 ring-primary" : "hover:bg-accent/50"
|
|
754
|
+
),
|
|
755
|
+
children: [
|
|
756
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
757
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
758
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground truncate", children: entry.family }),
|
|
759
|
+
entry.source === "custom" && /* @__PURE__ */ jsxs("span", { className: "shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded bg-accent text-accent-foreground", children: [
|
|
760
|
+
/* @__PURE__ */ jsx(Upload, { className: "w-2.5 h-2.5" }),
|
|
761
|
+
"Custom"
|
|
762
|
+
] }),
|
|
763
|
+
entry.source === "custom" && entry.weights && entry.weights.length > 0 && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-[10px] text-muted-foreground", children: [
|
|
764
|
+
entry.weights.length,
|
|
765
|
+
" style",
|
|
766
|
+
entry.weights.length !== 1 ? "s" : ""
|
|
767
|
+
] }),
|
|
768
|
+
entry.category && entry.source === "google" && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-muted-foreground capitalize", children: entry.category })
|
|
769
|
+
] }),
|
|
770
|
+
showPreview && /* @__PURE__ */ jsx(
|
|
771
|
+
"p",
|
|
772
|
+
{
|
|
773
|
+
className: "text-base text-muted-foreground mt-0.5 truncate",
|
|
774
|
+
style: { fontFamily: `'${entry.family}', sans-serif` },
|
|
775
|
+
children: previewText
|
|
776
|
+
}
|
|
777
|
+
)
|
|
778
|
+
] }),
|
|
779
|
+
isSelected && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-primary shrink-0" })
|
|
780
|
+
]
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
};
|
|
784
|
+
var FontPickerContent = ({
|
|
785
|
+
value,
|
|
786
|
+
onSelect,
|
|
787
|
+
onConfirm,
|
|
788
|
+
scope,
|
|
789
|
+
fontsConfigAppId,
|
|
790
|
+
showCustomFonts = false,
|
|
791
|
+
categories,
|
|
792
|
+
showPreview = true,
|
|
793
|
+
previewText = "The quick brown fox jumps over the lazy dog",
|
|
794
|
+
pageSize = 50,
|
|
795
|
+
admin,
|
|
796
|
+
className
|
|
797
|
+
}) => {
|
|
798
|
+
const {
|
|
799
|
+
loading,
|
|
800
|
+
setSearch,
|
|
801
|
+
activeTab,
|
|
802
|
+
setActiveTab,
|
|
803
|
+
hasCustomFonts,
|
|
804
|
+
customFontCount,
|
|
805
|
+
customFamilies,
|
|
806
|
+
activeCategory,
|
|
807
|
+
setActiveCategory,
|
|
808
|
+
availableCategories,
|
|
809
|
+
pageFonts,
|
|
810
|
+
page,
|
|
811
|
+
setPage,
|
|
812
|
+
totalPages,
|
|
813
|
+
totalCount,
|
|
814
|
+
categoryLabels,
|
|
815
|
+
uploading,
|
|
816
|
+
uploadProgress,
|
|
817
|
+
uploadFont,
|
|
818
|
+
addFontStyle,
|
|
819
|
+
deleteFontFamily,
|
|
820
|
+
deleteFontStyle
|
|
821
|
+
} = useFontSearch({ categories, scope, showCustomFonts, admin, pageSize, fontsConfigAppId });
|
|
822
|
+
const inputRef = useRef(null);
|
|
823
|
+
useEffect(() => {
|
|
824
|
+
inputRef.current?.focus();
|
|
825
|
+
}, []);
|
|
826
|
+
const [pendingStyle, setPendingStyle] = useState(null);
|
|
827
|
+
const [pendingFamilyName, setPendingFamilyName] = useState("");
|
|
828
|
+
const handleUpload = useCallback(async (file) => {
|
|
829
|
+
const style = await uploadFont(file);
|
|
830
|
+
if (style) {
|
|
831
|
+
setPendingFamilyName(fontFamilyFromFilename(file.name));
|
|
832
|
+
setPendingStyle(style);
|
|
833
|
+
}
|
|
834
|
+
return style;
|
|
835
|
+
}, [uploadFont]);
|
|
836
|
+
const handleConfirmUpload = useCallback(async (familyName, style) => {
|
|
837
|
+
await addFontStyle(familyName, style);
|
|
838
|
+
setPendingStyle(null);
|
|
839
|
+
setPendingFamilyName("");
|
|
840
|
+
}, [addFontStyle]);
|
|
841
|
+
const handleSelect = useCallback(
|
|
842
|
+
(entry) => {
|
|
843
|
+
const selection = buildSelection(entry);
|
|
844
|
+
onSelect?.(selection);
|
|
845
|
+
onConfirm?.(selection);
|
|
846
|
+
},
|
|
847
|
+
[onSelect, onConfirm]
|
|
848
|
+
);
|
|
849
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
850
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
851
|
+
/* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" }),
|
|
852
|
+
/* @__PURE__ */ jsx(
|
|
853
|
+
"input",
|
|
854
|
+
{
|
|
855
|
+
ref: inputRef,
|
|
856
|
+
type: "text",
|
|
857
|
+
placeholder: "Search fonts\u2026",
|
|
858
|
+
onChange: (e) => setSearch(e.target.value),
|
|
859
|
+
className: "w-full pl-9 pr-3 py-2 text-sm rounded-md border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring"
|
|
860
|
+
}
|
|
861
|
+
)
|
|
862
|
+
] }),
|
|
863
|
+
hasCustomFonts && /* @__PURE__ */ jsxs("div", { className: "flex gap-1 border-b border-border pb-2", children: [
|
|
864
|
+
/* @__PURE__ */ jsxs(
|
|
865
|
+
"button",
|
|
866
|
+
{
|
|
867
|
+
onClick: () => setActiveTab("google"),
|
|
868
|
+
className: cn(
|
|
869
|
+
"px-3 py-1.5 text-xs font-semibold rounded-md transition-colors inline-flex items-center gap-1.5",
|
|
870
|
+
activeTab === "google" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
871
|
+
),
|
|
872
|
+
children: [
|
|
873
|
+
/* @__PURE__ */ jsx(Type, { className: "w-3.5 h-3.5" }),
|
|
874
|
+
"Google Fonts"
|
|
875
|
+
]
|
|
876
|
+
}
|
|
877
|
+
),
|
|
878
|
+
/* @__PURE__ */ jsxs(
|
|
879
|
+
"button",
|
|
880
|
+
{
|
|
881
|
+
onClick: () => setActiveTab("custom"),
|
|
882
|
+
className: cn(
|
|
883
|
+
"px-3 py-1.5 text-xs font-semibold rounded-md transition-colors inline-flex items-center gap-1.5",
|
|
884
|
+
activeTab === "custom" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
885
|
+
),
|
|
886
|
+
children: [
|
|
887
|
+
/* @__PURE__ */ jsx(Upload, { className: "w-3.5 h-3.5" }),
|
|
888
|
+
"Custom",
|
|
889
|
+
customFontCount > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-[10px] opacity-70", children: [
|
|
890
|
+
"(",
|
|
891
|
+
customFontCount,
|
|
892
|
+
")"
|
|
893
|
+
] })
|
|
894
|
+
]
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
] }),
|
|
898
|
+
activeTab === "google" && availableCategories.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex gap-1 flex-wrap items-center", children: [
|
|
899
|
+
/* @__PURE__ */ jsx(
|
|
900
|
+
"button",
|
|
901
|
+
{
|
|
902
|
+
onClick: () => setActiveCategory(null),
|
|
903
|
+
className: cn(
|
|
904
|
+
"px-2.5 py-1 text-xs font-medium rounded-full transition-colors",
|
|
905
|
+
!activeCategory ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:bg-accent"
|
|
906
|
+
),
|
|
907
|
+
children: "All"
|
|
908
|
+
}
|
|
909
|
+
),
|
|
910
|
+
availableCategories.map((c) => /* @__PURE__ */ jsx(
|
|
911
|
+
"button",
|
|
912
|
+
{
|
|
913
|
+
onClick: () => setActiveCategory(c === activeCategory ? null : c),
|
|
914
|
+
className: cn(
|
|
915
|
+
"px-2.5 py-1 text-xs font-medium rounded-full transition-colors",
|
|
916
|
+
c === activeCategory ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:bg-accent"
|
|
917
|
+
),
|
|
918
|
+
children: categoryLabels[c]
|
|
919
|
+
},
|
|
920
|
+
c
|
|
921
|
+
)),
|
|
922
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto text-[11px] text-muted-foreground", children: [
|
|
923
|
+
totalCount,
|
|
924
|
+
" fonts"
|
|
925
|
+
] })
|
|
926
|
+
] }),
|
|
927
|
+
activeTab === "custom" && !loading && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
928
|
+
scope?.collectionId && /* @__PURE__ */ jsx(
|
|
929
|
+
FontUploadZone,
|
|
930
|
+
{
|
|
931
|
+
uploading,
|
|
932
|
+
uploadProgress,
|
|
933
|
+
onUpload: handleUpload
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
pendingStyle && /* @__PURE__ */ jsx(
|
|
937
|
+
FontUploadForm,
|
|
938
|
+
{
|
|
939
|
+
defaultFamilyName: pendingFamilyName,
|
|
940
|
+
style: pendingStyle,
|
|
941
|
+
existingFamilies: customFamilies.map((f) => f.name),
|
|
942
|
+
onConfirm: handleConfirmUpload,
|
|
943
|
+
onCancel: () => {
|
|
944
|
+
setPendingStyle(null);
|
|
945
|
+
setPendingFamilyName("");
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
),
|
|
949
|
+
customFamilies.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
950
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide", children: [
|
|
951
|
+
"Uploaded Fonts (",
|
|
952
|
+
customFamilies.length,
|
|
953
|
+
")"
|
|
954
|
+
] }),
|
|
955
|
+
customFamilies.map((fam, idx) => /* @__PURE__ */ jsx(
|
|
956
|
+
FontFamilyCard,
|
|
957
|
+
{
|
|
958
|
+
family: fam,
|
|
959
|
+
familyIndex: idx,
|
|
960
|
+
onDeleteFamily: deleteFontFamily,
|
|
961
|
+
onDeleteStyle: deleteFontStyle
|
|
962
|
+
},
|
|
963
|
+
`${fam.name}-${idx}`
|
|
964
|
+
))
|
|
965
|
+
] })
|
|
966
|
+
] }),
|
|
967
|
+
loading && activeTab === "custom" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 gap-3", children: [
|
|
968
|
+
/* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 animate-spin text-muted-foreground" }),
|
|
969
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading custom fonts\u2026" })
|
|
970
|
+
] }),
|
|
971
|
+
activeTab === "google" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
972
|
+
!loading && pageFonts.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-10 gap-2", children: [
|
|
973
|
+
/* @__PURE__ */ jsx(Type, { className: "w-6 h-6 text-muted-foreground/40" }),
|
|
974
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No fonts found" })
|
|
975
|
+
] }) : !loading && /* @__PURE__ */ jsx("div", { className: "space-y-0.5 max-h-[400px] overflow-y-auto", children: pageFonts.map((font) => /* @__PURE__ */ jsx(
|
|
976
|
+
FontItem,
|
|
977
|
+
{
|
|
978
|
+
entry: font,
|
|
979
|
+
isSelected: value === font.family,
|
|
980
|
+
previewText,
|
|
981
|
+
showPreview,
|
|
982
|
+
onClick: () => handleSelect(font)
|
|
983
|
+
},
|
|
984
|
+
`${font.source}-${font.family}`
|
|
985
|
+
)) }),
|
|
986
|
+
totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
987
|
+
/* @__PURE__ */ jsx(
|
|
988
|
+
"button",
|
|
989
|
+
{
|
|
990
|
+
onClick: () => setPage(Math.max(0, page - 1)),
|
|
991
|
+
disabled: page === 0,
|
|
992
|
+
className: "p-1 rounded hover:bg-accent disabled:opacity-30 transition-colors",
|
|
993
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-4 h-4" })
|
|
994
|
+
}
|
|
995
|
+
),
|
|
996
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
997
|
+
page + 1,
|
|
998
|
+
" / ",
|
|
999
|
+
totalPages
|
|
1000
|
+
] }),
|
|
1001
|
+
/* @__PURE__ */ jsx(
|
|
1002
|
+
"button",
|
|
1003
|
+
{
|
|
1004
|
+
onClick: () => setPage(Math.min(totalPages - 1, page + 1)),
|
|
1005
|
+
disabled: page >= totalPages - 1,
|
|
1006
|
+
className: "p-1 rounded hover:bg-accent disabled:opacity-30 transition-colors",
|
|
1007
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-4 h-4" })
|
|
1008
|
+
}
|
|
1009
|
+
)
|
|
1010
|
+
] })
|
|
1011
|
+
] }),
|
|
1012
|
+
activeTab === "custom" && !loading && pageFonts.length > 0 && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5 max-h-[300px] overflow-y-auto border-t border-border pt-3", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Select a Custom Font" }),
|
|
1014
|
+
pageFonts.map((font) => /* @__PURE__ */ jsx(
|
|
1015
|
+
FontItem,
|
|
1016
|
+
{
|
|
1017
|
+
entry: font,
|
|
1018
|
+
isSelected: value === font.family,
|
|
1019
|
+
previewText,
|
|
1020
|
+
showPreview,
|
|
1021
|
+
onClick: () => handleSelect(font)
|
|
1022
|
+
},
|
|
1023
|
+
`${font.source}-${font.family}`
|
|
1024
|
+
))
|
|
1025
|
+
] }) })
|
|
1026
|
+
] });
|
|
1027
|
+
};
|
|
1028
|
+
var PickerDialog = ({ open, onClose, children }) => {
|
|
1029
|
+
if (!open) return null;
|
|
1030
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1031
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: onClose }),
|
|
1032
|
+
/* @__PURE__ */ jsxs("div", { className: "relative z-10 w-full max-w-lg max-h-[80vh] bg-background rounded-xl shadow-2xl border border-border flex flex-col overflow-hidden mx-4", children: [
|
|
1033
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border", children: [
|
|
1034
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: "Select Font" }),
|
|
1035
|
+
/* @__PURE__ */ jsx("button", { onClick: onClose, className: "p-1 rounded hover:bg-accent transition-colors", children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
1036
|
+
] }),
|
|
1037
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-4", children })
|
|
1038
|
+
] })
|
|
1039
|
+
] });
|
|
1040
|
+
};
|
|
1041
|
+
var FontPicker = (props) => {
|
|
1042
|
+
const {
|
|
1043
|
+
mode = "inline",
|
|
1044
|
+
trigger,
|
|
1045
|
+
open: controlledOpen,
|
|
1046
|
+
onClose,
|
|
1047
|
+
value,
|
|
1048
|
+
onSelect,
|
|
1049
|
+
className,
|
|
1050
|
+
...rest
|
|
1051
|
+
} = props;
|
|
1052
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
1053
|
+
const isOpen = controlledOpen ?? internalOpen;
|
|
1054
|
+
const handleClose = useCallback(() => {
|
|
1055
|
+
setInternalOpen(false);
|
|
1056
|
+
onClose?.();
|
|
1057
|
+
}, [onClose]);
|
|
1058
|
+
const handleSelectAndClose = useCallback(
|
|
1059
|
+
(font) => {
|
|
1060
|
+
onSelect?.(font);
|
|
1061
|
+
handleClose();
|
|
1062
|
+
},
|
|
1063
|
+
[onSelect, handleClose]
|
|
1064
|
+
);
|
|
1065
|
+
if (mode === "inline") {
|
|
1066
|
+
return /* @__PURE__ */ jsx(FontPickerContent, { value, onSelect, className, ...rest });
|
|
1067
|
+
}
|
|
1068
|
+
const triggerElement = trigger || /* @__PURE__ */ jsxs(
|
|
1069
|
+
"div",
|
|
1070
|
+
{
|
|
1071
|
+
className: cn(
|
|
1072
|
+
"inline-flex items-center gap-2 px-3 py-2 rounded-md border border-border cursor-pointer",
|
|
1073
|
+
"hover:border-ring transition-colors",
|
|
1074
|
+
className
|
|
1075
|
+
),
|
|
1076
|
+
children: [
|
|
1077
|
+
value ? /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", style: { fontFamily: `'${value}', sans-serif` }, children: value }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Choose font\u2026" }),
|
|
1078
|
+
/* @__PURE__ */ jsx(Type, { className: "w-3.5 h-3.5 text-muted-foreground ml-auto" })
|
|
1079
|
+
]
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1083
|
+
/* @__PURE__ */ jsx("span", { onClick: () => setInternalOpen(true), className: "cursor-pointer", children: triggerElement }),
|
|
1084
|
+
/* @__PURE__ */ jsx(PickerDialog, { open: isOpen, onClose: handleClose, children: /* @__PURE__ */ jsx(FontPickerContent, { value, onSelect: handleSelectAndClose, ...rest }) })
|
|
1085
|
+
] });
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
export { CATEGORY_FALLBACKS, CATEGORY_LABELS, FontPicker, GOOGLE_FONTS_CATALOG, getGoogleFontUrl };
|
|
1089
|
+
//# sourceMappingURL=chunk-4Z46C4MJ.js.map
|
|
1090
|
+
//# sourceMappingURL=chunk-4Z46C4MJ.js.map
|