@fragments-sdk/cli 0.7.3 → 0.7.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.
Files changed (70) hide show
  1. package/LICENSE +1 -4
  2. package/README.md +2 -0
  3. package/dist/bin.js +39 -16
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-D34Q6A7S.js → chunk-AWYCDRPG.js} +8 -2
  6. package/dist/chunk-AWYCDRPG.js.map +1 -0
  7. package/dist/{chunk-R2YH7NLN.js → chunk-CR3XHBGM.js} +3 -3
  8. package/dist/{chunk-QPY4DUFB.js → chunk-EFQ7SIBX.js} +583 -108
  9. package/dist/chunk-EFQ7SIBX.js.map +1 -0
  10. package/dist/{chunk-UXLGIGSX.js → chunk-GIC3I2KZ.js} +2 -2
  11. package/dist/{chunk-R6IZZSE7.js → chunk-JZNATKQA.js} +9 -3
  12. package/dist/chunk-JZNATKQA.js.map +1 -0
  13. package/dist/{chunk-P33AKQJW.js → chunk-SFWZ4K7C.js} +8 -2
  14. package/dist/{chunk-P33AKQJW.js.map → chunk-SFWZ4K7C.js.map} +1 -1
  15. package/dist/{core-3NMNCLFW.js → core-T7BDYEGO.js} +3 -3
  16. package/dist/{discovery-AKGA6CJD.js → discovery-Z4RDDFVR.js} +2 -2
  17. package/dist/{generate-JAUEHKK7.js → generate-C2DKFCFJ.js} +5 -5
  18. package/dist/index.d.ts +28 -2
  19. package/dist/index.js +9 -7
  20. package/dist/index.js.map +1 -1
  21. package/dist/{init-DZQOT54X.js → init-O3FCHEPN.js} +26 -8
  22. package/dist/init-O3FCHEPN.js.map +1 -0
  23. package/dist/mcp-bin.js +3 -3
  24. package/dist/{scan-OJRCVKK2.js → scan-IYTZDUKG.js} +6 -6
  25. package/dist/{service-CFFBHW4X.js → service-VA6XKADO.js} +3 -3
  26. package/dist/{static-viewer-VA2JXSCX.js → static-viewer-5N42MBDR.js} +3 -3
  27. package/dist/{test-O7DZNKDC.js → test-OMMDWL2W.js} +4 -4
  28. package/dist/{tokens-N7THFD6J.js → tokens-6VJAHFIG.js} +5 -5
  29. package/dist/{viewer-QTR7QJMM.js → viewer-IVP5XC7U.js} +37 -17
  30. package/dist/viewer-IVP5XC7U.js.map +1 -0
  31. package/package.json +8 -2
  32. package/src/bin.ts +4 -0
  33. package/src/commands/add.ts +6 -0
  34. package/src/commands/init.ts +24 -4
  35. package/src/commands/validate.ts +24 -2
  36. package/src/core/config.ts +6 -0
  37. package/src/core/discovery.ts +7 -1
  38. package/src/core/index.ts +1 -0
  39. package/src/core/schema.ts +6 -0
  40. package/src/core/types.ts +21 -0
  41. package/src/index.ts +2 -1
  42. package/src/migrate/detect.ts +4 -0
  43. package/src/service/snippet-validation.test.ts +209 -0
  44. package/src/service/snippet-validation.ts +635 -0
  45. package/src/validators.ts +53 -5
  46. package/src/viewer/__tests__/viewer-integration.test.ts +8 -0
  47. package/src/viewer/components/App.tsx +63 -2
  48. package/src/viewer/components/CodePanel.naming.test.tsx +60 -0
  49. package/src/viewer/components/CodePanel.tsx +76 -468
  50. package/src/viewer/components/Layout.tsx +2 -2
  51. package/src/viewer/components/LeftSidebar.tsx +35 -77
  52. package/src/viewer/preview-frame.html +1 -1
  53. package/src/viewer/styles/globals.css +2 -1
  54. package/src/viewer/utils/a11y-fixes.ts +24 -9
  55. package/src/viewer/vite-plugin.ts +27 -2
  56. package/dist/chunk-D34Q6A7S.js.map +0 -1
  57. package/dist/chunk-QPY4DUFB.js.map +0 -1
  58. package/dist/chunk-R6IZZSE7.js.map +0 -1
  59. package/dist/init-DZQOT54X.js.map +0 -1
  60. package/dist/viewer-QTR7QJMM.js.map +0 -1
  61. /package/dist/{chunk-R2YH7NLN.js.map → chunk-CR3XHBGM.js.map} +0 -0
  62. /package/dist/{chunk-UXLGIGSX.js.map → chunk-GIC3I2KZ.js.map} +0 -0
  63. /package/dist/{core-3NMNCLFW.js.map → core-T7BDYEGO.js.map} +0 -0
  64. /package/dist/{discovery-AKGA6CJD.js.map → discovery-Z4RDDFVR.js.map} +0 -0
  65. /package/dist/{generate-JAUEHKK7.js.map → generate-C2DKFCFJ.js.map} +0 -0
  66. /package/dist/{scan-OJRCVKK2.js.map → scan-IYTZDUKG.js.map} +0 -0
  67. /package/dist/{service-CFFBHW4X.js.map → service-VA6XKADO.js.map} +0 -0
  68. /package/dist/{static-viewer-VA2JXSCX.js.map → static-viewer-5N42MBDR.js.map} +0 -0
  69. /package/dist/{test-O7DZNKDC.js.map → test-OMMDWL2W.js.map} +0 -0
  70. /package/dist/{tokens-N7THFD6J.js.map → tokens-6VJAHFIG.js.map} +0 -0
