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.
Files changed (133) hide show
  1. package/README.md +108 -0
  2. package/bin/radtools.js +5 -0
  3. package/dist/cli/index.js +427 -0
  4. package/package.json +55 -0
  5. package/templates/api-routes/assets/optimize/route.ts +94 -0
  6. package/templates/api-routes/assets/route.ts +159 -0
  7. package/templates/api-routes/components/create-folder/route.ts +55 -0
  8. package/templates/api-routes/components/route.ts +156 -0
  9. package/templates/api-routes/fonts/route.ts +96 -0
  10. package/templates/api-routes/fonts/upload/route.ts +79 -0
  11. package/templates/api-routes/read-css/route.ts +29 -0
  12. package/templates/api-routes/write-css/route.ts +423 -0
  13. package/templates/components/Rad_os/AppWindow.tsx +423 -0
  14. package/templates/components/Rad_os/MobileAppModal.tsx +76 -0
  15. package/templates/components/Rad_os/WindowTitleBar.tsx +290 -0
  16. package/templates/components/icons/Icon.tsx +224 -0
  17. package/templates/components/icons/README.md +85 -0
  18. package/templates/components/icons/index.ts +20 -0
  19. package/templates/components/icons.tsx +164 -0
  20. package/templates/components/ui/Accordion.tsx +268 -0
  21. package/templates/components/ui/Alert.tsx +111 -0
  22. package/templates/components/ui/Badge.tsx +87 -0
  23. package/templates/components/ui/Breadcrumbs.tsx +88 -0
  24. package/templates/components/ui/Button.tsx +249 -0
  25. package/templates/components/ui/Card.tsx +137 -0
  26. package/templates/components/ui/Checkbox.tsx +137 -0
  27. package/templates/components/ui/ContextMenu.tsx +220 -0
  28. package/templates/components/ui/Dialog.tsx +264 -0
  29. package/templates/components/ui/Divider.tsx +70 -0
  30. package/templates/components/ui/DropdownMenu.tsx +301 -0
  31. package/templates/components/ui/HelpPanel.tsx +119 -0
  32. package/templates/components/ui/Input.tsx +176 -0
  33. package/templates/components/ui/Popover.tsx +211 -0
  34. package/templates/components/ui/Progress.tsx +158 -0
  35. package/templates/components/ui/Select.tsx +134 -0
  36. package/templates/components/ui/Sheet.tsx +316 -0
  37. package/templates/components/ui/Slider.tsx +223 -0
  38. package/templates/components/ui/Switch.tsx +155 -0
  39. package/templates/components/ui/Tabs.tsx +253 -0
  40. package/templates/components/ui/Toast.tsx +192 -0
  41. package/templates/components/ui/Tooltip.tsx +129 -0
  42. package/templates/components/ui/hooks/useModalBehavior.ts +66 -0
  43. package/templates/components/ui/index.ts +84 -0
  44. package/templates/devtools/DevToolsPanel.tsx +261 -0
  45. package/templates/devtools/DevToolsProvider.tsx +43 -0
  46. package/templates/devtools/components/BreakpointIndicator.tsx +49 -0
  47. package/templates/devtools/components/ColorPicker.tsx +33 -0
  48. package/templates/devtools/components/ComponentsSecondaryNav.tsx +44 -0
  49. package/templates/devtools/components/ContextualFooter.tsx +56 -0
  50. package/templates/devtools/components/DraggablePanel.tsx +43 -0
  51. package/templates/devtools/components/PrimaryNavigationFooter.tsx +254 -0
  52. package/templates/devtools/components/SearchableColorDropdown.tsx +253 -0
  53. package/templates/devtools/components/SecondaryNavigation.tsx +36 -0
  54. package/templates/devtools/components/TokenDropdown.tsx +47 -0
  55. package/templates/devtools/components/TypographyFooter.tsx +145 -0
  56. package/templates/devtools/hooks/useMockState.ts +16 -0
  57. package/templates/devtools/index.ts +17 -0
  58. package/templates/devtools/lib/componentScanner.ts +78 -0
  59. package/templates/devtools/lib/cssParser.ts +465 -0
  60. package/templates/devtools/lib/searchIndexes.ts +45 -0
  61. package/templates/devtools/lib/selectorGenerator.ts +86 -0
  62. package/templates/devtools/store/index.ts +66 -0
  63. package/templates/devtools/store/slices/assetsSlice.ts +106 -0
  64. package/templates/devtools/store/slices/componentsSlice.ts +59 -0
  65. package/templates/devtools/store/slices/mockStatesSlice.ts +77 -0
  66. package/templates/devtools/store/slices/panelSlice.ts +17 -0
  67. package/templates/devtools/store/slices/typographySlice.ts +538 -0
  68. package/templates/devtools/store/slices/variablesSlice.ts +167 -0
  69. package/templates/devtools/tabs/AssetsTab/AssetGrid.tsx +76 -0
  70. package/templates/devtools/tabs/AssetsTab/FolderTree.tsx +53 -0
  71. package/templates/devtools/tabs/AssetsTab/UploadDropzone.tsx +76 -0
  72. package/templates/devtools/tabs/AssetsTab/index.tsx +182 -0
  73. package/templates/devtools/tabs/ComponentsTab/AddTabButton.tsx +63 -0
  74. package/templates/devtools/tabs/ComponentsTab/ComponentList.tsx +153 -0
  75. package/templates/devtools/tabs/ComponentsTab/DesignSystemTab.tsx +1515 -0
  76. package/templates/devtools/tabs/ComponentsTab/DynamicFolderTab.tsx +113 -0
  77. package/templates/devtools/tabs/ComponentsTab/PropDisplay.tsx +55 -0
  78. package/templates/devtools/tabs/ComponentsTab/index.tsx +167 -0
  79. package/templates/devtools/tabs/ComponentsTab/previews/.gitkeep +4 -0
  80. package/templates/devtools/tabs/ComponentsTab/previews/Rad_os.tsx +262 -0
  81. package/templates/devtools/tabs/ComponentsTab/tabConfig.ts +53 -0
  82. package/templates/devtools/tabs/MockStatesTab/index.tsx +29 -0
  83. package/templates/devtools/tabs/TypographyTab/FontManager.tsx +421 -0
  84. package/templates/devtools/tabs/TypographyTab/TypographyStylesDisplay.tsx +290 -0
  85. package/templates/devtools/tabs/TypographyTab/index.tsx +98 -0
  86. package/templates/devtools/tabs/VariablesTab/BaseColorEditor.tsx +267 -0
  87. package/templates/devtools/tabs/VariablesTab/BorderRadiusEditor.tsx +37 -0
  88. package/templates/devtools/tabs/VariablesTab/ColorModeSelector.tsx +235 -0
  89. package/templates/devtools/tabs/VariablesTab/index.tsx +100 -0
  90. package/templates/devtools/types/index.ts +99 -0
  91. package/templates/globals.css +574 -0
  92. package/templates/hooks/index.ts +1 -0
  93. package/templates/hooks/useWindowManager.ts +212 -0
  94. package/templates/public/assets/icons/avatar.svg +18 -0
  95. package/templates/public/assets/icons/checkmark-filled.svg +14 -0
  96. package/templates/public/assets/icons/checkmark.svg +14 -0
  97. package/templates/public/assets/icons/chevron-down.svg +14 -0
  98. package/templates/public/assets/icons/close.svg +14 -0
  99. package/templates/public/assets/icons/copy.svg +14 -0
  100. package/templates/public/assets/icons/download.svg +14 -0
  101. package/templates/public/assets/icons/expand.svg +31 -0
  102. package/templates/public/assets/icons/file-blank.svg +17 -0
  103. package/templates/public/assets/icons/file-image.svg +19 -0
  104. package/templates/public/assets/icons/file-written.svg +17 -0
  105. package/templates/public/assets/icons/folder-closed.svg +17 -0
  106. package/templates/public/assets/icons/folder-open.svg +17 -0
  107. package/templates/public/assets/icons/hamburger.svg +18 -0
  108. package/templates/public/assets/icons/home-outline.svg +28 -0
  109. package/templates/public/assets/icons/home.svg +30 -0
  110. package/templates/public/assets/icons/hourglass.svg +25 -0
  111. package/templates/public/assets/icons/information-circle.svg +14 -0
  112. package/templates/public/assets/icons/information.svg +17 -0
  113. package/templates/public/assets/icons/lightning.svg +14 -0
  114. package/templates/public/assets/icons/locked.svg +17 -0
  115. package/templates/public/assets/icons/not-allowed.svg +14 -0
  116. package/templates/public/assets/icons/plus.svg +5 -0
  117. package/templates/public/assets/icons/power-thin.svg +17 -0
  118. package/templates/public/assets/icons/power.svg +17 -0
  119. package/templates/public/assets/icons/question-block.svg +14 -0
  120. package/templates/public/assets/icons/question.svg +17 -0
  121. package/templates/public/assets/icons/refresh-block.svg +14 -0
  122. package/templates/public/assets/icons/refresh.svg +17 -0
  123. package/templates/public/assets/icons/save.svg +14 -0
  124. package/templates/public/assets/icons/search.svg +25 -0
  125. package/templates/public/assets/icons/settings.svg +14 -0
  126. package/templates/public/assets/icons/trash-full.svg +21 -0
  127. package/templates/public/assets/icons/trash-open.svg +23 -0
  128. package/templates/public/assets/icons/trash.svg +18 -0
  129. package/templates/public/assets/icons/unlocked.svg +17 -0
  130. package/templates/public/assets/icons/waring-triangle-filled.svg +17 -0
  131. package/templates/public/assets/icons/warning-triangle-filled-2.svg +30 -0
  132. package/templates/public/assets/icons/warning-triangle-lines.svg +29 -0
  133. 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
+