radtools 0.1.0
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 +108 -0
- package/bin/radtools.js +5 -0
- package/dist/cli/index.js +427 -0
- package/package.json +55 -0
- package/templates/api-routes/assets/optimize/route.ts +94 -0
- package/templates/api-routes/assets/route.ts +159 -0
- package/templates/api-routes/components/create-folder/route.ts +55 -0
- package/templates/api-routes/components/route.ts +156 -0
- package/templates/api-routes/fonts/route.ts +96 -0
- package/templates/api-routes/fonts/upload/route.ts +79 -0
- package/templates/api-routes/read-css/route.ts +29 -0
- package/templates/api-routes/write-css/route.ts +423 -0
- package/templates/components/Rad_os/AppWindow.tsx +423 -0
- package/templates/components/Rad_os/MobileAppModal.tsx +76 -0
- package/templates/components/Rad_os/WindowTitleBar.tsx +290 -0
- package/templates/components/icons/Icon.tsx +224 -0
- package/templates/components/icons/README.md +85 -0
- package/templates/components/icons/index.ts +20 -0
- package/templates/components/icons.tsx +164 -0
- package/templates/components/ui/Accordion.tsx +268 -0
- package/templates/components/ui/Alert.tsx +111 -0
- package/templates/components/ui/Badge.tsx +87 -0
- package/templates/components/ui/Breadcrumbs.tsx +88 -0
- package/templates/components/ui/Button.tsx +249 -0
- package/templates/components/ui/Card.tsx +137 -0
- package/templates/components/ui/Checkbox.tsx +137 -0
- package/templates/components/ui/ContextMenu.tsx +220 -0
- package/templates/components/ui/Dialog.tsx +264 -0
- package/templates/components/ui/Divider.tsx +70 -0
- package/templates/components/ui/DropdownMenu.tsx +301 -0
- package/templates/components/ui/HelpPanel.tsx +119 -0
- package/templates/components/ui/Input.tsx +176 -0
- package/templates/components/ui/Popover.tsx +211 -0
- package/templates/components/ui/Progress.tsx +158 -0
- package/templates/components/ui/Select.tsx +134 -0
- package/templates/components/ui/Sheet.tsx +316 -0
- package/templates/components/ui/Slider.tsx +223 -0
- package/templates/components/ui/Switch.tsx +155 -0
- package/templates/components/ui/Tabs.tsx +253 -0
- package/templates/components/ui/Toast.tsx +192 -0
- package/templates/components/ui/Tooltip.tsx +129 -0
- package/templates/components/ui/hooks/useModalBehavior.ts +66 -0
- package/templates/components/ui/index.ts +84 -0
- package/templates/devtools/DevToolsPanel.tsx +261 -0
- package/templates/devtools/DevToolsProvider.tsx +43 -0
- package/templates/devtools/components/BreakpointIndicator.tsx +49 -0
- package/templates/devtools/components/ColorPicker.tsx +33 -0
- package/templates/devtools/components/ComponentsSecondaryNav.tsx +44 -0
- package/templates/devtools/components/ContextualFooter.tsx +56 -0
- package/templates/devtools/components/DraggablePanel.tsx +43 -0
- package/templates/devtools/components/PrimaryNavigationFooter.tsx +254 -0
- package/templates/devtools/components/SearchableColorDropdown.tsx +253 -0
- package/templates/devtools/components/SecondaryNavigation.tsx +36 -0
- package/templates/devtools/components/TokenDropdown.tsx +47 -0
- package/templates/devtools/components/TypographyFooter.tsx +145 -0
- package/templates/devtools/hooks/useMockState.ts +16 -0
- package/templates/devtools/index.ts +17 -0
- package/templates/devtools/lib/componentScanner.ts +78 -0
- package/templates/devtools/lib/cssParser.ts +465 -0
- package/templates/devtools/lib/searchIndexes.ts +45 -0
- package/templates/devtools/lib/selectorGenerator.ts +86 -0
- package/templates/devtools/store/index.ts +66 -0
- package/templates/devtools/store/slices/assetsSlice.ts +106 -0
- package/templates/devtools/store/slices/componentsSlice.ts +59 -0
- package/templates/devtools/store/slices/mockStatesSlice.ts +77 -0
- package/templates/devtools/store/slices/panelSlice.ts +17 -0
- package/templates/devtools/store/slices/typographySlice.ts +538 -0
- package/templates/devtools/store/slices/variablesSlice.ts +167 -0
- package/templates/devtools/tabs/AssetsTab/AssetGrid.tsx +76 -0
- package/templates/devtools/tabs/AssetsTab/FolderTree.tsx +53 -0
- package/templates/devtools/tabs/AssetsTab/UploadDropzone.tsx +76 -0
- package/templates/devtools/tabs/AssetsTab/index.tsx +182 -0
- package/templates/devtools/tabs/ComponentsTab/AddTabButton.tsx +63 -0
- package/templates/devtools/tabs/ComponentsTab/ComponentList.tsx +153 -0
- package/templates/devtools/tabs/ComponentsTab/DesignSystemTab.tsx +1515 -0
- package/templates/devtools/tabs/ComponentsTab/DynamicFolderTab.tsx +113 -0
- package/templates/devtools/tabs/ComponentsTab/PropDisplay.tsx +55 -0
- package/templates/devtools/tabs/ComponentsTab/index.tsx +167 -0
- package/templates/devtools/tabs/ComponentsTab/previews/.gitkeep +4 -0
- package/templates/devtools/tabs/ComponentsTab/previews/Rad_os.tsx +262 -0
- package/templates/devtools/tabs/ComponentsTab/tabConfig.ts +53 -0
- package/templates/devtools/tabs/MockStatesTab/index.tsx +29 -0
- package/templates/devtools/tabs/TypographyTab/FontManager.tsx +421 -0
- package/templates/devtools/tabs/TypographyTab/TypographyStylesDisplay.tsx +290 -0
- package/templates/devtools/tabs/TypographyTab/index.tsx +98 -0
- package/templates/devtools/tabs/VariablesTab/BaseColorEditor.tsx +267 -0
- package/templates/devtools/tabs/VariablesTab/BorderRadiusEditor.tsx +37 -0
- package/templates/devtools/tabs/VariablesTab/ColorModeSelector.tsx +235 -0
- package/templates/devtools/tabs/VariablesTab/index.tsx +100 -0
- package/templates/devtools/types/index.ts +99 -0
- package/templates/globals.css +574 -0
- package/templates/hooks/index.ts +1 -0
- package/templates/hooks/useWindowManager.ts +212 -0
- package/templates/public/assets/icons/avatar.svg +18 -0
- package/templates/public/assets/icons/checkmark-filled.svg +14 -0
- package/templates/public/assets/icons/checkmark.svg +14 -0
- package/templates/public/assets/icons/chevron-down.svg +14 -0
- package/templates/public/assets/icons/close.svg +14 -0
- package/templates/public/assets/icons/copy.svg +14 -0
- package/templates/public/assets/icons/download.svg +14 -0
- package/templates/public/assets/icons/expand.svg +31 -0
- package/templates/public/assets/icons/file-blank.svg +17 -0
- package/templates/public/assets/icons/file-image.svg +19 -0
- package/templates/public/assets/icons/file-written.svg +17 -0
- package/templates/public/assets/icons/folder-closed.svg +17 -0
- package/templates/public/assets/icons/folder-open.svg +17 -0
- package/templates/public/assets/icons/hamburger.svg +18 -0
- package/templates/public/assets/icons/home-outline.svg +28 -0
- package/templates/public/assets/icons/home.svg +30 -0
- package/templates/public/assets/icons/hourglass.svg +25 -0
- package/templates/public/assets/icons/information-circle.svg +14 -0
- package/templates/public/assets/icons/information.svg +17 -0
- package/templates/public/assets/icons/lightning.svg +14 -0
- package/templates/public/assets/icons/locked.svg +17 -0
- package/templates/public/assets/icons/not-allowed.svg +14 -0
- package/templates/public/assets/icons/plus.svg +5 -0
- package/templates/public/assets/icons/power-thin.svg +17 -0
- package/templates/public/assets/icons/power.svg +17 -0
- package/templates/public/assets/icons/question-block.svg +14 -0
- package/templates/public/assets/icons/question.svg +17 -0
- package/templates/public/assets/icons/refresh-block.svg +14 -0
- package/templates/public/assets/icons/refresh.svg +17 -0
- package/templates/public/assets/icons/save.svg +14 -0
- package/templates/public/assets/icons/search.svg +25 -0
- package/templates/public/assets/icons/settings.svg +14 -0
- package/templates/public/assets/icons/trash-full.svg +21 -0
- package/templates/public/assets/icons/trash-open.svg +23 -0
- package/templates/public/assets/icons/trash.svg +18 -0
- package/templates/public/assets/icons/unlocked.svg +17 -0
- package/templates/public/assets/icons/waring-triangle-filled.svg +17 -0
- package/templates/public/assets/icons/warning-triangle-filled-2.svg +30 -0
- package/templates/public/assets/icons/warning-triangle-lines.svg +29 -0
- package/templates/public/assets/icons/wrench.svg +17 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { useDevToolsStore } from '../../store';
|
|
5
|
+
import { Icon } from '@/components/icons';
|
|
6
|
+
import { useToast } from '@/components/ui/Toast';
|
|
7
|
+
import { detectFontPropertiesFromFilename } from '../../lib/cssParser';
|
|
8
|
+
import type { FontDefinition, FontFile } from '../../types';
|
|
9
|
+
|
|
10
|
+
interface UploadingFont {
|
|
11
|
+
file: File;
|
|
12
|
+
name: string;
|
|
13
|
+
family: string;
|
|
14
|
+
weight: number;
|
|
15
|
+
style: string;
|
|
16
|
+
format: FontFile['format'];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function FontManager() {
|
|
20
|
+
const { fonts, addFont, updateFont, deleteFont, addFontFile, removeFontFile, loadTypographyFromCSS, loadFontsFromFilesystem } = useDevToolsStore();
|
|
21
|
+
const [uploadingFont, setUploadingFont] = useState<UploadingFont | null>(null);
|
|
22
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
23
|
+
const [expandedFont, setExpandedFont] = useState<string | null>(null);
|
|
24
|
+
const [isReloading, setIsReloading] = useState(false);
|
|
25
|
+
const { addToast } = useToast();
|
|
26
|
+
|
|
27
|
+
// Reload fonts from filesystem on mount (more accurate than CSS)
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
loadFontsFromFilesystem();
|
|
30
|
+
}, [loadFontsFromFilesystem]);
|
|
31
|
+
|
|
32
|
+
// Handle reload fonts - only use filesystem (more accurate than CSS)
|
|
33
|
+
const handleReload = useCallback(async () => {
|
|
34
|
+
setIsReloading(true);
|
|
35
|
+
try {
|
|
36
|
+
// Load from filesystem only - CSS may not have all fonts
|
|
37
|
+
await loadFontsFromFilesystem();
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// Failed to reload fonts
|
|
40
|
+
} finally {
|
|
41
|
+
setIsReloading(false);
|
|
42
|
+
}
|
|
43
|
+
}, [loadFontsFromFilesystem]);
|
|
44
|
+
|
|
45
|
+
// Handle file drop/selection
|
|
46
|
+
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
47
|
+
const file = e.target.files?.[0];
|
|
48
|
+
if (!file) return;
|
|
49
|
+
|
|
50
|
+
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
51
|
+
if (!['woff2', 'woff', 'ttf', 'otf'].includes(ext || '')) {
|
|
52
|
+
addToast({
|
|
53
|
+
title: 'Unsupported format',
|
|
54
|
+
description: 'Please use woff2, woff, ttf, or otf',
|
|
55
|
+
variant: 'warning',
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Auto-detect properties from filename
|
|
61
|
+
const { weight, style } = detectFontPropertiesFromFilename(file.name);
|
|
62
|
+
|
|
63
|
+
// Extract font name from filename (e.g., "Mondwest-Bold.woff2" -> "Mondwest")
|
|
64
|
+
const nameParts = file.name.replace(/\.[^.]+$/, '').split('-');
|
|
65
|
+
const name = nameParts[0] || file.name.replace(/\.[^.]+$/, '');
|
|
66
|
+
|
|
67
|
+
setUploadingFont({
|
|
68
|
+
file,
|
|
69
|
+
name,
|
|
70
|
+
family: name,
|
|
71
|
+
weight,
|
|
72
|
+
style,
|
|
73
|
+
format: ext as FontFile['format'],
|
|
74
|
+
});
|
|
75
|
+
}, [addToast]);
|
|
76
|
+
|
|
77
|
+
// Handle drop
|
|
78
|
+
const handleDrop = useCallback((e: React.DragEvent) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
const file = e.dataTransfer.files[0];
|
|
81
|
+
if (!file) return;
|
|
82
|
+
|
|
83
|
+
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
84
|
+
if (!['woff2', 'woff', 'ttf', 'otf'].includes(ext || '')) {
|
|
85
|
+
addToast({
|
|
86
|
+
title: 'Unsupported format',
|
|
87
|
+
description: 'Please use woff2, woff, ttf, or otf',
|
|
88
|
+
variant: 'warning',
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { weight, style } = detectFontPropertiesFromFilename(file.name);
|
|
94
|
+
const nameParts = file.name.replace(/\.[^.]+$/, '').split('-');
|
|
95
|
+
const name = nameParts[0] || file.name.replace(/\.[^.]+$/, '');
|
|
96
|
+
|
|
97
|
+
setUploadingFont({
|
|
98
|
+
file,
|
|
99
|
+
name,
|
|
100
|
+
family: name,
|
|
101
|
+
weight,
|
|
102
|
+
style,
|
|
103
|
+
format: ext as FontFile['format'],
|
|
104
|
+
});
|
|
105
|
+
}, [addToast]);
|
|
106
|
+
|
|
107
|
+
// Upload font to server
|
|
108
|
+
const handleUpload = async () => {
|
|
109
|
+
if (!uploadingFont) return;
|
|
110
|
+
|
|
111
|
+
setIsUploading(true);
|
|
112
|
+
try {
|
|
113
|
+
const formData = new FormData();
|
|
114
|
+
formData.append('file', uploadingFont.file);
|
|
115
|
+
formData.append('name', uploadingFont.name);
|
|
116
|
+
formData.append('family', uploadingFont.family);
|
|
117
|
+
formData.append('weight', uploadingFont.weight.toString());
|
|
118
|
+
formData.append('style', uploadingFont.style);
|
|
119
|
+
formData.append('format', uploadingFont.format);
|
|
120
|
+
|
|
121
|
+
const response = await fetch('/api/devtools/fonts/upload', {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: formData,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error('Failed to upload font');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = await response.json();
|
|
131
|
+
|
|
132
|
+
// Check if we're adding to existing font family or creating new one
|
|
133
|
+
const existingFont = fonts.find(f => f.family === uploadingFont.family);
|
|
134
|
+
|
|
135
|
+
if (existingFont) {
|
|
136
|
+
// Add file to existing font
|
|
137
|
+
addFontFile(existingFont.id, {
|
|
138
|
+
weight: uploadingFont.weight,
|
|
139
|
+
style: uploadingFont.style,
|
|
140
|
+
format: uploadingFont.format,
|
|
141
|
+
path: result.path,
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
// Create new font
|
|
145
|
+
addFont({
|
|
146
|
+
name: uploadingFont.name,
|
|
147
|
+
family: uploadingFont.family,
|
|
148
|
+
files: [{
|
|
149
|
+
id: crypto.randomUUID(),
|
|
150
|
+
weight: uploadingFont.weight,
|
|
151
|
+
style: uploadingFont.style,
|
|
152
|
+
format: uploadingFont.format,
|
|
153
|
+
path: result.path,
|
|
154
|
+
}],
|
|
155
|
+
weights: [uploadingFont.weight],
|
|
156
|
+
styles: [uploadingFont.style],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setUploadingFont(null);
|
|
161
|
+
|
|
162
|
+
// Reload fonts from filesystem to reflect the new upload
|
|
163
|
+
await loadFontsFromFilesystem();
|
|
164
|
+
} catch (error) {
|
|
165
|
+
addToast({
|
|
166
|
+
title: 'Upload failed',
|
|
167
|
+
description: 'Failed to upload font. Please try again.',
|
|
168
|
+
variant: 'error',
|
|
169
|
+
});
|
|
170
|
+
} finally {
|
|
171
|
+
setIsUploading(false);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Delete font
|
|
176
|
+
const handleDeleteFont = (fontId: string) => {
|
|
177
|
+
if (confirm('Are you sure you want to delete this font?')) {
|
|
178
|
+
deleteFont(fontId);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Delete font file
|
|
183
|
+
const handleDeleteFontFile = (fontId: string, fileId: string) => {
|
|
184
|
+
removeFontFile(fontId, fileId);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Get weight label
|
|
188
|
+
const getWeightLabel = (weight: number) => {
|
|
189
|
+
const labels: Record<number, string> = {
|
|
190
|
+
100: 'Thin',
|
|
191
|
+
200: 'Extra Light',
|
|
192
|
+
300: 'Light',
|
|
193
|
+
400: 'Regular',
|
|
194
|
+
500: 'Medium',
|
|
195
|
+
600: 'Semibold',
|
|
196
|
+
700: 'Bold',
|
|
197
|
+
800: 'Extra Bold',
|
|
198
|
+
900: 'Black',
|
|
199
|
+
};
|
|
200
|
+
return labels[weight] || weight.toString();
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div className="space-y-4">
|
|
205
|
+
<div className="flex items-center justify-between">
|
|
206
|
+
<h3 className="font-joystix text-sm text-black uppercase">Fonts</h3>
|
|
207
|
+
<button
|
|
208
|
+
onClick={handleReload}
|
|
209
|
+
disabled={isReloading}
|
|
210
|
+
className="flex items-center gap-1 px-2 py-1 text-xs font-mondwest text-black/60 hover:text-black border border-black/20 rounded-sm hover:bg-black/5 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
211
|
+
title="Reload fonts from CSS"
|
|
212
|
+
>
|
|
213
|
+
<Icon name="refresh" size={12} />
|
|
214
|
+
{isReloading ? 'Reloading...' : 'Reload'}
|
|
215
|
+
</button>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{/* Font List */}
|
|
219
|
+
<div className="space-y-2">
|
|
220
|
+
{fonts.map((font) => (
|
|
221
|
+
<div
|
|
222
|
+
key={font.id}
|
|
223
|
+
className="border border-black rounded-sm bg-warm-cloud overflow-hidden"
|
|
224
|
+
>
|
|
225
|
+
{/* Font Header */}
|
|
226
|
+
<div
|
|
227
|
+
className="flex items-center justify-between px-3 py-2 cursor-pointer hover:bg-black/5"
|
|
228
|
+
onClick={() => setExpandedFont(expandedFont === font.id ? null : font.id)}
|
|
229
|
+
>
|
|
230
|
+
<div className="flex items-center gap-3">
|
|
231
|
+
<span
|
|
232
|
+
className="text-lg"
|
|
233
|
+
style={{ fontFamily: font.family }}
|
|
234
|
+
>
|
|
235
|
+
Aa
|
|
236
|
+
</span>
|
|
237
|
+
<div>
|
|
238
|
+
<div className="font-mondwest text-base text-black">
|
|
239
|
+
{font.name}
|
|
240
|
+
</div>
|
|
241
|
+
<div className="font-mondwest text-sm text-black/60">
|
|
242
|
+
{font.weights.length} weight{font.weights.length !== 1 ? 's' : ''} • {font.styles.join(', ')}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
<div className="flex items-center gap-2">
|
|
247
|
+
<button
|
|
248
|
+
onClick={(e) => {
|
|
249
|
+
e.stopPropagation();
|
|
250
|
+
handleDeleteFont(font.id);
|
|
251
|
+
}}
|
|
252
|
+
className="text-black/50 hover:text-error-red flex items-center"
|
|
253
|
+
title="Delete font"
|
|
254
|
+
>
|
|
255
|
+
<Icon name="close" size={16} />
|
|
256
|
+
</button>
|
|
257
|
+
<span className="text-black/40">{expandedFont === font.id ? '▼' : '▶'}</span>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{/* Font Files (expanded) */}
|
|
262
|
+
{expandedFont === font.id && (
|
|
263
|
+
<div className="border-t border-black/20 px-3 py-2 space-y-1 bg-black/5">
|
|
264
|
+
{font.files.map((file) => (
|
|
265
|
+
<div key={file.id} className="flex items-center justify-between py-1">
|
|
266
|
+
<div className="font-mondwest text-sm text-black/70">
|
|
267
|
+
{getWeightLabel(file.weight)} {file.style !== 'normal' ? `(${file.style})` : ''} • {file.format}
|
|
268
|
+
</div>
|
|
269
|
+
<button
|
|
270
|
+
onClick={() => handleDeleteFontFile(font.id, file.id)}
|
|
271
|
+
className="text-black/40 hover:text-error-red flex items-center"
|
|
272
|
+
title="Remove file"
|
|
273
|
+
>
|
|
274
|
+
<Icon name="close" size={14} />
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
))}
|
|
278
|
+
<div className="text-xs text-black/40 font-mono pt-1 border-t border-black/10">
|
|
279
|
+
{font.files[0]?.path}
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
))}
|
|
285
|
+
|
|
286
|
+
{fonts.length === 0 && (
|
|
287
|
+
<div className="text-center py-4 text-black/50 font-mondwest text-base">
|
|
288
|
+
No fonts installed
|
|
289
|
+
</div>
|
|
290
|
+
)}
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
{/* Upload Dropzone */}
|
|
294
|
+
<div
|
|
295
|
+
onDrop={handleDrop}
|
|
296
|
+
onDragOver={(e) => e.preventDefault()}
|
|
297
|
+
className="border-2 border-dashed border-black/30 rounded-md p-4 text-center hover:border-black/50 transition-colors"
|
|
298
|
+
>
|
|
299
|
+
<input
|
|
300
|
+
type="file"
|
|
301
|
+
id="font-upload"
|
|
302
|
+
accept=".woff2,.woff,.ttf,.otf"
|
|
303
|
+
onChange={handleFileSelect}
|
|
304
|
+
className="hidden"
|
|
305
|
+
/>
|
|
306
|
+
<label
|
|
307
|
+
htmlFor="font-upload"
|
|
308
|
+
className="cursor-pointer block"
|
|
309
|
+
>
|
|
310
|
+
<div className="font-mondwest text-base text-black/60">
|
|
311
|
+
Drop font file here or click to upload
|
|
312
|
+
</div>
|
|
313
|
+
<div className="font-mondwest text-xs text-black/40 mt-1">
|
|
314
|
+
Supports woff2, woff, ttf, otf
|
|
315
|
+
</div>
|
|
316
|
+
</label>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
{/* Upload Modal */}
|
|
320
|
+
{uploadingFont && (
|
|
321
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
322
|
+
<div className="bg-warm-cloud border-2 border-black rounded-md p-4 max-w-md w-full mx-4 shadow-[4px_4px_0_0_var(--color-black)]">
|
|
323
|
+
<h4 className="font-joystix text-sm text-black uppercase mb-4">
|
|
324
|
+
Configure Font
|
|
325
|
+
</h4>
|
|
326
|
+
|
|
327
|
+
<div className="space-y-3">
|
|
328
|
+
{/* Font Name */}
|
|
329
|
+
<div>
|
|
330
|
+
<label className="font-mondwest text-sm text-black/60 block mb-1">
|
|
331
|
+
Font Name
|
|
332
|
+
</label>
|
|
333
|
+
<input
|
|
334
|
+
type="text"
|
|
335
|
+
value={uploadingFont.name}
|
|
336
|
+
onChange={(e) => setUploadingFont({ ...uploadingFont, name: e.target.value })}
|
|
337
|
+
className="w-full px-3 py-2 border border-black rounded-sm font-mondwest text-base bg-white"
|
|
338
|
+
/>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
{/* Font Family */}
|
|
342
|
+
<div>
|
|
343
|
+
<label className="font-mondwest text-sm text-black/60 block mb-1">
|
|
344
|
+
CSS Font Family
|
|
345
|
+
</label>
|
|
346
|
+
<input
|
|
347
|
+
type="text"
|
|
348
|
+
value={uploadingFont.family}
|
|
349
|
+
onChange={(e) => setUploadingFont({ ...uploadingFont, family: e.target.value })}
|
|
350
|
+
className="w-full px-3 py-2 border border-black rounded-sm font-mondwest text-base bg-white"
|
|
351
|
+
/>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
{/* Weight */}
|
|
355
|
+
<div>
|
|
356
|
+
<label className="font-mondwest text-sm text-black/60 block mb-1">
|
|
357
|
+
Weight
|
|
358
|
+
</label>
|
|
359
|
+
<select
|
|
360
|
+
value={uploadingFont.weight}
|
|
361
|
+
onChange={(e) => setUploadingFont({ ...uploadingFont, weight: parseInt(e.target.value, 10) })}
|
|
362
|
+
className="w-full px-3 py-2 border border-black rounded-sm font-mondwest text-base bg-white"
|
|
363
|
+
>
|
|
364
|
+
<option value={100}>100 - Thin</option>
|
|
365
|
+
<option value={200}>200 - Extra Light</option>
|
|
366
|
+
<option value={300}>300 - Light</option>
|
|
367
|
+
<option value={400}>400 - Regular</option>
|
|
368
|
+
<option value={500}>500 - Medium</option>
|
|
369
|
+
<option value={600}>600 - Semibold</option>
|
|
370
|
+
<option value={700}>700 - Bold</option>
|
|
371
|
+
<option value={800}>800 - Extra Bold</option>
|
|
372
|
+
<option value={900}>900 - Black</option>
|
|
373
|
+
</select>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
{/* Style */}
|
|
377
|
+
<div>
|
|
378
|
+
<label className="font-mondwest text-sm text-black/60 block mb-1">
|
|
379
|
+
Style
|
|
380
|
+
</label>
|
|
381
|
+
<select
|
|
382
|
+
value={uploadingFont.style}
|
|
383
|
+
onChange={(e) => setUploadingFont({ ...uploadingFont, style: e.target.value })}
|
|
384
|
+
className="w-full px-3 py-2 border border-black rounded-sm font-mondwest text-base bg-white"
|
|
385
|
+
>
|
|
386
|
+
<option value="normal">Normal</option>
|
|
387
|
+
<option value="italic">Italic</option>
|
|
388
|
+
</select>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
{/* File Info */}
|
|
392
|
+
<div className="text-xs text-black/40 font-mono">
|
|
393
|
+
File: {uploadingFont.file.name}
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
{/* Actions */}
|
|
398
|
+
<div className="flex justify-end gap-2 mt-4">
|
|
399
|
+
<button
|
|
400
|
+
onClick={() => setUploadingFont(null)}
|
|
401
|
+
className="px-4 py-2 font-mondwest text-base text-black border border-black rounded-sm hover:bg-black/5"
|
|
402
|
+
>
|
|
403
|
+
Cancel
|
|
404
|
+
</button>
|
|
405
|
+
<button
|
|
406
|
+
onClick={handleUpload}
|
|
407
|
+
disabled={isUploading}
|
|
408
|
+
className="px-4 py-2 font-mondwest text-base text-black bg-sun-yellow border border-black rounded-sm hover:bg-sun-yellow/80 disabled:opacity-50"
|
|
409
|
+
>
|
|
410
|
+
{isUploading ? 'Uploading...' : 'Upload'}
|
|
411
|
+
</button>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export default FontManager;
|
|
421
|
+
|