@@ -1,6 +1,6 @@
1
- import { useMemo, isValidElement, type ReactNode } from 'react';
2
- import type { FragmentVariant, PropDefinition } from '../../core/index.js';
1
+ import { useMemo } from 'react';
3
2
  import { CodeBlock } from '@fragments/ui';
3
+ import type { FragmentVariant, PropDefinition } from '../../core/index.js';
4
4
 
5
5
  interface CodePanelProps {
6
6
  variant: FragmentVariant;
@@ -9,238 +9,107 @@ interface CodePanelProps {
9
9
  propDefs?: Record<string, PropDefinition>;
10
10
  }
11
11
 
12
- // Extract props from rendered element by calling render() and introspecting
13
- function extractPropsFromRender(variant: FragmentVariant, componentName: string): Record<string, unknown> | null {
14
- try {
15
- const rendered = variant.render();
16
- if (!isValidElement(rendered)) return null;
17
-
18
- // Check if this is the expected component (by displayName or name)
19
- const elementType = rendered.type;
20
- const typeName = typeof elementType === 'function'
21
- ? (elementType as { displayName?: string; name?: string }).displayName || (elementType as { name?: string }).name
22
- : typeof elementType === 'string' ? elementType : null;
23
-
24
- // Only extract if it's a single element of the expected component type
25
- if (typeName && typeName.toLowerCase() === componentName.toLowerCase()) {
26
- return rendered.props as Record<string, unknown>;
27
- }
28
-
29
- // If it's a wrapper element, try to find the component in children
30
- const props = rendered.props as { children?: ReactNode };
31
- if (props.children && isValidElement(props.children)) {
32
- const childType = props.children.type;
33
- const childTypeName = typeof childType === 'function'
34
- ? (childType as { displayName?: string; name?: string }).displayName || (childType as { name?: string }).name
35
- : null;
36
- if (childTypeName && childTypeName.toLowerCase() === componentName.toLowerCase()) {
37
- return props.children.props as Record<string, unknown>;
38
- }
39
- }
40
-
41
- return null;
42
- } catch {
43
- return null;
44
- }
12
+ function quoteString(value: string): string {
13
+ return `'${value
14
+ .replace(/\\/g, '\\\\')
15
+ .replace(/'/g, "\\'")
16
+ .replace(/\n/g, '\\n')
17
+ }'`;
45
18
  }
46
19
 
47
- // Check if the component likely needs state management
48
- function needsStatefulExample(componentName: string, propDefs?: Record<string, PropDefinition>): boolean {
49
- const statefulComponents = ['toggle', 'checkbox', 'input', 'select', 'switch', 'radio'];
50
- const lowerName = componentName.toLowerCase();
20
+ function serializeValue(value: unknown): string | null {
21
+ if (value === null) return '{null}';
22
+ if (value === undefined) return null;
51
23
 
52
- if (statefulComponents.some(c => lowerName.includes(c))) {
53
- return true;
24
+ if (typeof value === 'string') {
25
+ return quoteString(value);
54
26
  }
55
27
 
56
- // Check if component has onChange/onChecked props
57
- if (propDefs) {
58
- const hasChangeHandler = Object.keys(propDefs).some(
59
- key => key === 'onChange' || key === 'onChecked' || key === 'onValueChange'
60
- );
61
- const hasValueProp = Object.keys(propDefs).some(
62
- key => key === 'value' || key === 'checked'
63
- );
64
- return hasChangeHandler && hasValueProp;
28
+ if (typeof value === 'number' || typeof value === 'boolean') {
29
+ return `{${String(value)}}`;
65
30
  }
66
31
 
67
- return false;
68
- }
32
+ if (Array.isArray(value)) {
33
+ const items = value
34
+ .map((item) => serializeValue(item))
35
+ .filter((item): item is string => item !== null)
36
+ .map((item) => (item.startsWith('{') && item.endsWith('}') ? item.slice(1, -1) : item));
69
37
 
70
- /**
71
- * Convert compiled jsxDEV/jsx calls back to clean JSX syntax.
72
- * Handles: jsxDEV(Component, { props, children }, ...) -> <Component props>children</Component>
73
- */
74
- function decompileJsxDev(code: string, indent = 0): string {
75
- const indentStr = ' '.repeat(indent);
76
-
77
- // Match jsxDEV or jsx call: jsxDEV(Component, {props}, ...)
78
- // Also handle _jsxDEV, /* @__PURE__ */ prefix
79
- const jsxMatch = code.match(/(?:\/\*\s*@__PURE__\s*\*\/\s*)?_?jsxs?(?:DEV)?\s*\(\s*([^,]+)\s*,\s*(\{[\s\S]*\})\s*(?:,[\s\S]*)?\)$/);
80
-
81
- if (!jsxMatch) {
82
- // Not a jsxDEV call - might be a plain string or primitive
83
- const trimmed = code.trim();
84
- // If it's a quoted string, return the content
85
- if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
86
- (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
87
- return trimmed.slice(1, -1);
88
- }
89
- return trimmed;
38
+ if (items.length === 0) return '{[]}';
39
+ return `{[${items.join(', ')}]}`;
90
40
  }
91
41
 
92
- let componentName = jsxMatch[1].trim();
93
- const propsStr = jsxMatch[2];
94
-
95
- // Clean up component name (remove quotes for HTML elements)
96
- if (componentName.startsWith('"') || componentName.startsWith("'")) {
97
- componentName = componentName.slice(1, -1);
42
+ if (typeof value === 'object') {
43
+ return '{/* complex value */}';
98
44
  }
99
45
 
100
- // Parse props object - extract key-value pairs
101
- const props: Record<string, string> = {};
102
- let children: string | null = null;
103
-
104
- // Debug props that should be excluded (added by jsxDEV in development)
105
- const debugProps = new Set(['fileName', 'lineNumber', 'columnNumber', '__source', '__self']);
106
-
107
- // Simple prop extraction (handles most common cases)
108
- // Match: key: value, key: "string", key: number, key: boolean
109
- const propMatches = propsStr.matchAll(/(\w+)\s*:\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[\w.]+|\{[^}]*\}|(?:\/\*[\s\S]*?\*\/\s*)?_?jsxs?(?:DEV)?\s*\([^)]*(?:\([^)]*\)[^)]*)*\))/g);
110
-
111
- for (const match of propMatches) {
112
- const key = match[1];
113
- const value = match[2];
46
+ return null;
47
+ }
114
48
 
115
- // Skip debug props
116
- if (debugProps.has(key)) continue;
49
+ function toSingleLine(code: string): string {
50
+ return code
51
+ .split('\n')
52
+ .map((line) => line.trim())
53
+ .filter(Boolean)
54
+ .join(' ')
55
+ .trim();
56
+ }
117
57
 
118
- if (key === 'children') {
119
- // Handle children specially
120
- if (value.includes('jsxDEV') || value.includes('jsx(') || value.includes('_jsx')) {
121
- // Nested JSX - recursively decompile
122
- children = decompileJsxDev(value, indent + 1);
123
- } else if (value.startsWith('"') || value.startsWith("'")) {
124
- // String children
125
- children = value.slice(1, -1);
126
- } else {
127
- children = `{${value}}`;
128
- }
129
- } else {
130
- // Regular props
131
- if (value.startsWith('"') || value.startsWith("'")) {
132
- props[key] = value; // Keep as quoted string
133
- } else if (value === 'true' || value === 'false') {
134
- props[key] = value === 'true' ? `{true}` : `{false}`;
135
- } else if (!isNaN(Number(value))) {
136
- props[key] = `{${value}}`;
137
- } else {
138
- props[key] = `{${value}}`;
139
- }
140
- }
141
- }
58
+ function buildFallbackSnippet(componentName: string, variant: FragmentVariant): string {
59
+ const args = variant.args ?? {};
60
+ const propEntries = Object.entries(args)
61
+ .filter(([name, value]) => name !== 'children' && typeof value !== 'function')
62
+ .map(([name, value]) => {
63
+ const serialized = serializeValue(value);
64
+ return serialized ? `${name}=${serialized}` : null;
65
+ })
66
+ .filter((entry): entry is string => entry !== null);
67
+
68
+ const children = typeof args.children === 'string' ? args.children : null;
69
+ const openTag = propEntries.length > 0
70
+ ? `<${componentName} ${propEntries.join(' ')}>`
71
+ : `<${componentName}>`;
72
+
73
+ const usage = children
74
+ ? `${openTag}${children}</${componentName}>`
75
+ : toSingleLine(`${openTag}</${componentName}>`);
76
+
77
+ return `import { ${componentName} } from '@/components/${componentName}';
78
+
79
+ // TODO: Add explicit \`code\` for variant "${variant.name}" in this fragment file.
80
+ ${usage}`;
81
+ }
142
82
 
143
- // Build JSX string
144
- const propParts = Object.entries(props)
145
- .filter(([k]) => k !== 'children')
146
- .map(([k, v]) => {
147
- // Convert camelCase event handlers back
148
- if (v.startsWith('"') || v.startsWith("'")) {
149
- return `${k}=${v}`;
150
- }
151
- return `${k}=${v}`;
152
- });
83
+ function normalizeAuthoredSnippet(code: string): string {
84
+ const normalized = code.replace(/\r\n/g, '\n').trim();
85
+ return normalized.length > 0 ? normalized : '';
86
+ }
153
87
 
154
- const propsJsx = propParts.length > 0 ? ' ' + propParts.join(' ') : '';
88
+ export function __buildFallbackSnippetForTest(componentName: string, variant: FragmentVariant): string {
89
+ return buildFallbackSnippet(componentName, variant);
90
+ }
155
91
 
156
- if (children) {
157
- if (children.includes('\n')) {
158
- return `${indentStr}<${componentName}${propsJsx}>\n${children}\n${indentStr}</${componentName}>`;
92
+ function resolveDisplayedCode(componentName: string, variant: FragmentVariant): string {
93
+ if (typeof variant.code === 'string') {
94
+ const normalized = normalizeAuthoredSnippet(variant.code);
95
+ if (normalized.length > 0) {
96
+ return normalized;
159
97
  }
160
- return `${indentStr}<${componentName}${propsJsx}>${children}</${componentName}>`;
161
98
  }
162
99
 
163
- return `${indentStr}<${componentName}${propsJsx} />`;
100
+ return buildFallbackSnippet(componentName, variant);
164
101
  }
165
102
 
166
- /**
167
- * Extract the JSX body from a render function's source code.
168
- * Works with arrow functions like: () => <Component /> or () => (<div>...</div>)
169
- * Also handles compiled jsxDEV calls and converts them back to JSX.
170
- */
171
- function extractRenderBody(renderFn: () => ReactNode): string | null {
172
- try {
173
- const source = renderFn.toString();
174
-
175
- // Match arrow function body: () => <...> or () => (...)
176
- // Handle both: () => <X /> and () => (\n <X />\n)
177
- const arrowMatch = source.match(/^\s*\(\s*\)\s*=>\s*(.+)$/s);
178
- if (arrowMatch) {
179
- let body = arrowMatch[1].trim();
180
-
181
- // Remove outer parentheses if present
182
- if (body.startsWith('(') && body.endsWith(')')) {
183
- body = body.slice(1, -1).trim();
184
- }
185
-
186
- // Check if this is compiled JSX (jsxDEV calls)
187
- if (body.includes('jsxDEV') || body.includes('jsx(') || body.includes('_jsx')) {
188
- return decompileJsxDev(body);
189
- }
190
-
191
- return body;
192
- }
193
-
194
- // Match function body with return statement
195
- const returnMatch = source.match(/return\s*\(\s*([\s\S]*?)\s*\)\s*;?\s*\}?\s*$/);
196
- if (returnMatch) {
197
- const body = returnMatch[1].trim();
198
-
199
- // Check if this is compiled JSX
200
- if (body.includes('jsxDEV') || body.includes('jsx(') || body.includes('_jsx')) {
201
- return decompileJsxDev(body);
202
- }
203
-
204
- return body;
205
- }
206
-
207
- return null;
208
- } catch {
209
- return null;
210
- }
103
+ export function __resolveDisplayedCodeForTest(componentName: string, variant: FragmentVariant): string {
104
+ return resolveDisplayedCode(componentName, variant);
211
105
  }
212
106
 
213
- export function CodePanel({ variant, componentName, compact = false, propDefs }: CodePanelProps) {
214
- // Generate code - extract from render function source for accuracy
215
- const generatedCode = useMemo(() => {
216
- // Priority 1: Use variant.code if available (from compiled JSON/AST)
217
- if (variant.code) {
218
- return generateFullExample(componentName, variant.code);
219
- }
220
-
221
- // Priority 2: Extract source from render function (runtime)
222
- if (variant.render) {
223
- const renderBody = extractRenderBody(variant.render);
224
- if (renderBody) {
225
- return generateFullExample(componentName, renderBody);
226
- }
227
- }
228
-
229
- // Priority 3: Generate from args (fallback for edge cases)
230
- const needsState = needsStatefulExample(componentName, propDefs);
231
- let effectiveArgs = variant.args;
232
-
233
- // If no args on variant, try to extract from rendered element
234
- if (!effectiveArgs || Object.keys(effectiveArgs).length === 0) {
235
- effectiveArgs = extractPropsFromRender(variant, componentName) ?? undefined;
236
- }
237
-
238
- return generateCombinedCode(componentName, propDefs, effectiveArgs, needsState);
239
- }, [componentName, variant, propDefs]);
107
+ export function CodePanel({ variant, componentName, compact = false }: CodePanelProps) {
108
+ const code = useMemo(() => resolveDisplayedCode(componentName, variant), [componentName, variant]);
240
109
 
241
110
  return (
242
111
  <CodeBlock
243
- code={generatedCode}
112
+ code={code}
244
113
  language="tsx"
245
114
  showCopy
246
115
  maxHeight={400}
@@ -248,264 +117,3 @@ export function CodePanel({ variant, componentName, compact = false, propDefs }:
248
117
  />
249
118
  );
250
119
  }
251
-
252
- /**
253
- * Normalize indentation by removing common leading whitespace from all lines.
254
- * Handles JSX where first line may be at column 0 but inner content is indented.
255
- */
256
- function normalizeIndentation(code: string): string {
257
- const lines = code.split('\n');
258
- if (lines.length <= 1) return code;
259
-
260
- // Find minimum indentation (ignoring empty lines and first line)
261
- // First line often has 0 indent, but inner content is over-indented
262
- let minIndent = Infinity;
263
- const firstLineIndent = lines[0].match(/^(\s*)/)?.[1].length ?? 0;
264
-
265
- for (let i = 1; i < lines.length; i++) {
266
- const line = lines[i];
267
- if (line.trim().length === 0) continue;
268
- const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
269
- minIndent = Math.min(minIndent, indent);
270
- }
271
-
272
- // If first line has indent, include it in calculation
273
- if (firstLineIndent > 0) {
274
- minIndent = Math.min(minIndent, firstLineIndent);
275
- }
276
-
277
- if (minIndent === Infinity || minIndent === 0) return code;
278
-
279
- // Remove the common indentation from all lines
280
- return lines
281
- .map(line => line.slice(Math.min(minIndent, line.match(/^(\s*)/)?.[1].length ?? 0)))
282
- .join('\n');
283
- }
284
-
285
- // Generate a full example from the variant's code string
286
- // Shows the ACTUAL code from the fragment file - no transformations
287
- function generateFullExample(
288
- componentName: string,
289
- variantCode: string
290
- ): string {
291
- // Normalize indentation first
292
- const normalizedCode = normalizeIndentation(variantCode);
293
-
294
- // Add component import
295
- const imports = [`import { ${componentName} } from '@/components/${componentName}';`];
296
-
297
- // Check if the code uses StatefulXxx wrapper - if so, include that helper
298
- const statefulPattern = new RegExp(`<Stateful${componentName}`, 'i');
299
- const usesStatefulWrapper = statefulPattern.test(normalizedCode);
300
-
301
- if (usesStatefulWrapper) {
302
- imports.unshift(`import { useState } from 'react';`);
303
-
304
- // Determine the state type based on component
305
- const lowerName = componentName.toLowerCase();
306
- const isToggleLike = lowerName.includes('toggle') || lowerName.includes('checkbox') || lowerName.includes('switch');
307
-
308
- const stateType = isToggleLike ? 'boolean' : 'string';
309
- const defaultValue = isToggleLike ? 'false' : "''";
310
- const stateName = isToggleLike ? 'checked' : 'value';
311
- const setterName = isToggleLike ? 'setChecked' : 'setValue';
312
- const valueProp = isToggleLike ? 'checked' : 'value';
313
- const handlerProp = 'onChange';
314
-
315
- // Generate the stateful wrapper helper that the code uses
316
- const helperCode = `
317
- // Stateful wrapper for interactive demos
318
- function Stateful${componentName}(props: React.ComponentProps<typeof ${componentName}>) {
319
- const [${stateName}, ${setterName}] = useState(props.${valueProp} ?? ${defaultValue});
320
- return <${componentName} {...props} ${valueProp}={${stateName}} ${handlerProp}={${setterName}} />;
321
- }`;
322
-
323
- return `${imports.join('\n')}
324
- ${helperCode}
325
-
326
- // Example usage:
327
- ${normalizedCode}`;
328
- }
329
-
330
- // For non-stateful code, just show the raw code with import
331
- return `${imports.join('\n')}
332
-
333
- ${normalizedCode}`;
334
- }
335
-
336
- // Generate combined import statement + JSX usage
337
- function generateCombinedCode(
338
- componentName: string,
339
- propDefs?: Record<string, PropDefinition>,
340
- variantArgs?: Record<string, unknown>,
341
- needsState?: boolean
342
- ): string {
343
- const imports: string[] = [];
344
- const hooks: string[] = [];
345
-
346
- if (needsState) {
347
- imports.push(`import { useState } from 'react';`);
348
- }
349
- imports.push(`import { ${componentName} } from '@/components/${componentName}';`);
350
-
351
- // Determine state props that will be managed by hooks (to avoid duplicates)
352
- const lowerName = componentName.toLowerCase();
353
- const isToggleLike = lowerName.includes('toggle') || lowerName.includes('checkbox') || lowerName.includes('switch');
354
- const statePropsToSkip = needsState
355
- ? isToggleLike
356
- ? ['checked', 'onChange', 'onCheckedChange']
357
- : ['value', 'onChange', 'onValueChange']
358
- : [];
359
-
360
- // Build props string - prefer actual variant args over prop definitions
361
- const propsEntries: string[] = [];
362
- let childrenValue: unknown = undefined;
363
-
364
- if (variantArgs && Object.keys(variantArgs).length > 0) {
365
- // Use actual variant args for accurate code generation
366
- for (const [name, value] of Object.entries(variantArgs)) {
367
- if (value === undefined) continue;
368
-
369
- // Handle children specially - will be rendered as element content
370
- if (name === 'children') {
371
- childrenValue = value;
372
- continue;
373
- }
374
-
375
- // Skip function props (callbacks) - they're runtime handlers
376
- if (typeof value === 'function') continue;
377
-
378
- // Skip props that will be managed by state hooks
379
- if (statePropsToSkip.includes(name)) continue;
380
-
381
- const propType = propDefs?.[name]?.type || typeof value;
382
- const formattedValue = formatPropValue(value, propType);
383
- propsEntries.push(`${name}=${formattedValue}`);
384
- }
385
- } else if (propDefs) {
386
- // Fallback to prop definitions if no variant args
387
- for (const [name, def] of Object.entries(propDefs)) {
388
- if (name === 'children') {
389
- if (def.default !== undefined) {
390
- childrenValue = def.default;
391
- }
392
- continue;
393
- }
394
-
395
- // Skip props that will be managed by state hooks
396
- if (statePropsToSkip.includes(name)) continue;
397
-
398
- if (def.required && def.default !== undefined) {
399
- const value = formatPropValue(def.default, def.type);
400
- propsEntries.push(`${name}=${value}`);
401
- } else if (def.required) {
402
- const placeholder = getTypePlaceholder(name, def);
403
- propsEntries.push(`${name}=${placeholder}`);
404
- }
405
- }
406
- }
407
-
408
- // Build JSX code
409
- const hasChildren = childrenValue !== undefined && childrenValue !== null;
410
- const childrenStr = hasChildren ? formatChildrenValue(childrenValue) : null;
411
-
412
- // For stateful components, wrap in a function component
413
- if (needsState) {
414
- let stateName = 'value';
415
- let setterName = 'setValue';
416
- let handlerProp = 'onChange';
417
- let valueProp = 'value';
418
- let defaultValue = "''";
419
- let stateType = 'string';
420
-
421
- if (isToggleLike) {
422
- stateType = 'boolean';
423
- defaultValue = 'false';
424
- stateName = 'checked';
425
- setterName = 'setChecked';
426
- handlerProp = propDefs?.['onCheckedChange'] ? 'onCheckedChange' : 'onChange';
427
- valueProp = 'checked';
428
- }
429
-
430
- hooks.push(`const [${stateName}, ${setterName}] = useState<${stateType}>(${defaultValue});`);
431
-
432
- // Add state props
433
- propsEntries.push(`${valueProp}={${stateName}}`);
434
- propsEntries.push(`${handlerProp}={${setterName}}`);
435
-
436
- const propsStr = propsEntries.length > 0
437
- ? `\n ${propsEntries.join('\n ')}\n `
438
- : ' ';
439
-
440
- const jsxCode = hasChildren
441
- ? `<${componentName}${propsStr}>${childrenStr}</${componentName}>`
442
- : `<${componentName}${propsStr}/>`;
443
-
444
- return `${imports.join('\n')}
445
-
446
- function Example() {
447
- ${hooks.join('\n ')}
448
-
449
- return (
450
- ${jsxCode}
451
- );
452
- }`;
453
- }
454
-
455
- // Simple non-stateful code
456
- let jsxCode: string;
457
-
458
- if (propsEntries.length === 0 && !hasChildren) {
459
- jsxCode = `<${componentName} />`;
460
- } else if (propsEntries.length === 0 && hasChildren) {
461
- jsxCode = `<${componentName}>${childrenStr}</${componentName}>`;
462
- } else if (propsEntries.length === 1 && !hasChildren) {
463
- jsxCode = `<${componentName} ${propsEntries[0]} />`;
464
- } else if (propsEntries.length === 1 && hasChildren) {
465
- jsxCode = `<${componentName} ${propsEntries[0]}>\n ${childrenStr}\n</${componentName}>`;
466
- } else if (!hasChildren) {
467
- jsxCode = `<${componentName}\n ${propsEntries.join('\n ')}\n/>`;
468
- } else {
469
- jsxCode = `<${componentName}\n ${propsEntries.join('\n ')}\n>\n ${childrenStr}\n</${componentName}>`;
470
- }
471
-
472
- return `${imports.join('\n')}
473
-
474
- ${jsxCode}`;
475
- }
476
-
477
- function formatChildrenValue(value: unknown): string {
478
- if (typeof value === 'string') {
479
- return value;
480
- }
481
- if (typeof value === 'number' || typeof value === 'boolean') {
482
- return `{${value}}`;
483
- }
484
- return '{/* children */}';
485
- }
486
-
487
- function formatPropValue(value: unknown, type: string): string {
488
- if (type === 'string' || typeof value === 'string') {
489
- return `"${value}"`;
490
- }
491
- if (type === 'boolean' || typeof value === 'boolean') {
492
- return `{${value}}`;
493
- }
494
- if (type === 'number' || typeof value === 'number') {
495
- return `{${value}}`;
496
- }
497
- return `{${JSON.stringify(value)}}`;
498
- }
499
-
500
- function getTypePlaceholder(name: string, def: PropDefinition): string {
501
- if (def.values && def.values.length > 0) {
502
- return `"${def.values[0]}"`;
503
- }
504
- switch (def.type) {
505
- case 'string': return `"${name}"`;
506
- case 'boolean': return '{true}';
507
- case 'number': return '{0}';
508
- case 'function': return '{() => {}}';
509
- default: return '{undefined}';
510
- }
511
- }
@@ -9,11 +9,11 @@ interface LayoutProps {
9
9
 
10
10
  export function Layout({ leftSidebar, header, children }: LayoutProps) {
11
11
  return (
12
- <AppShell layout="inset">
12
+ <AppShell>
13
13
  <AppShell.Header>
14
14
  {header}
15
15
  </AppShell.Header>
16
- <AppShell.Sidebar width="256px" collapsible="icon">
16
+ <AppShell.Sidebar width="260px" collapsible="icon">
17
17
  {leftSidebar}
18
18
  </AppShell.Sidebar>
19
19
  <AppShell.Main padding="none">