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,290 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useDevToolsStore } from '../../store';
|
|
5
|
+
import type { TypographyStyle } from '../../types';
|
|
6
|
+
|
|
7
|
+
// Sample text for each element type
|
|
8
|
+
const SAMPLE_TEXT: Record<string, string> = {
|
|
9
|
+
h1: 'Heading 1',
|
|
10
|
+
h2: 'Heading 2',
|
|
11
|
+
h3: 'Heading 3',
|
|
12
|
+
h4: 'Heading 4',
|
|
13
|
+
h5: 'Heading 5',
|
|
14
|
+
h6: 'Heading 6',
|
|
15
|
+
p: 'The quick brown fox jumps over the lazy dog.',
|
|
16
|
+
a: 'Click this link',
|
|
17
|
+
ul: 'List item example',
|
|
18
|
+
ol: 'Numbered item',
|
|
19
|
+
li: 'Item content',
|
|
20
|
+
small: 'Small fine print text',
|
|
21
|
+
strong: 'Strong importance',
|
|
22
|
+
em: 'Emphasized text',
|
|
23
|
+
code: 'inline code',
|
|
24
|
+
pre: 'code block\nexample',
|
|
25
|
+
kbd: 'Ctrl+C',
|
|
26
|
+
mark: 'Highlighted text',
|
|
27
|
+
blockquote: 'A wise quote goes here.',
|
|
28
|
+
cite: 'Citation reference',
|
|
29
|
+
abbr: 'HTML',
|
|
30
|
+
dfn: 'Definition term',
|
|
31
|
+
q: 'Inline quotation',
|
|
32
|
+
sub: 'Subscript',
|
|
33
|
+
sup: 'Superscript',
|
|
34
|
+
del: 'Deleted text',
|
|
35
|
+
ins: 'Inserted text',
|
|
36
|
+
caption: 'Table caption',
|
|
37
|
+
label: 'Form label',
|
|
38
|
+
figcaption: 'Figure caption',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function TypographyStylesDisplay() {
|
|
42
|
+
const { typographyStyles, fonts } = useDevToolsStore();
|
|
43
|
+
|
|
44
|
+
// Group styles by category
|
|
45
|
+
const headings = typographyStyles.filter(s => s.element.startsWith('h'));
|
|
46
|
+
const text = typographyStyles.filter(s => ['p', 'a'].includes(s.element));
|
|
47
|
+
const lists = typographyStyles.filter(s => ['ul', 'ol', 'li'].includes(s.element));
|
|
48
|
+
const code = typographyStyles.filter(s => ['code', 'pre', 'kbd', 'samp', 'var'].includes(s.element));
|
|
49
|
+
const semantic = typographyStyles.filter(s => ['strong', 'em', 'mark', 'del', 'ins', 'b', 'i', 'u', 's'].includes(s.element));
|
|
50
|
+
const quotations = typographyStyles.filter(s => ['blockquote', 'cite', 'q'].includes(s.element));
|
|
51
|
+
const captions = typographyStyles.filter(s => ['caption', 'small', 'figcaption', 'label'].includes(s.element));
|
|
52
|
+
const other = typographyStyles.filter(s =>
|
|
53
|
+
!s.element.startsWith('h') &&
|
|
54
|
+
!['p', 'a', 'ul', 'ol', 'li', 'code', 'pre', 'kbd', 'samp', 'var', 'strong', 'em', 'mark', 'del', 'ins', 'b', 'i', 'u', 's', 'blockquote', 'cite', 'q', 'caption', 'small', 'figcaption', 'label'].includes(s.element)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Get font family for display
|
|
58
|
+
const getFontFamily = (fontFamilyId: string) => {
|
|
59
|
+
const font = fonts.find(f => f.id === fontFamilyId);
|
|
60
|
+
return font?.family || fontFamilyId;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Build classes string
|
|
64
|
+
const buildClasses = (style: TypographyStyle) => {
|
|
65
|
+
const classes = [
|
|
66
|
+
style.fontSize,
|
|
67
|
+
style.fontWeight,
|
|
68
|
+
style.lineHeight,
|
|
69
|
+
style.letterSpacing,
|
|
70
|
+
...(style.utilities || []),
|
|
71
|
+
].filter(Boolean);
|
|
72
|
+
return classes.join(' ');
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Render a single style row
|
|
76
|
+
const renderStyleRow = (style: TypographyStyle) => {
|
|
77
|
+
const fontFamily = getFontFamily(style.fontFamilyId);
|
|
78
|
+
const classes = buildClasses(style);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
key={style.id}
|
|
83
|
+
className="p-4 bg-[var(--color-cream)] rounded flex flex-col gap-4 mb-4"
|
|
84
|
+
>
|
|
85
|
+
{/* Element name and preview */}
|
|
86
|
+
<div className="flex items-start justify-between gap-4">
|
|
87
|
+
<div className="flex items-center gap-2">
|
|
88
|
+
<code className="px-2 py-0.5 bg-black/10 rounded">
|
|
89
|
+
{`<${style.element}>`}
|
|
90
|
+
</code>
|
|
91
|
+
<span className="font-mondwest text-sm text-black/60">
|
|
92
|
+
{style.displayName}
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{/* Live Preview - each element has data-edit-scope directly */}
|
|
98
|
+
<div className="py-2 px-3 bg-warm-cloud rounded-sm">
|
|
99
|
+
{style.element === 'h1' && <h1 data-edit-scope="layer-base">{SAMPLE_TEXT.h1}</h1>}
|
|
100
|
+
{style.element === 'h2' && <h2 data-edit-scope="layer-base">{SAMPLE_TEXT.h2}</h2>}
|
|
101
|
+
{style.element === 'h3' && <h3 data-edit-scope="layer-base">{SAMPLE_TEXT.h3}</h3>}
|
|
102
|
+
{style.element === 'h4' && <h4 data-edit-scope="layer-base">{SAMPLE_TEXT.h4}</h4>}
|
|
103
|
+
{style.element === 'h5' && <h5 data-edit-scope="layer-base">{SAMPLE_TEXT.h5}</h5>}
|
|
104
|
+
{style.element === 'h6' && <h6 data-edit-scope="layer-base">{SAMPLE_TEXT.h6}</h6>}
|
|
105
|
+
{style.element === 'p' && <p data-edit-scope="layer-base">{SAMPLE_TEXT.p}</p>}
|
|
106
|
+
{style.element === 'a' && <a href="#" data-edit-scope="layer-base">{SAMPLE_TEXT.a}</a>}
|
|
107
|
+
{style.element === 'ul' && (
|
|
108
|
+
<ul data-edit-scope="layer-base">
|
|
109
|
+
<li>{SAMPLE_TEXT.ul}</li>
|
|
110
|
+
</ul>
|
|
111
|
+
)}
|
|
112
|
+
{style.element === 'ol' && (
|
|
113
|
+
<ol data-edit-scope="layer-base">
|
|
114
|
+
<li>{SAMPLE_TEXT.ol}</li>
|
|
115
|
+
</ol>
|
|
116
|
+
)}
|
|
117
|
+
{style.element === 'li' && <li data-edit-scope="layer-base">{SAMPLE_TEXT.li}</li>}
|
|
118
|
+
{style.element === 'small' && <small data-edit-scope="layer-base">{SAMPLE_TEXT.small}</small>}
|
|
119
|
+
{style.element === 'strong' && <strong data-edit-scope="layer-base">{SAMPLE_TEXT.strong}</strong>}
|
|
120
|
+
{style.element === 'em' && <em data-edit-scope="layer-base">{SAMPLE_TEXT.em}</em>}
|
|
121
|
+
{style.element === 'code' && <code data-edit-scope="layer-base">{SAMPLE_TEXT.code}</code>}
|
|
122
|
+
{style.element === 'pre' && <pre data-edit-scope="layer-base">{SAMPLE_TEXT.pre}</pre>}
|
|
123
|
+
{style.element === 'kbd' && <kbd data-edit-scope="layer-base">{SAMPLE_TEXT.kbd}</kbd>}
|
|
124
|
+
{style.element === 'mark' && <mark data-edit-scope="layer-base">{SAMPLE_TEXT.mark}</mark>}
|
|
125
|
+
{style.element === 'blockquote' && <blockquote data-edit-scope="layer-base">{SAMPLE_TEXT.blockquote}</blockquote>}
|
|
126
|
+
{style.element === 'cite' && <cite data-edit-scope="layer-base">{SAMPLE_TEXT.cite}</cite>}
|
|
127
|
+
{style.element === 'abbr' && <abbr title="HyperText Markup Language" data-edit-scope="layer-base">{SAMPLE_TEXT.abbr}</abbr>}
|
|
128
|
+
{style.element === 'dfn' && <dfn data-edit-scope="layer-base">{SAMPLE_TEXT.dfn}</dfn>}
|
|
129
|
+
{style.element === 'q' && <q data-edit-scope="layer-base">{SAMPLE_TEXT.q}</q>}
|
|
130
|
+
{style.element === 'sub' && <sub data-edit-scope="layer-base">{SAMPLE_TEXT.sub}</sub>}
|
|
131
|
+
{style.element === 'sup' && <sup data-edit-scope="layer-base">{SAMPLE_TEXT.sup}</sup>}
|
|
132
|
+
{style.element === 'del' && <del data-edit-scope="layer-base">{SAMPLE_TEXT.del}</del>}
|
|
133
|
+
{style.element === 'ins' && <ins data-edit-scope="layer-base">{SAMPLE_TEXT.ins}</ins>}
|
|
134
|
+
{style.element === 'caption' && <caption data-edit-scope="layer-base">{SAMPLE_TEXT.caption}</caption>}
|
|
135
|
+
{style.element === 'label' && <label data-edit-scope="layer-base">{SAMPLE_TEXT.label}</label>}
|
|
136
|
+
{style.element === 'figcaption' && <figcaption data-edit-scope="layer-base">{SAMPLE_TEXT.figcaption}</figcaption>}
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Style Properties (read-only) */}
|
|
140
|
+
<div className="grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
|
|
141
|
+
<div className="flex items-center gap-2">
|
|
142
|
+
<span className="text-black/50">Font:</span>
|
|
143
|
+
<span className="text-black font-mono">{fontFamily}</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div className="flex items-center gap-2">
|
|
146
|
+
<span className="text-black/50">Size:</span>
|
|
147
|
+
<span className="text-black font-mono">{style.fontSize}</span>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="flex items-center gap-2">
|
|
150
|
+
<span className="text-black/50">Weight:</span>
|
|
151
|
+
<span className="text-black font-mono">{style.fontWeight}</span>
|
|
152
|
+
</div>
|
|
153
|
+
{style.lineHeight && (
|
|
154
|
+
<div className="flex items-center gap-2">
|
|
155
|
+
<span className="text-black/50">Line Height:</span>
|
|
156
|
+
<span className="text-black font-mono">{style.lineHeight}</span>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
{style.letterSpacing && (
|
|
160
|
+
<div className="flex items-center gap-2">
|
|
161
|
+
<span className="text-black/50">Tracking:</span>
|
|
162
|
+
<span className="text-black font-mono">{style.letterSpacing}</span>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
<div className="flex items-center gap-2">
|
|
166
|
+
<span className="text-black/50">Color:</span>
|
|
167
|
+
<span className="text-black font-mono">{style.baseColorId}</span>
|
|
168
|
+
</div>
|
|
169
|
+
{style.utilities && style.utilities.length > 0 && (
|
|
170
|
+
<div className="flex items-center gap-2 col-span-2">
|
|
171
|
+
<span className="text-black/50">Utilities:</span>
|
|
172
|
+
<span className="text-black font-mono">{style.utilities.join(' ')}</span>
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{/* @apply classes (what will be written to CSS) */}
|
|
178
|
+
<div className="pt-2 border-t border-black/10">
|
|
179
|
+
<code>
|
|
180
|
+
@apply {classes}
|
|
181
|
+
</code>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<div className="space-y-4">
|
|
189
|
+
{/* Headings Section */}
|
|
190
|
+
<div id="typography-headings">
|
|
191
|
+
<h3 className="mb-3">
|
|
192
|
+
Headings
|
|
193
|
+
</h3>
|
|
194
|
+
<div className="space-y-2">
|
|
195
|
+
{headings.map(renderStyleRow)}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{/* Text Section */}
|
|
200
|
+
<div id="typography-text">
|
|
201
|
+
<h3 className="mb-3">
|
|
202
|
+
Text
|
|
203
|
+
</h3>
|
|
204
|
+
<div className="space-y-2">
|
|
205
|
+
{text.map(renderStyleRow)}
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* Lists Section */}
|
|
210
|
+
<div id="typography-lists">
|
|
211
|
+
<h3 className="mb-3">
|
|
212
|
+
Lists
|
|
213
|
+
</h3>
|
|
214
|
+
<div className="space-y-2">
|
|
215
|
+
{lists.map(renderStyleRow)}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{/* Code Section */}
|
|
220
|
+
{code.length > 0 && (
|
|
221
|
+
<div id="typography-code">
|
|
222
|
+
<h3 className="mb-3">
|
|
223
|
+
Code
|
|
224
|
+
</h3>
|
|
225
|
+
<div className="space-y-2">
|
|
226
|
+
{code.map(renderStyleRow)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{/* Semantic Section */}
|
|
232
|
+
{semantic.length > 0 && (
|
|
233
|
+
<div id="typography-semantic">
|
|
234
|
+
<h3 className="mb-3">
|
|
235
|
+
Semantic
|
|
236
|
+
</h3>
|
|
237
|
+
<div className="space-y-2">
|
|
238
|
+
{semantic.map(renderStyleRow)}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{/* Quotations Section */}
|
|
244
|
+
{quotations.length > 0 && (
|
|
245
|
+
<div id="typography-quotations">
|
|
246
|
+
<h3 className="mb-3">
|
|
247
|
+
Quotations
|
|
248
|
+
</h3>
|
|
249
|
+
<div className="space-y-2">
|
|
250
|
+
{quotations.map(renderStyleRow)}
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{/* Captions Section */}
|
|
256
|
+
{captions.length > 0 && (
|
|
257
|
+
<div id="typography-captions">
|
|
258
|
+
<h3 className="mb-3">
|
|
259
|
+
Captions & Labels
|
|
260
|
+
</h3>
|
|
261
|
+
<div className="space-y-2">
|
|
262
|
+
{captions.map(renderStyleRow)}
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{/* Other Section */}
|
|
268
|
+
{other.length > 0 && (
|
|
269
|
+
<div>
|
|
270
|
+
<h3 className="mb-3">
|
|
271
|
+
Other
|
|
272
|
+
</h3>
|
|
273
|
+
<div className="space-y-2">
|
|
274
|
+
{other.map(renderStyleRow)}
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
|
|
279
|
+
{/* Note about editing */}
|
|
280
|
+
<div className="p-3 bg-sun-yellow/20 border border-tertiary rounded-sm">
|
|
281
|
+
<p>
|
|
282
|
+
Typography styles are display-only. Use the Cursor visual editor to modify these styles directly in your CSS.
|
|
283
|
+
</p>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export default TypographyStylesDisplay;
|
|
290
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { useDevToolsStore } from '../../store';
|
|
5
|
+
import { FontManager } from './FontManager';
|
|
6
|
+
import { TypographyStylesDisplay } from './TypographyStylesDisplay';
|
|
7
|
+
import { Button } from '@/components/ui/Button';
|
|
8
|
+
import { Divider } from '@/components/ui/Divider';
|
|
9
|
+
|
|
10
|
+
interface TypographyTabProps {
|
|
11
|
+
searchQuery?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function TypographyTab({ searchQuery = '' }: TypographyTabProps) {
|
|
15
|
+
const { loadTypographyFromCSS, loadFontsFromFilesystem } = useDevToolsStore();
|
|
16
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
17
|
+
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
|
18
|
+
|
|
19
|
+
// Load typography on mount
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
loadTypographyFromCSS();
|
|
22
|
+
}, [loadTypographyFromCSS]);
|
|
23
|
+
|
|
24
|
+
// Handle reload
|
|
25
|
+
const handleReload = async () => {
|
|
26
|
+
setIsLoading(true);
|
|
27
|
+
setMessage(null);
|
|
28
|
+
try {
|
|
29
|
+
// Load fonts from filesystem (more accurate)
|
|
30
|
+
await loadFontsFromFilesystem();
|
|
31
|
+
// Load typography styles from CSS
|
|
32
|
+
await loadTypographyFromCSS();
|
|
33
|
+
setMessage({ type: 'success', text: 'Typography reloaded!' });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
setMessage({ type: 'error', text: 'Failed to reload typography.' });
|
|
36
|
+
} finally {
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Clear message after 3 seconds
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (message) {
|
|
44
|
+
const timer = setTimeout(() => setMessage(null), 3000);
|
|
45
|
+
return () => clearTimeout(timer);
|
|
46
|
+
}
|
|
47
|
+
}, [message]);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex flex-col h-full overflow-auto pt-4 pb-4 pl-4 pr-2 bg-[var(--color-white)] border border-black rounded space-y-4">
|
|
51
|
+
{/* Header */}
|
|
52
|
+
<div className="flex items-center justify-between">
|
|
53
|
+
<h2>
|
|
54
|
+
Typography
|
|
55
|
+
</h2>
|
|
56
|
+
<div className="flex items-center gap-2">
|
|
57
|
+
<Button
|
|
58
|
+
variant="outline"
|
|
59
|
+
size="md"
|
|
60
|
+
iconName="refresh"
|
|
61
|
+
onClick={handleReload}
|
|
62
|
+
disabled={isLoading}
|
|
63
|
+
>
|
|
64
|
+
{isLoading ? 'Reloading...' : 'Reload'}
|
|
65
|
+
</Button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{/* Status Message */}
|
|
70
|
+
{message && (
|
|
71
|
+
<div
|
|
72
|
+
className={`px-4 py-2 text-sm font-mondwest ${
|
|
73
|
+
message.type === 'success'
|
|
74
|
+
? 'bg-success-green/30 text-black'
|
|
75
|
+
: 'bg-error-red/30 text-black'
|
|
76
|
+
}`}
|
|
77
|
+
>
|
|
78
|
+
{message.text}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
{/* Content */}
|
|
83
|
+
<div className="space-y-4">
|
|
84
|
+
{/* Font Manager Section */}
|
|
85
|
+
<FontManager />
|
|
86
|
+
|
|
87
|
+
{/* Divider */}
|
|
88
|
+
<Divider />
|
|
89
|
+
|
|
90
|
+
{/* Typography Styles Section */}
|
|
91
|
+
<TypographyStylesDisplay />
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default TypographyTab;
|
|
98
|
+
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useDevToolsStore } from '../../store';
|
|
5
|
+
import { Icon } from '@/components/icons';
|
|
6
|
+
import type { BaseColor } from '../../types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* BaseColorEditor - Edit brand and neutral base colors
|
|
10
|
+
* Two separate sections: Brand Colors and Neutral Colors
|
|
11
|
+
* Each row has: color swatch, editable name, editable hex, delete button
|
|
12
|
+
* Each section has: "+ Add Variable" button
|
|
13
|
+
*/
|
|
14
|
+
export function BaseColorEditor() {
|
|
15
|
+
const { baseColors, addBaseColor, updateBaseColor, deleteBaseColor } = useDevToolsStore();
|
|
16
|
+
|
|
17
|
+
const brandColors = baseColors.filter((c) => c.category === 'brand');
|
|
18
|
+
const neutralColors = baseColors.filter((c) => c.category === 'neutral');
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="space-y-6">
|
|
22
|
+
{/* Brand Colors Section */}
|
|
23
|
+
<ColorSection
|
|
24
|
+
title="Brand Colors"
|
|
25
|
+
colors={brandColors}
|
|
26
|
+
category="brand"
|
|
27
|
+
onAdd={addBaseColor}
|
|
28
|
+
onUpdate={updateBaseColor}
|
|
29
|
+
onDelete={deleteBaseColor}
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
{/* Neutral Colors Section */}
|
|
33
|
+
<ColorSection
|
|
34
|
+
title="Neutrals"
|
|
35
|
+
colors={neutralColors}
|
|
36
|
+
category="neutral"
|
|
37
|
+
onAdd={addBaseColor}
|
|
38
|
+
onUpdate={updateBaseColor}
|
|
39
|
+
onDelete={deleteBaseColor}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ColorSectionProps {
|
|
46
|
+
title: string;
|
|
47
|
+
colors: BaseColor[];
|
|
48
|
+
category: 'brand' | 'neutral';
|
|
49
|
+
onAdd: (color: Omit<BaseColor, 'id'>) => void;
|
|
50
|
+
onUpdate: (id: string, updates: Partial<BaseColor>) => void;
|
|
51
|
+
onDelete: (id: string) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ColorSection({ title, colors, category, onAdd, onUpdate, onDelete }: ColorSectionProps) {
|
|
55
|
+
const [isAdding, setIsAdding] = useState(false);
|
|
56
|
+
const [newName, setNewName] = useState('');
|
|
57
|
+
const [newHex, setNewHex] = useState('#000000');
|
|
58
|
+
|
|
59
|
+
const handleAdd = () => {
|
|
60
|
+
if (!newName.trim()) return;
|
|
61
|
+
|
|
62
|
+
const name = newName.trim().toLowerCase().replace(/\s+/g, '-');
|
|
63
|
+
const displayName = newName.trim()
|
|
64
|
+
.split(/[-_\s]+/)
|
|
65
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
66
|
+
.join(' ');
|
|
67
|
+
|
|
68
|
+
onAdd({
|
|
69
|
+
name,
|
|
70
|
+
displayName,
|
|
71
|
+
value: newHex.toUpperCase(),
|
|
72
|
+
category,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
setNewName('');
|
|
76
|
+
setNewHex('#000000');
|
|
77
|
+
setIsAdding(false);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
81
|
+
if (e.key === 'Enter') {
|
|
82
|
+
handleAdd();
|
|
83
|
+
} else if (e.key === 'Escape') {
|
|
84
|
+
setIsAdding(false);
|
|
85
|
+
setNewName('');
|
|
86
|
+
setNewHex('#000000');
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="space-y-2">
|
|
92
|
+
{/* Section Header */}
|
|
93
|
+
<h4>
|
|
94
|
+
{title}
|
|
95
|
+
</h4>
|
|
96
|
+
|
|
97
|
+
{/* Color List */}
|
|
98
|
+
<div className="space-y-1">
|
|
99
|
+
{colors.map((color) => (
|
|
100
|
+
<ColorRow
|
|
101
|
+
key={color.id}
|
|
102
|
+
color={color}
|
|
103
|
+
onUpdate={onUpdate}
|
|
104
|
+
onDelete={onDelete}
|
|
105
|
+
/>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Add New Row */}
|
|
110
|
+
{isAdding ? (
|
|
111
|
+
<div className="flex items-center gap-2 p-2 bg-sun-yellow/30 rounded-sm border border-dashed border-black">
|
|
112
|
+
{/* Color Picker */}
|
|
113
|
+
<input
|
|
114
|
+
type="color"
|
|
115
|
+
value={newHex}
|
|
116
|
+
onChange={(e) => setNewHex(e.target.value)}
|
|
117
|
+
className="w-6 h-6 rounded-xs border border-black cursor-pointer flex-shrink-0"
|
|
118
|
+
/>
|
|
119
|
+
{/* Name Input */}
|
|
120
|
+
<input
|
|
121
|
+
type="text"
|
|
122
|
+
value={newName}
|
|
123
|
+
onChange={(e) => setNewName(e.target.value)}
|
|
124
|
+
onKeyDown={handleKeyDown}
|
|
125
|
+
placeholder="Color name"
|
|
126
|
+
autoFocus
|
|
127
|
+
className="flex-1 min-w-0 px-2 py-1 font-mondwest text-base bg-warm-cloud border border-black rounded-sm text-black focus:outline-none focus:ring-1 focus:ring-tertiary"
|
|
128
|
+
/>
|
|
129
|
+
{/* Hex Input */}
|
|
130
|
+
<input
|
|
131
|
+
type="text"
|
|
132
|
+
value={newHex}
|
|
133
|
+
onChange={(e) => setNewHex(e.target.value)}
|
|
134
|
+
onKeyDown={handleKeyDown}
|
|
135
|
+
placeholder="#000000"
|
|
136
|
+
className="w-24 px-2 py-1 font-mondwest text-sm font-mono bg-warm-cloud border border-black rounded-sm text-black uppercase focus:outline-none focus:ring-1 focus:ring-tertiary"
|
|
137
|
+
/>
|
|
138
|
+
{/* Actions */}
|
|
139
|
+
<button
|
|
140
|
+
onClick={handleAdd}
|
|
141
|
+
className="font-mondwest text-base text-black hover:text-sun-yellow px-1 flex items-center"
|
|
142
|
+
title="Add"
|
|
143
|
+
>
|
|
144
|
+
<Icon name="checkmark-filled" size={16} />
|
|
145
|
+
</button>
|
|
146
|
+
<button
|
|
147
|
+
onClick={() => {
|
|
148
|
+
setIsAdding(false);
|
|
149
|
+
setNewName('');
|
|
150
|
+
setNewHex('#000000');
|
|
151
|
+
}}
|
|
152
|
+
className="font-mondwest text-base text-black/50 hover:text-black px-1 flex items-center"
|
|
153
|
+
title="Cancel"
|
|
154
|
+
>
|
|
155
|
+
<Icon name="close" size={16} />
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
) : (
|
|
159
|
+
<button
|
|
160
|
+
onClick={() => setIsAdding(true)}
|
|
161
|
+
className="
|
|
162
|
+
w-full py-2 px-3
|
|
163
|
+
font-joystix text-xs uppercase
|
|
164
|
+
text-black/50 hover:text-black
|
|
165
|
+
border border-dashed border-black/30 hover:border-black
|
|
166
|
+
rounded-sm
|
|
167
|
+
transition-colors
|
|
168
|
+
"
|
|
169
|
+
>
|
|
170
|
+
+ Add Variable
|
|
171
|
+
</button>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface ColorRowProps {
|
|
178
|
+
color: BaseColor;
|
|
179
|
+
onUpdate: (id: string, updates: Partial<BaseColor>) => void;
|
|
180
|
+
onDelete: (id: string) => void;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function ColorRow({ color, onUpdate, onDelete }: ColorRowProps) {
|
|
184
|
+
const [isEditingName, setIsEditingName] = useState(false);
|
|
185
|
+
const [editedName, setEditedName] = useState(color.displayName);
|
|
186
|
+
|
|
187
|
+
const handleNameSubmit = () => {
|
|
188
|
+
if (editedName.trim() && editedName !== color.displayName) {
|
|
189
|
+
const name = editedName.trim().toLowerCase().replace(/\s+/g, '-');
|
|
190
|
+
onUpdate(color.id, {
|
|
191
|
+
name,
|
|
192
|
+
displayName: editedName.trim(),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
setIsEditingName(false);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const handleNameKeyDown = (e: React.KeyboardEvent) => {
|
|
199
|
+
if (e.key === 'Enter') {
|
|
200
|
+
handleNameSubmit();
|
|
201
|
+
} else if (e.key === 'Escape') {
|
|
202
|
+
setEditedName(color.displayName);
|
|
203
|
+
setIsEditingName(false);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleHexChange = (value: string) => {
|
|
208
|
+
// Allow typing and validate on blur
|
|
209
|
+
onUpdate(color.id, { value: value.toUpperCase() });
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className="flex items-center gap-2 p-2 rounded-sm hover:bg-black/5 group">
|
|
214
|
+
{/* Color Picker */}
|
|
215
|
+
<input
|
|
216
|
+
type="color"
|
|
217
|
+
value={color.value}
|
|
218
|
+
onChange={(e) => handleHexChange(e.target.value)}
|
|
219
|
+
className="w-6 h-6 rounded-xs border border-black cursor-pointer flex-shrink-0"
|
|
220
|
+
/>
|
|
221
|
+
|
|
222
|
+
{/* Name (editable on click) */}
|
|
223
|
+
{isEditingName ? (
|
|
224
|
+
<input
|
|
225
|
+
type="text"
|
|
226
|
+
value={editedName}
|
|
227
|
+
onChange={(e) => setEditedName(e.target.value)}
|
|
228
|
+
onBlur={handleNameSubmit}
|
|
229
|
+
onKeyDown={handleNameKeyDown}
|
|
230
|
+
autoFocus
|
|
231
|
+
className="flex-1 min-w-0 px-2 py-1 font-mondwest text-base bg-warm-cloud border border-black rounded-sm text-black focus:outline-none focus:ring-1 focus:ring-tertiary"
|
|
232
|
+
/>
|
|
233
|
+
) : (
|
|
234
|
+
<span
|
|
235
|
+
onClick={() => {
|
|
236
|
+
setEditedName(color.displayName);
|
|
237
|
+
setIsEditingName(true);
|
|
238
|
+
}}
|
|
239
|
+
className="flex-1 min-w-0 font-mondwest text-base text-black truncate cursor-text hover:bg-black/5 px-2 py-1 rounded-sm"
|
|
240
|
+
title="Click to edit"
|
|
241
|
+
>
|
|
242
|
+
{color.displayName}
|
|
243
|
+
</span>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
{/* Hex Input */}
|
|
247
|
+
<input
|
|
248
|
+
type="text"
|
|
249
|
+
value={color.value}
|
|
250
|
+
onChange={(e) => handleHexChange(e.target.value)}
|
|
251
|
+
className="w-24 px-2 py-1 font-mondwest text-sm font-mono bg-warm-cloud border border-black rounded-sm text-black uppercase focus:outline-none focus:ring-1 focus:ring-tertiary"
|
|
252
|
+
/>
|
|
253
|
+
|
|
254
|
+
{/* Delete Button */}
|
|
255
|
+
<button
|
|
256
|
+
onClick={() => onDelete(color.id)}
|
|
257
|
+
className="font-mondwest text-base text-error-red opacity-0 group-hover:opacity-100 hover:text-error-red/80 px-1 transition-opacity flex items-center"
|
|
258
|
+
title="Delete"
|
|
259
|
+
>
|
|
260
|
+
<Icon name="close" size={16} />
|
|
261
|
+
</button>
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export default BaseColorEditor;
|
|
267
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useDevToolsStore } from '../../store';
|
|
4
|
+
|
|
5
|
+
const RADIUS_KEYS = ['none', 'xs', 'sm', 'md', 'lg', 'full'] as const;
|
|
6
|
+
|
|
7
|
+
export function BorderRadiusEditor() {
|
|
8
|
+
const { borderRadius, updateBorderRadius } = useDevToolsStore();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="space-y-3">
|
|
12
|
+
<h3>Border Radius</h3>
|
|
13
|
+
|
|
14
|
+
<div className="grid grid-cols-2 gap-2">
|
|
15
|
+
{RADIUS_KEYS.map((key) => (
|
|
16
|
+
<div key={key} className="flex items-center gap-2 p-2 bg-black/10 rounded-sm">
|
|
17
|
+
<div
|
|
18
|
+
className="w-8 h-8 bg-sun-yellow border border-black"
|
|
19
|
+
style={{ borderRadius: borderRadius[key] || '0' }}
|
|
20
|
+
/>
|
|
21
|
+
<div className="flex-1">
|
|
22
|
+
<span className="font-mondwest text-base font-mono text-black block">{key}</span>
|
|
23
|
+
<input
|
|
24
|
+
type="text"
|
|
25
|
+
value={borderRadius[key] || ''}
|
|
26
|
+
onChange={(e) => updateBorderRadius(key, e.target.value)}
|
|
27
|
+
className="w-full px-1.5 py-0.5 font-mondwest text-base font-mono bg-warm-cloud border border-black rounded-sm text-black mt-0.5"
|
|
28
|
+
placeholder="0.5rem"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|