figma-local 1.0.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 +342 -0
- package/bin/fig-start +289 -0
- package/bin/setup-alias.sh +48 -0
- package/package.json +47 -0
- package/src/blocks/dashboard-01.js +379 -0
- package/src/blocks/index.js +27 -0
- package/src/daemon.js +664 -0
- package/src/figjam-client.js +313 -0
- package/src/figma-client.js +4198 -0
- package/src/figma-patch.js +185 -0
- package/src/index.js +8543 -0
- package/src/platform.js +206 -0
- package/src/prompt-templates.js +289 -0
- package/src/read.js +243 -0
- package/src/shadcn.js +237 -0
package/src/read.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* read.js — Staged, token-efficient Figma design extraction
|
|
3
|
+
*
|
|
4
|
+
* Instead of dumping raw canvas data, this extracts design info in stages
|
|
5
|
+
* and returns a lean structured text block (91-97% smaller than full dumps).
|
|
6
|
+
*
|
|
7
|
+
* Inspired by the figma-to-ai-prompter approach by Roy Villasana:
|
|
8
|
+
* only pull metadata first, then frame details, then only the tokens
|
|
9
|
+
* that specific frame actually uses — nothing more.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Stage 1: Lightweight canvas metadata
|
|
14
|
+
* Returns page name, frame names/IDs, total node count.
|
|
15
|
+
* No layout data, no token data — just a map.
|
|
16
|
+
*/
|
|
17
|
+
export const STAGE1_METADATA = `
|
|
18
|
+
(function() {
|
|
19
|
+
var page = figma.currentPage;
|
|
20
|
+
var frames = page.children.filter(function(n) {
|
|
21
|
+
return n.type === 'FRAME' || n.type === 'COMPONENT' || n.type === 'COMPONENT_SET';
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
page: page.name,
|
|
25
|
+
frameCount: frames.length,
|
|
26
|
+
totalNodes: page.children.length,
|
|
27
|
+
frames: frames.map(function(f) {
|
|
28
|
+
return { id: f.id, name: f.name, type: f.type, w: Math.round(f.width), h: Math.round(f.height) };
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
})()
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stage 2: Frame structure — layout, components, text content.
|
|
36
|
+
* Only runs on the specific frame the user cares about.
|
|
37
|
+
* @param {string} frameId
|
|
38
|
+
*/
|
|
39
|
+
export function buildFrameStructureCode(frameId) {
|
|
40
|
+
return `
|
|
41
|
+
(function() {
|
|
42
|
+
var target = figma.getNodeById('${frameId}');
|
|
43
|
+
if (!target) return { error: 'Node not found: ${frameId}' };
|
|
44
|
+
|
|
45
|
+
function summariseNode(node, depth) {
|
|
46
|
+
if (depth > 4) return null; // cap depth to avoid huge dumps
|
|
47
|
+
var entry = {
|
|
48
|
+
id: node.id,
|
|
49
|
+
name: node.name,
|
|
50
|
+
type: node.type,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Layout
|
|
54
|
+
if (node.width !== undefined) {
|
|
55
|
+
entry.size = Math.round(node.width) + 'x' + Math.round(node.height);
|
|
56
|
+
}
|
|
57
|
+
if (node.layoutMode && node.layoutMode !== 'NONE') {
|
|
58
|
+
entry.layout = node.layoutMode.toLowerCase();
|
|
59
|
+
entry.gap = node.itemSpacing || 0;
|
|
60
|
+
entry.padding = [node.paddingTop||0, node.paddingRight||0, node.paddingBottom||0, node.paddingLeft||0];
|
|
61
|
+
entry.align = node.primaryAxisAlignItems;
|
|
62
|
+
entry.crossAlign = node.counterAxisAlignItems;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Text
|
|
66
|
+
if (node.type === 'TEXT') {
|
|
67
|
+
entry.text = node.characters;
|
|
68
|
+
if (node.fontSize) entry.fontSize = node.fontSize;
|
|
69
|
+
if (node.fontName) entry.fontFamily = node.fontName.family;
|
|
70
|
+
if (node.fontWeight) entry.fontWeight = node.fontWeight;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Fill color (first solid fill, or variable binding)
|
|
74
|
+
if (node.fills && node.fills.length > 0) {
|
|
75
|
+
var fill = node.fills[0];
|
|
76
|
+
if (fill.type === 'SOLID') {
|
|
77
|
+
var r = Math.round((fill.color.r || 0) * 255);
|
|
78
|
+
var g = Math.round((fill.color.g || 0) * 255);
|
|
79
|
+
var b = Math.round((fill.color.b || 0) * 255);
|
|
80
|
+
entry.fill = '#' + r.toString(16).padStart(2,'0') + g.toString(16).padStart(2,'0') + b.toString(16).padStart(2,'0');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Corner radius
|
|
85
|
+
if (node.cornerRadius && node.cornerRadius !== 0) {
|
|
86
|
+
entry.radius = node.cornerRadius;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Visibility
|
|
90
|
+
if (node.visible === false) entry.hidden = true;
|
|
91
|
+
|
|
92
|
+
// Component instance
|
|
93
|
+
if (node.type === 'INSTANCE' && node.mainComponent) {
|
|
94
|
+
entry.component = node.mainComponent.name;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Children
|
|
98
|
+
if (node.children && node.children.length > 0) {
|
|
99
|
+
var kids = [];
|
|
100
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
101
|
+
var child = summariseNode(node.children[i], depth + 1);
|
|
102
|
+
if (child) kids.push(child);
|
|
103
|
+
}
|
|
104
|
+
if (kids.length > 0) entry.children = kids;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return entry;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return summariseNode(target, 0);
|
|
111
|
+
})()
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Stage 3: Extract only variable bindings used in this frame.
|
|
117
|
+
* Returns { varName: resolvedValue } for each unique variable the frame references.
|
|
118
|
+
* @param {string} frameId
|
|
119
|
+
*/
|
|
120
|
+
export function buildUsedTokensCode(frameId) {
|
|
121
|
+
return `
|
|
122
|
+
(function() {
|
|
123
|
+
var target = figma.getNodeById('${frameId}');
|
|
124
|
+
if (!target) return {};
|
|
125
|
+
|
|
126
|
+
var usedVars = {};
|
|
127
|
+
|
|
128
|
+
function collectVars(node) {
|
|
129
|
+
// Check variable bindings on fills, strokes, effects
|
|
130
|
+
if (node.boundVariables) {
|
|
131
|
+
var bindings = node.boundVariables;
|
|
132
|
+
Object.keys(bindings).forEach(function(prop) {
|
|
133
|
+
var binding = bindings[prop];
|
|
134
|
+
if (!binding) return;
|
|
135
|
+
var ids = Array.isArray(binding) ? binding.map(function(b){ return b.id; }) : [binding.id];
|
|
136
|
+
ids.forEach(function(id) {
|
|
137
|
+
if (!id) return;
|
|
138
|
+
try {
|
|
139
|
+
var v = figma.variables.getVariableById(id);
|
|
140
|
+
if (v) {
|
|
141
|
+
var modes = Object.keys(v.valuesByMode);
|
|
142
|
+
var val = modes.length > 0 ? v.valuesByMode[modes[0]] : null;
|
|
143
|
+
var resolved = val;
|
|
144
|
+
if (val && typeof val === 'object' && val.r !== undefined) {
|
|
145
|
+
var r = Math.round((val.r||0)*255);
|
|
146
|
+
var g = Math.round((val.g||0)*255);
|
|
147
|
+
var b = Math.round((val.b||0)*255);
|
|
148
|
+
resolved = '#' + r.toString(16).padStart(2,'0') + g.toString(16).padStart(2,'0') + b.toString(16).padStart(2,'0');
|
|
149
|
+
}
|
|
150
|
+
usedVars[v.name] = resolved;
|
|
151
|
+
}
|
|
152
|
+
} catch(e) {}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (node.children) {
|
|
158
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
159
|
+
collectVars(node.children[i]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
collectVars(target);
|
|
165
|
+
return usedVars;
|
|
166
|
+
})()
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Format the staged data into a lean structured text block.
|
|
172
|
+
* This is what gets passed to Claude / AI tools — not raw JSON.
|
|
173
|
+
*
|
|
174
|
+
* @param {object} metadata - Stage 1 result
|
|
175
|
+
* @param {object} frame - Stage 2 result (frame structure)
|
|
176
|
+
* @param {object} tokens - Stage 3 result (used tokens only)
|
|
177
|
+
* @param {string} frameName - The frame we focused on
|
|
178
|
+
*/
|
|
179
|
+
export function formatLeanContext(metadata, frame, tokens, frameName) {
|
|
180
|
+
const lines = [];
|
|
181
|
+
|
|
182
|
+
lines.push(`## Design Context — ${frameName}`);
|
|
183
|
+
lines.push(`Page: ${metadata.page} | Total frames: ${metadata.frameCount}`);
|
|
184
|
+
lines.push('');
|
|
185
|
+
|
|
186
|
+
// Frame summary
|
|
187
|
+
if (frame && !frame.error) {
|
|
188
|
+
lines.push(`### Frame: ${frame.name} (${frame.size})`);
|
|
189
|
+
if (frame.layout) {
|
|
190
|
+
lines.push(`Layout: ${frame.layout}, gap=${frame.gap}px, padding=[${frame.padding?.join(',')}]`);
|
|
191
|
+
}
|
|
192
|
+
lines.push('');
|
|
193
|
+
|
|
194
|
+
// Component tree (compact)
|
|
195
|
+
lines.push('### Structure');
|
|
196
|
+
lines.push(formatNode(frame, 0));
|
|
197
|
+
lines.push('');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Used tokens only
|
|
201
|
+
const tokenKeys = Object.keys(tokens || {});
|
|
202
|
+
if (tokenKeys.length > 0) {
|
|
203
|
+
lines.push(`### Design Tokens (${tokenKeys.length} used in this frame)`);
|
|
204
|
+
for (const key of tokenKeys) {
|
|
205
|
+
lines.push(` ${key}: ${tokens[key]}`);
|
|
206
|
+
}
|
|
207
|
+
lines.push('');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Token estimate
|
|
211
|
+
const text = lines.join('\n');
|
|
212
|
+
const tokenEst = Math.round(text.length / 4);
|
|
213
|
+
lines.push(`---`);
|
|
214
|
+
lines.push(`Token estimate: ~${tokenEst} tokens (${text.length} chars)`);
|
|
215
|
+
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Compact node tree formatter — indented, key info only.
|
|
221
|
+
*/
|
|
222
|
+
function formatNode(node, depth) {
|
|
223
|
+
const indent = ' '.repeat(depth);
|
|
224
|
+
const parts = [`${indent}[${node.type}] ${node.name}`];
|
|
225
|
+
|
|
226
|
+
if (node.size) parts[0] += ` (${node.size})`;
|
|
227
|
+
if (node.text) parts[0] += ` "${node.text.slice(0, 40)}${node.text.length > 40 ? '…' : ''}"`;
|
|
228
|
+
if (node.component) parts[0] += ` → ${node.component}`;
|
|
229
|
+
if (node.fill) parts[0] += ` fill=${node.fill}`;
|
|
230
|
+
if (node.radius) parts[0] += ` r=${node.radius}`;
|
|
231
|
+
if (node.hidden) parts[0] += ` [hidden]`;
|
|
232
|
+
|
|
233
|
+
const result = [parts[0]];
|
|
234
|
+
if (node.children && depth < 3) {
|
|
235
|
+
for (const child of node.children) {
|
|
236
|
+
result.push(formatNode(child, depth + 1));
|
|
237
|
+
}
|
|
238
|
+
} else if (node.children && node.children.length > 0) {
|
|
239
|
+
result.push(`${' '.repeat(depth + 1)}… ${node.children.length} more children`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return result.join('\n');
|
|
243
|
+
}
|
package/src/shadcn.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shadcn/ui Component Generator for Figma
|
|
3
|
+
*
|
|
4
|
+
* Generates Figma components matching official shadcn/ui specs.
|
|
5
|
+
* Based on the public registry at ui.shadcn.com.
|
|
6
|
+
* Requires shadcn tokens: `node src/index.js tokens preset shadcn`
|
|
7
|
+
*
|
|
8
|
+
* Uses real Lucide icons via <Icon name="lucide:icon-name" /> syntax.
|
|
9
|
+
* Icons are fetched from Iconify API and rendered as SVG nodes in Figma.
|
|
10
|
+
*
|
|
11
|
+
* Tailwind class mapping:
|
|
12
|
+
* h-10 = 40px, h-9 = 36px, h-11 = 44px
|
|
13
|
+
* px-4 = 16px, px-3 = 12px, px-8 = 32px, py-2 = 8px
|
|
14
|
+
* p-6 = 24px, p-4 = 16px
|
|
15
|
+
* rounded-md = 6px, rounded-lg = 8px, rounded-full = 9999px
|
|
16
|
+
* text-sm = 14px, text-xs = 12px, text-2xl = 24px
|
|
17
|
+
* space-y-1.5 = gap 6px
|
|
18
|
+
* px-2.5 = 10px, py-0.5 = 2px
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const components = {
|
|
22
|
+
|
|
23
|
+
// ── Button ──────────────────────────────────────────────────────────
|
|
24
|
+
// Base: inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium
|
|
25
|
+
// Variants: default, secondary, destructive, outline, ghost, link
|
|
26
|
+
// Sizes: default (h-10 px-4 py-2), sm (h-9 px-3), lg (h-11 px-8), icon (h-10 w-10)
|
|
27
|
+
button: () => {
|
|
28
|
+
const btn = (name, h, px, rounded, bg, fg) =>
|
|
29
|
+
`<Frame name="${name}" h={${h}} bg="${bg}" rounded={${rounded}} flex="row" justify="center" items="center" gap={8} px={${px}} py={8}><Text size={14} weight="medium" color="${fg}">Button</Text></Frame>`;
|
|
30
|
+
return [
|
|
31
|
+
{ name: 'Button / Default', jsx: btn('Button / Default', 40, 16, 6, 'var:primary', 'var:primary-foreground') },
|
|
32
|
+
{ name: 'Button / Secondary', jsx: btn('Button / Secondary', 40, 16, 6, 'var:secondary', 'var:secondary-foreground') },
|
|
33
|
+
{ name: 'Button / Destructive', jsx: btn('Button / Destructive', 40, 16, 6, 'var:destructive', 'var:destructive-foreground') },
|
|
34
|
+
{ name: 'Button / Outline', jsx: `<Frame name="Button / Outline" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" justify="center" items="center" gap={8} px={16} py={8}><Text size={14} weight="medium" color="var:foreground">Button</Text></Frame>` },
|
|
35
|
+
{ name: 'Button / Ghost', jsx: `<Frame name="Button / Ghost" h={40} rounded={6} flex="row" justify="center" items="center" gap={8} px={16} py={8}><Text size={14} weight="medium" color="var:foreground">Button</Text></Frame>` },
|
|
36
|
+
{ name: 'Button / Link', jsx: `<Frame name="Button / Link" h={40} rounded={6} flex="row" justify="center" items="center" gap={8} px={16} py={8}><Text size={14} weight="medium" color="var:primary">Button</Text></Frame>` },
|
|
37
|
+
{ name: 'Button / Small', jsx: btn('Button / Small', 36, 12, 6, 'var:primary', 'var:primary-foreground') },
|
|
38
|
+
{ name: 'Button / Large', jsx: btn('Button / Large', 44, 32, 6, 'var:primary', 'var:primary-foreground') },
|
|
39
|
+
{ name: 'Button / Icon', jsx: `<Frame name="Button / Icon" w={40} h={40} bg="var:primary" rounded={6} flex="row" justify="center" items="center"><Icon name="lucide:plus" size={16} color="var:primary-foreground" /></Frame>` },
|
|
40
|
+
];
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// ── Badge ───────────────────────────────────────────────────────────
|
|
44
|
+
// Base: inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold
|
|
45
|
+
badge: () => [
|
|
46
|
+
{ name: 'Badge / Default', jsx: `<Frame name="Badge / Default" bg="var:primary" rounded={9999} flex="row" items="center" px={10} py={2}><Text size={12} weight="semibold" color="var:primary-foreground">Badge</Text></Frame>` },
|
|
47
|
+
{ name: 'Badge / Secondary', jsx: `<Frame name="Badge / Secondary" bg="var:secondary" rounded={9999} flex="row" items="center" px={10} py={2}><Text size={12} weight="semibold" color="var:secondary-foreground">Badge</Text></Frame>` },
|
|
48
|
+
{ name: 'Badge / Destructive', jsx: `<Frame name="Badge / Destructive" bg="var:destructive" rounded={9999} flex="row" items="center" px={10} py={2}><Text size={12} weight="semibold" color="var:destructive-foreground">Badge</Text></Frame>` },
|
|
49
|
+
{ name: 'Badge / Outline', jsx: `<Frame name="Badge / Outline" rounded={9999} flex="row" items="center" px={10} py={2} stroke="var:border" strokeWidth={1}><Text size={12} weight="semibold" color="var:foreground">Badge</Text></Frame>` },
|
|
50
|
+
],
|
|
51
|
+
|
|
52
|
+
// ── Card ─────────────────────────────────────────────────────────────
|
|
53
|
+
card: () => [
|
|
54
|
+
{ name: 'Card', jsx: `<Frame name="Card" w={350} flex="col" bg="var:card" stroke="var:border" strokeWidth={1} rounded={8} shadow="0px 1px 2px rgba(0,0,0,0.05)"><Frame name="CardHeader" flex="col" gap={6} p={24} w="fill"><Text size={24} weight="semibold" color="var:card-foreground" w="fill">Card Title</Text><Text size={14} color="var:muted-foreground" w="fill">Card description goes here.</Text></Frame><Frame name="CardContent" flex="col" gap={8} px={24} pb={24} w="fill"><Text size={14} color="var:card-foreground" w="fill">Your content goes here. Add any components or text.</Text></Frame><Frame name="CardFooter" flex="row" items="center" gap={8} px={24} pb={24} w="fill" justify="start"><Frame bg="var:primary" px={16} py={8} rounded={6} flex="row" justify="center" items="center"><Text size={14} weight="medium" color="var:primary-foreground">Save</Text></Frame><Frame bg="var:background" stroke="var:input" strokeWidth={1} px={16} py={8} rounded={6} flex="row" justify="center" items="center"><Text size={14} weight="medium" color="var:foreground">Cancel</Text></Frame></Frame></Frame>` },
|
|
55
|
+
],
|
|
56
|
+
|
|
57
|
+
// ── Input ────────────────────────────────────────────────────────────
|
|
58
|
+
input: () => [
|
|
59
|
+
{ name: 'Input / Default', jsx: `<Frame name="Input / Default" w={280} h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:muted-foreground">Email</Text></Frame>` },
|
|
60
|
+
{ name: 'Input / Filled', jsx: `<Frame name="Input / Filled" w={280} h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:foreground">john@example.com</Text></Frame>` },
|
|
61
|
+
{ name: 'Input / With Label', jsx: `<Frame name="Input / With Label" w={280} flex="col" gap={8}><Text size={14} weight="medium" color="var:foreground">Email</Text><Frame w="fill" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:muted-foreground">m@example.com</Text></Frame></Frame>` },
|
|
62
|
+
],
|
|
63
|
+
|
|
64
|
+
// ── Textarea ─────────────────────────────────────────────────────────
|
|
65
|
+
textarea: () => [
|
|
66
|
+
{ name: 'Textarea', jsx: `<Frame name="Textarea" w={280} h={80} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="col" p={12} pt={8}><Text size={14} color="var:muted-foreground" w="fill">Type your message here.</Text></Frame>` },
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
// ── Label ────────────────────────────────────────────────────────────
|
|
70
|
+
label: () => [
|
|
71
|
+
{ name: 'Label', jsx: `<Frame name="Label" flex="row"><Text size={14} weight="medium" color="var:foreground">Label</Text></Frame>` },
|
|
72
|
+
],
|
|
73
|
+
|
|
74
|
+
// ── Alert ────────────────────────────────────────────────────────────
|
|
75
|
+
alert: () => [
|
|
76
|
+
{ name: 'Alert / Default', jsx: `<Frame name="Alert / Default" w={400} flex="row" bg="var:background" stroke="var:border" strokeWidth={1} rounded={8} p={16} gap={12} items="start"><Icon name="lucide:info" size={16} color="var:foreground" /><Frame flex="col" gap={4} w="fill"><Text size={14} weight="medium" color="var:foreground" w="fill">Heads up!</Text><Text size={14} color="var:muted-foreground" w="fill">You can add components to your app using the CLI.</Text></Frame></Frame>` },
|
|
77
|
+
{ name: 'Alert / Destructive', jsx: `<Frame name="Alert / Destructive" w={400} flex="row" bg="var:background" stroke="var:destructive" strokeWidth={1} rounded={8} p={16} gap={12} items="start"><Icon name="lucide:alert-circle" size={16} color="var:destructive" /><Frame flex="col" gap={4} w="fill"><Text size={14} weight="medium" color="var:destructive" w="fill">Error</Text><Text size={14} color="var:muted-foreground" w="fill">Your session has expired. Please log in again.</Text></Frame></Frame>` },
|
|
78
|
+
],
|
|
79
|
+
|
|
80
|
+
// ── Avatar ───────────────────────────────────────────────────────────
|
|
81
|
+
avatar: () => [
|
|
82
|
+
{ name: 'Avatar / Default', jsx: `<Frame name="Avatar / Default" w={40} h={40} bg="var:muted" rounded={9999} flex="row" justify="center" items="center"><Text size={16} weight="medium" color="var:muted-foreground">CN</Text></Frame>` },
|
|
83
|
+
{ name: 'Avatar / Small', jsx: `<Frame name="Avatar / Small" w={32} h={32} bg="var:muted" rounded={9999} flex="row" justify="center" items="center"><Text size={12} weight="medium" color="var:muted-foreground">CN</Text></Frame>` },
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
// ── Switch ──────────────────────────────────────────────────────────
|
|
87
|
+
switch: () => [
|
|
88
|
+
{ name: 'Switch / On', jsx: `<Frame name="Switch / On" w={44} h={24} bg="var:primary" rounded={9999} flex="row" items="center" p={2} justify="end"><Frame w={20} h={20} bg="var:primary-foreground" rounded={9999} /></Frame>` },
|
|
89
|
+
{ name: 'Switch / Off', jsx: `<Frame name="Switch / Off" w={44} h={24} bg="var:input" rounded={9999} flex="row" items="center" p={2} justify="start"><Frame w={20} h={20} bg="var:background" rounded={9999} /></Frame>` },
|
|
90
|
+
],
|
|
91
|
+
|
|
92
|
+
// ── Separator ───────────────────────────────────────────────────────
|
|
93
|
+
separator: () => [
|
|
94
|
+
{ name: 'Separator / Horizontal', jsx: `<Frame name="Separator / Horizontal" w={280} h={1} bg="var:border" />` },
|
|
95
|
+
{ name: 'Separator / Vertical', jsx: `<Frame name="Separator / Vertical" w={1} h={40} bg="var:border" />` },
|
|
96
|
+
],
|
|
97
|
+
|
|
98
|
+
// ── Skeleton ────────────────────────────────────────────────────────
|
|
99
|
+
skeleton: () => [
|
|
100
|
+
{ name: 'Skeleton / Text', jsx: `<Frame name="Skeleton / Text" w={200} h={16} bg="var:muted" rounded={6} />` },
|
|
101
|
+
{ name: 'Skeleton / Circle', jsx: `<Frame name="Skeleton / Circle" w={40} h={40} bg="var:muted" rounded={9999} />` },
|
|
102
|
+
{ name: 'Skeleton / Card', jsx: `<Frame name="Skeleton / Card" w={350} flex="col" gap={12} p={24}><Frame flex="row" gap={16} items="center" w="fill"><Frame w={48} h={48} bg="var:muted" rounded={9999} /><Frame flex="col" gap={8} w="fill"><Frame w={200} h={16} bg="var:muted" rounded={6} /><Frame w={140} h={14} bg="var:muted" rounded={6} /></Frame></Frame><Frame w="fill" h={14} bg="var:muted" rounded={6} /><Frame w="fill" h={14} bg="var:muted" rounded={6} /><Frame w={200} h={14} bg="var:muted" rounded={6} /></Frame>` },
|
|
103
|
+
],
|
|
104
|
+
|
|
105
|
+
// ── Progress ────────────────────────────────────────────────────────
|
|
106
|
+
progress: () => [
|
|
107
|
+
{ name: 'Progress / 60%', jsx: `<Frame name="Progress / 60%" w={280} h={8} bg="var:secondary" rounded={9999} overflow="hidden"><Frame w={168} h={8} bg="var:primary" rounded={9999} /></Frame>` },
|
|
108
|
+
{ name: 'Progress / 30%', jsx: `<Frame name="Progress / 30%" w={280} h={8} bg="var:secondary" rounded={9999} overflow="hidden"><Frame w={84} h={8} bg="var:primary" rounded={9999} /></Frame>` },
|
|
109
|
+
],
|
|
110
|
+
|
|
111
|
+
// ── Toggle ──────────────────────────────────────────────────────────
|
|
112
|
+
toggle: () => [
|
|
113
|
+
{ name: 'Toggle / Default', jsx: `<Frame name="Toggle / Default" w={40} h={40} rounded={6} flex="row" justify="center" items="center"><Icon name="lucide:bold" size={16} color="var:foreground" /></Frame>` },
|
|
114
|
+
{ name: 'Toggle / Active', jsx: `<Frame name="Toggle / Active" w={40} h={40} bg="var:accent" rounded={6} flex="row" justify="center" items="center"><Icon name="lucide:bold" size={16} color="var:accent-foreground" /></Frame>` },
|
|
115
|
+
],
|
|
116
|
+
|
|
117
|
+
// ── Checkbox ────────────────────────────────────────────────────────
|
|
118
|
+
checkbox: () => [
|
|
119
|
+
{ name: 'Checkbox / Unchecked', jsx: `<Frame name="Checkbox / Unchecked" w={16} h={16} bg="var:background" stroke="var:primary" strokeWidth={1} rounded={4} />` },
|
|
120
|
+
{ name: 'Checkbox / Checked', jsx: `<Frame name="Checkbox / Checked" w={16} h={16} bg="var:primary" rounded={4} flex="row" justify="center" items="center"><Icon name="lucide:check" size={12} color="var:primary-foreground" /></Frame>` },
|
|
121
|
+
],
|
|
122
|
+
|
|
123
|
+
// ── Tabs ─────────────────────────────────────────────────────────────
|
|
124
|
+
tabs: () => [
|
|
125
|
+
{ name: 'Tabs', jsx: `<Frame name="Tabs" w={400} flex="col" gap={8}><Frame name="TabsList" w="fill" h={40} bg="var:muted" rounded={6} flex="row" p={4} gap={4}><Frame name="Tab Active" bg="var:background" rounded={4} flex="row" justify="center" items="center" grow={1} shadow="0px 1px 2px rgba(0,0,0,0.05)"><Text size={14} weight="medium" color="var:foreground">Account</Text></Frame><Frame name="Tab Inactive" rounded={4} flex="row" justify="center" items="center" grow={1}><Text size={14} color="var:muted-foreground">Password</Text></Frame></Frame><Frame name="TabContent" w="fill" bg="var:card" stroke="var:border" strokeWidth={1} rounded={8} p={24} flex="col" gap={16}><Frame flex="col" gap={4} w="fill"><Text size={18} weight="semibold" color="var:card-foreground" w="fill">Account</Text><Text size={14} color="var:muted-foreground" w="fill">Make changes to your account here.</Text></Frame><Frame flex="col" gap={8} w="fill"><Text size={14} weight="medium" color="var:foreground">Name</Text><Frame w="fill" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:foreground">Pedro Duarte</Text></Frame></Frame><Frame bg="var:primary" px={16} py={8} rounded={6} flex="row" justify="center" items="center"><Text size={14} weight="medium" color="var:primary-foreground">Save changes</Text></Frame></Frame></Frame>` },
|
|
126
|
+
],
|
|
127
|
+
|
|
128
|
+
// ── Table ────────────────────────────────────────────────────────────
|
|
129
|
+
table: () => [
|
|
130
|
+
{ name: 'Table', jsx: `<Frame name="Table" w={500} flex="col" bg="var:card" stroke="var:border" strokeWidth={1} rounded={8} overflow="hidden"><Frame name="Header" w="fill" flex="row" bg="var:muted" px={16} h={48} items="center"><Frame w={200} flex="row"><Text size={14} weight="medium" color="var:muted-foreground">Invoice</Text></Frame><Frame w={100} flex="row"><Text size={14} weight="medium" color="var:muted-foreground">Status</Text></Frame><Frame grow={1} flex="row" justify="end"><Text size={14} weight="medium" color="var:muted-foreground">Amount</Text></Frame></Frame><Frame name="Row 1" w="fill" flex="row" px={16} h={48} items="center" stroke="var:border" strokeWidth={1} strokeAlign="inside"><Frame w={200} flex="row"><Text size={14} weight="medium" color="var:card-foreground">INV001</Text></Frame><Frame w={100} flex="row"><Text size={14} color="var:card-foreground">Paid</Text></Frame><Frame grow={1} flex="row" justify="end"><Text size={14} color="var:card-foreground">$250.00</Text></Frame></Frame><Frame name="Row 2" w="fill" flex="row" px={16} h={48} items="center" stroke="var:border" strokeWidth={1} strokeAlign="inside"><Frame w={200} flex="row"><Text size={14} weight="medium" color="var:card-foreground">INV002</Text></Frame><Frame w={100} flex="row"><Text size={14} color="var:card-foreground">Pending</Text></Frame><Frame grow={1} flex="row" justify="end"><Text size={14} color="var:card-foreground">$150.00</Text></Frame></Frame><Frame name="Row 3" w="fill" flex="row" px={16} h={48} items="center"><Frame w={200} flex="row"><Text size={14} weight="medium" color="var:card-foreground">INV003</Text></Frame><Frame w={100} flex="row"><Text size={14} color="var:card-foreground">Unpaid</Text></Frame><Frame grow={1} flex="row" justify="end"><Text size={14} color="var:card-foreground">$350.00</Text></Frame></Frame></Frame>` },
|
|
131
|
+
],
|
|
132
|
+
|
|
133
|
+
// ── Radio Group ──────────────────────────────────────────────────────
|
|
134
|
+
'radio-group': () => [
|
|
135
|
+
{ name: 'Radio / Unchecked', jsx: `<Frame name="Radio / Unchecked" w={16} h={16} stroke="var:primary" strokeWidth={1} rounded={9999} />` },
|
|
136
|
+
{ name: 'Radio / Checked', jsx: `<Frame name="Radio / Checked" w={16} h={16} stroke="var:primary" strokeWidth={1} rounded={9999} flex="row" justify="center" items="center"><Frame w={8} h={8} bg="var:primary" rounded={9999} /></Frame>` },
|
|
137
|
+
{ name: 'Radio Group', jsx: `<Frame name="Radio Group" flex="col" gap={12}><Frame flex="row" gap={8} items="center"><Frame w={16} h={16} stroke="var:primary" strokeWidth={1} rounded={9999} flex="row" justify="center" items="center"><Frame w={8} h={8} bg="var:primary" rounded={9999} /></Frame><Text size={14} color="var:foreground">Default</Text></Frame><Frame flex="row" gap={8} items="center"><Frame w={16} h={16} stroke="var:primary" strokeWidth={1} rounded={9999} /><Text size={14} color="var:foreground">Comfortable</Text></Frame><Frame flex="row" gap={8} items="center"><Frame w={16} h={16} stroke="var:primary" strokeWidth={1} rounded={9999} /><Text size={14} color="var:foreground">Compact</Text></Frame></Frame>` },
|
|
138
|
+
],
|
|
139
|
+
|
|
140
|
+
// ── Select ──────────────────────────────────────────────────────────
|
|
141
|
+
select: () => [
|
|
142
|
+
{ name: 'Select / Closed', jsx: `<Frame name="Select / Closed" w={200} h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12} gap={8}><Text size={14} color="var:muted-foreground">Select...</Text><Frame grow={1} /><Icon name="lucide:chevron-down" size={14} color="var:muted-foreground" /></Frame>` },
|
|
143
|
+
{ name: 'Select / Filled', jsx: `<Frame name="Select / Filled" w={200} h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12} gap={8}><Text size={14} color="var:foreground">Option A</Text><Frame grow={1} /><Icon name="lucide:chevron-down" size={14} color="var:muted-foreground" /></Frame>` },
|
|
144
|
+
{ name: 'Select / Open', jsx: `<Frame name="Select / Open" w={200} flex="col" gap={4}><Frame w="fill" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12} gap={8}><Text size={14} color="var:foreground">Option A</Text><Frame grow={1} /><Icon name="lucide:chevron-up" size={14} color="var:muted-foreground" /></Frame><Frame w="fill" bg="var:card" stroke="var:border" strokeWidth={1} rounded={6} p={4} flex="col" shadow="0px 4px 12px rgba(0,0,0,0.1)"><Frame w="fill" h={32} bg="var:accent" rounded={4} flex="row" items="center" px={8} gap={8}><Icon name="lucide:check" size={14} color="var:accent-foreground" /><Text size={14} color="var:accent-foreground">Option A</Text></Frame><Frame w="fill" h={32} rounded={4} flex="row" items="center" px={8} pl={30}><Text size={14} color="var:card-foreground">Option B</Text></Frame><Frame w="fill" h={32} rounded={4} flex="row" items="center" px={8} pl={30}><Text size={14} color="var:card-foreground">Option C</Text></Frame></Frame></Frame>` },
|
|
145
|
+
],
|
|
146
|
+
|
|
147
|
+
// ── Slider ──────────────────────────────────────────────────────────
|
|
148
|
+
slider: () => [
|
|
149
|
+
{ name: 'Slider', jsx: `<Frame name="Slider" w={280} h={20} flex="row" items="center"><Frame w={168} h={8} bg="var:primary" roundedTL={9999} roundedBL={9999} /><Frame w={112} h={8} bg="var:secondary" roundedTR={9999} roundedBR={9999} /><Frame name="Thumb" w={20} h={20} bg="var:background" stroke="var:primary" strokeWidth={2} rounded={9999} position="absolute" x={158} /></Frame>` },
|
|
150
|
+
],
|
|
151
|
+
|
|
152
|
+
// ── Breadcrumb ──────────────────────────────────────────────────────
|
|
153
|
+
breadcrumb: () => [
|
|
154
|
+
{ name: 'Breadcrumb', jsx: `<Frame name="Breadcrumb" flex="row" gap={6} items="center"><Text size={14} color="var:muted-foreground">Home</Text><Icon name="lucide:chevron-right" size={14} color="var:muted-foreground" /><Text size={14} color="var:muted-foreground">Components</Text><Icon name="lucide:chevron-right" size={14} color="var:muted-foreground" /><Text size={14} weight="medium" color="var:foreground">Breadcrumb</Text></Frame>` },
|
|
155
|
+
],
|
|
156
|
+
|
|
157
|
+
// ── Pagination ──────────────────────────────────────────────────────
|
|
158
|
+
pagination: () => [
|
|
159
|
+
{ name: 'Pagination', jsx: `<Frame name="Pagination" flex="row" gap={4} items="center"><Frame w={40} h={40} rounded={6} stroke="var:input" strokeWidth={1} flex="row" justify="center" items="center"><Icon name="lucide:chevron-left" size={16} color="var:foreground" /></Frame><Frame w={40} h={40} bg="var:primary" rounded={6} flex="row" justify="center" items="center"><Text size={14} weight="medium" color="var:primary-foreground">1</Text></Frame><Frame w={40} h={40} rounded={6} flex="row" justify="center" items="center"><Text size={14} color="var:foreground">2</Text></Frame><Frame w={40} h={40} rounded={6} flex="row" justify="center" items="center"><Text size={14} color="var:foreground">3</Text></Frame><Frame w={40} h={40} rounded={6} flex="row" justify="center" items="center"><Icon name="lucide:ellipsis" size={16} color="var:muted-foreground" /></Frame><Frame w={40} h={40} rounded={6} stroke="var:input" strokeWidth={1} flex="row" justify="center" items="center"><Icon name="lucide:chevron-right" size={16} color="var:foreground" /></Frame></Frame>` },
|
|
160
|
+
],
|
|
161
|
+
|
|
162
|
+
// ── Kbd ─────────────────────────────────────────────────────────────
|
|
163
|
+
kbd: () => [
|
|
164
|
+
{ name: 'Kbd', jsx: `<Frame name="Kbd" flex="row" items="center" px={6} py={2} rounded={4} stroke="var:border" strokeWidth={1} bg="var:muted"><Text size={12} weight="medium" color="var:foreground">⌘K</Text></Frame>` },
|
|
165
|
+
{ name: 'Kbd / Large', jsx: `<Frame name="Kbd / Large" flex="row" items="center" gap={4}><Frame flex="row" items="center" px={6} py={2} rounded={4} stroke="var:border" strokeWidth={1} bg="var:muted"><Text size={12} weight="medium" color="var:foreground">⌘</Text></Frame><Frame flex="row" items="center" px={6} py={2} rounded={4} stroke="var:border" strokeWidth={1} bg="var:muted"><Text size={12} weight="medium" color="var:foreground">Shift</Text></Frame><Frame flex="row" items="center" px={6} py={2} rounded={4} stroke="var:border" strokeWidth={1} bg="var:muted"><Text size={12} weight="medium" color="var:foreground">P</Text></Frame></Frame>` },
|
|
166
|
+
],
|
|
167
|
+
|
|
168
|
+
// ── Spinner ─────────────────────────────────────────────────────────
|
|
169
|
+
spinner: () => [
|
|
170
|
+
{ name: 'Spinner / Small', jsx: `<Frame name="Spinner / Small" w={16} h={16} rounded={9999} stroke="var:primary" strokeWidth={2} />` },
|
|
171
|
+
{ name: 'Spinner / Medium', jsx: `<Frame name="Spinner / Medium" w={24} h={24} rounded={9999} stroke="var:primary" strokeWidth={2} />` },
|
|
172
|
+
],
|
|
173
|
+
|
|
174
|
+
// ── Tooltip ─────────────────────────────────────────────────────────
|
|
175
|
+
tooltip: () => [
|
|
176
|
+
{ name: 'Tooltip', jsx: `<Frame name="Tooltip" flex="col" items="center" gap={4}><Frame bg="var:primary" rounded={6} px={12} py={6} shadow="0px 4px 8px rgba(0,0,0,0.12)"><Text size={14} color="var:primary-foreground">Add to library</Text></Frame><Frame bg="var:primary" px={16} py={8} rounded={6} flex="row" justify="center" items="center"><Text size={14} weight="medium" color="var:primary-foreground">Hover me</Text></Frame></Frame>` },
|
|
177
|
+
],
|
|
178
|
+
|
|
179
|
+
// ── Dialog ──────────────────────────────────────────────────────────
|
|
180
|
+
dialog: () => [
|
|
181
|
+
{ name: 'Dialog', jsx: `<Frame name="Dialog" w={450} flex="col" bg="var:background" stroke="var:border" strokeWidth={1} rounded={8} p={24} gap={16} shadow="0px 8px 24px rgba(0,0,0,0.15)"><Frame flex="row" items="start" w="fill"><Frame flex="col" gap={6} w="fill"><Text size={18} weight="semibold" color="var:foreground" w="fill">Edit profile</Text><Text size={14} color="var:muted-foreground" w="fill">Make changes to your profile here. Click save when you are done.</Text></Frame><Frame w={24} h={24} rounded={4} flex="row" justify="center" items="center"><Icon name="lucide:x" size={16} color="var:muted-foreground" /></Frame></Frame><Frame flex="col" gap={12} w="fill"><Frame flex="col" gap={8} w="fill"><Text size={14} weight="medium" color="var:foreground">Name</Text><Frame w="fill" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:foreground">Pedro Duarte</Text></Frame></Frame><Frame flex="col" gap={8} w="fill"><Text size={14} weight="medium" color="var:foreground">Username</Text><Frame w="fill" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:foreground">@peduarte</Text></Frame></Frame></Frame><Frame flex="row" justify="end" w="fill"><Frame bg="var:primary" px={16} py={8} rounded={6} flex="row" justify="center" items="center"><Text size={14} weight="medium" color="var:primary-foreground">Save changes</Text></Frame></Frame></Frame>` },
|
|
182
|
+
],
|
|
183
|
+
|
|
184
|
+
// ── Dropdown Menu ───────────────────────────────────────────────────
|
|
185
|
+
'dropdown-menu': () => [
|
|
186
|
+
{ name: 'Dropdown Menu', jsx: `<Frame name="Dropdown Menu" w={200} bg="var:card" stroke="var:border" strokeWidth={1} rounded={6} p={4} flex="col" shadow="0px 4px 12px rgba(0,0,0,0.1)"><Frame w="fill" h={32} rounded={4} flex="row" items="center" px={8} bg="var:accent"><Text size={14} color="var:accent-foreground">Profile</Text></Frame><Frame w="fill" h={32} rounded={4} flex="row" items="center" px={8}><Text size={14} color="var:card-foreground">Billing</Text></Frame><Frame w="fill" h={32} rounded={4} flex="row" items="center" px={8}><Text size={14} color="var:card-foreground">Settings</Text></Frame><Frame w="fill" h={1} bg="var:border" /><Frame w="fill" h={32} rounded={4} flex="row" items="center" px={8}><Text size={14} color="var:card-foreground">Log out</Text></Frame></Frame>` },
|
|
187
|
+
],
|
|
188
|
+
|
|
189
|
+
// ── Accordion ───────────────────────────────────────────────────────
|
|
190
|
+
accordion: () => [
|
|
191
|
+
{ name: 'Accordion', jsx: `<Frame name="Accordion" w={400} flex="col" bg="var:background"><Frame name="Item Open" w="fill" flex="col" stroke="var:border" strokeWidth={1} strokeAlign="inside"><Frame w="fill" flex="row" items="center" px={0} py={16}><Text size={14} weight="medium" color="var:foreground" w="fill">Is it accessible?</Text><Icon name="lucide:chevron-down" size={16} color="var:muted-foreground" /></Frame><Frame w="fill" pb={16} flex="col"><Text size={14} color="var:muted-foreground" w="fill">Yes. It adheres to the WAI-ARIA design pattern.</Text></Frame></Frame><Frame name="Item Closed" w="fill" flex="row" items="center" py={16} stroke="var:border" strokeWidth={1} strokeAlign="inside"><Text size={14} weight="medium" color="var:foreground" w="fill">Is it styled?</Text><Icon name="lucide:chevron-right" size={16} color="var:muted-foreground" /></Frame><Frame name="Item Closed 2" w="fill" flex="row" items="center" py={16} stroke="var:border" strokeWidth={1} strokeAlign="inside"><Text size={14} weight="medium" color="var:foreground" w="fill">Is it animated?</Text><Icon name="lucide:chevron-right" size={16} color="var:muted-foreground" /></Frame></Frame>` },
|
|
192
|
+
],
|
|
193
|
+
|
|
194
|
+
// ── Navigation Menu ─────────────────────────────────────────────────
|
|
195
|
+
'navigation-menu': () => [
|
|
196
|
+
{ name: 'Navigation Menu', jsx: `<Frame name="Navigation Menu" flex="row" gap={4} items="center" bg="var:background" p={4} rounded={6}><Frame h={36} px={16} rounded={6} bg="var:accent" flex="row" items="center"><Text size={14} weight="medium" color="var:accent-foreground">Getting Started</Text></Frame><Frame h={36} px={16} rounded={6} flex="row" items="center" gap={4}><Text size={14} weight="medium" color="var:foreground">Components</Text><Icon name="lucide:chevron-down" size={12} color="var:muted-foreground" /></Frame><Frame h={36} px={16} rounded={6} flex="row" items="center"><Text size={14} weight="medium" color="var:foreground">Documentation</Text></Frame></Frame>` },
|
|
197
|
+
],
|
|
198
|
+
|
|
199
|
+
// ── Sheet ───────────────────────────────────────────────────────────
|
|
200
|
+
sheet: () => [
|
|
201
|
+
{ name: 'Sheet', jsx: `<Frame name="Sheet" w={380} h={500} bg="var:background" stroke="var:border" strokeWidth={1} p={24} flex="col" gap={16} shadow="0px 8px 24px rgba(0,0,0,0.15)"><Frame flex="row" items="start" w="fill"><Frame flex="col" gap={6} w="fill"><Text size={18} weight="semibold" color="var:foreground" w="fill">Edit profile</Text><Text size={14} color="var:muted-foreground" w="fill">Make changes to your profile here.</Text></Frame><Frame w={24} h={24} rounded={4} flex="row" justify="center" items="center"><Icon name="lucide:x" size={16} color="var:muted-foreground" /></Frame></Frame><Frame flex="col" gap={12} w="fill"><Frame flex="col" gap={8} w="fill"><Text size={14} weight="medium" color="var:foreground">Name</Text><Frame w="fill" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:foreground">Pedro Duarte</Text></Frame></Frame><Frame flex="col" gap={8} w="fill"><Text size={14} weight="medium" color="var:foreground">Username</Text><Frame w="fill" h={40} bg="var:background" stroke="var:input" strokeWidth={1} rounded={6} flex="row" items="center" px={12}><Text size={14} color="var:foreground">@peduarte</Text></Frame></Frame></Frame><Frame grow={1} /><Frame bg="var:primary" px={16} py={8} rounded={6} flex="row" justify="center" items="center"><Text size={14} weight="medium" color="var:primary-foreground">Save changes</Text></Frame></Frame>` },
|
|
202
|
+
],
|
|
203
|
+
|
|
204
|
+
// ── Hover Card ──────────────────────────────────────────────────────
|
|
205
|
+
'hover-card': () => [
|
|
206
|
+
{ name: 'Hover Card', jsx: `<Frame name="Hover Card" w={320} bg="var:card" stroke="var:border" strokeWidth={1} rounded={8} p={16} flex="row" gap={16} shadow="0px 4px 12px rgba(0,0,0,0.1)"><Frame w={40} h={40} bg="var:muted" rounded={9999} flex="row" justify="center" items="center"><Text size={16} weight="medium" color="var:muted-foreground">@</Text></Frame><Frame flex="col" gap={8} w="fill"><Frame flex="col" gap={2} w="fill"><Text size={14} weight="semibold" color="var:card-foreground" w="fill">@nextjs</Text><Text size={14} color="var:muted-foreground" w="fill">The React Framework, created and maintained by @vercel.</Text></Frame><Text size={12} color="var:muted-foreground">Joined December 2021</Text></Frame></Frame>` },
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const VISUAL_COMPONENTS = Object.keys(components);
|
|
211
|
+
|
|
212
|
+
const INTERACTIVE_ONLY = [
|
|
213
|
+
'context-menu', 'command', 'combobox',
|
|
214
|
+
'menubar', 'sidebar', 'collapsible', 'carousel',
|
|
215
|
+
'scroll-area', 'calendar', 'sonner', 'form', 'resizable',
|
|
216
|
+
'drawer', 'popover',
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
export function listComponents() {
|
|
220
|
+
return { available: VISUAL_COMPONENTS, interactive: INTERACTIVE_ONLY };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function getComponent(name) {
|
|
224
|
+
const fn = components[name];
|
|
225
|
+
if (!fn) return null;
|
|
226
|
+
return fn();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function getAllComponents() {
|
|
230
|
+
const all = [];
|
|
231
|
+
for (const name of VISUAL_COMPONENTS) {
|
|
232
|
+
all.push(...components[name]());
|
|
233
|
+
}
|
|
234
|
+
return all;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { VISUAL_COMPONENTS, INTERACTIVE_ONLY };
|