prev-cli 0.24.16 → 0.24.19
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/dist/cli.js +2 -1
- package/package.json +5 -2
- package/src/jsx/adapters/html.test.ts +191 -0
- package/src/jsx/adapters/html.ts +404 -0
- package/src/jsx/adapters/react.test.ts +172 -0
- package/src/jsx/adapters/react.tsx +346 -0
- package/src/jsx/define-component.ts +129 -0
- package/src/jsx/index.ts +47 -0
- package/src/jsx/jsx-runtime.test.ts +63 -0
- package/src/jsx/jsx-runtime.ts +117 -0
- package/src/jsx/migrate.test.ts +72 -0
- package/src/jsx/migrate.ts +451 -0
- package/src/jsx/schemas/index.ts +4 -0
- package/src/jsx/schemas/primitives.ts +107 -0
- package/src/jsx/schemas/tokens.ts +60 -0
- package/src/jsx/validation.ts +77 -0
- package/src/jsx/vnode.ts +159 -0
- package/src/primitives/index.ts +8 -0
- package/src/primitives/migrate.test.ts +317 -0
- package/src/primitives/migrate.ts +364 -0
- package/src/primitives/parser.test.ts +265 -0
- package/src/primitives/parser.ts +442 -0
- package/src/primitives/template-parser.test.ts +297 -0
- package/src/primitives/template-parser.ts +374 -0
- package/src/primitives/template-renderer.test.ts +359 -0
- package/src/primitives/template-renderer.ts +497 -0
- package/src/primitives/tokens.css +82 -0
- package/src/primitives/types.ts +248 -0
- package/src/tokens/defaults.test.ts +137 -0
- package/src/tokens/defaults.ts +77 -0
- package/src/tokens/defaults.yaml +76 -0
- package/src/tokens/resolver.test.ts +229 -0
- package/src/tokens/resolver.ts +173 -0
- package/src/tokens/utils.test.ts +172 -0
- package/src/tokens/utils.ts +104 -0
- package/src/tokens/validation.test.ts +118 -0
- package/src/tokens/validation.ts +226 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
// src/primitives/template-renderer.ts
|
|
2
|
+
// Renders templates to HTML using design tokens
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
isPrimitive,
|
|
6
|
+
isRef,
|
|
7
|
+
isContainerNode,
|
|
8
|
+
type TemplateNode,
|
|
9
|
+
type Template,
|
|
10
|
+
type Slots,
|
|
11
|
+
type SpacingToken,
|
|
12
|
+
type AlignToken,
|
|
13
|
+
type BackgroundToken,
|
|
14
|
+
type RadiusToken,
|
|
15
|
+
type ColorToken,
|
|
16
|
+
type SizeToken,
|
|
17
|
+
type WeightToken,
|
|
18
|
+
type FitToken,
|
|
19
|
+
} from './types'
|
|
20
|
+
import { parsePrimitive } from './parser'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Design token to Tailwind class mappings
|
|
24
|
+
* Using shadcn/ui-compatible naming conventions
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// Spacing token to Tailwind scale
|
|
28
|
+
const SPACING_GAP: Record<SpacingToken, string> = {
|
|
29
|
+
none: 'gap-0',
|
|
30
|
+
xs: 'gap-1',
|
|
31
|
+
sm: 'gap-2',
|
|
32
|
+
md: 'gap-4',
|
|
33
|
+
lg: 'gap-6',
|
|
34
|
+
xl: 'gap-8',
|
|
35
|
+
'2xl': 'gap-12',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SPACING_PADDING: Record<SpacingToken, string> = {
|
|
39
|
+
none: 'p-0',
|
|
40
|
+
xs: 'p-1',
|
|
41
|
+
sm: 'p-2',
|
|
42
|
+
md: 'p-4',
|
|
43
|
+
lg: 'p-6',
|
|
44
|
+
xl: 'p-8',
|
|
45
|
+
'2xl': 'p-12',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// For spacer fixed sizes (width/height)
|
|
49
|
+
const SPACING_SIZE: Record<SpacingToken, string> = {
|
|
50
|
+
none: 'w-0 h-0',
|
|
51
|
+
xs: 'w-1 h-1',
|
|
52
|
+
sm: 'w-2 h-2',
|
|
53
|
+
md: 'w-4 h-4',
|
|
54
|
+
lg: 'w-6 h-6',
|
|
55
|
+
xl: 'w-8 h-8',
|
|
56
|
+
'2xl': 'w-12 h-12',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const ALIGN_ITEMS: Record<AlignToken, string> = {
|
|
60
|
+
start: 'items-start',
|
|
61
|
+
center: 'items-center',
|
|
62
|
+
end: 'items-end',
|
|
63
|
+
stretch: 'items-stretch',
|
|
64
|
+
between: 'justify-between',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Background tokens - shadcn-compatible
|
|
68
|
+
const BG_CLASS: Record<BackgroundToken, string> = {
|
|
69
|
+
transparent: 'bg-transparent',
|
|
70
|
+
background: 'bg-background',
|
|
71
|
+
card: 'bg-card',
|
|
72
|
+
primary: 'bg-primary',
|
|
73
|
+
secondary: 'bg-secondary',
|
|
74
|
+
muted: 'bg-muted',
|
|
75
|
+
accent: 'bg-accent',
|
|
76
|
+
destructive: 'bg-destructive',
|
|
77
|
+
input: 'bg-input',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const RADIUS_CLASS: Record<RadiusToken, string> = {
|
|
81
|
+
none: 'rounded-none',
|
|
82
|
+
sm: 'rounded-sm',
|
|
83
|
+
md: 'rounded-md',
|
|
84
|
+
lg: 'rounded-lg',
|
|
85
|
+
xl: 'rounded-xl',
|
|
86
|
+
full: 'rounded-full',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Text color tokens - shadcn-compatible
|
|
90
|
+
const COLOR_CLASS: Record<ColorToken, string> = {
|
|
91
|
+
foreground: 'text-foreground',
|
|
92
|
+
'card-foreground': 'text-card-foreground',
|
|
93
|
+
primary: 'text-primary',
|
|
94
|
+
'primary-foreground': 'text-primary-foreground',
|
|
95
|
+
secondary: 'text-secondary',
|
|
96
|
+
'secondary-foreground': 'text-secondary-foreground',
|
|
97
|
+
muted: 'text-muted',
|
|
98
|
+
'muted-foreground': 'text-muted-foreground',
|
|
99
|
+
accent: 'text-accent',
|
|
100
|
+
'accent-foreground': 'text-accent-foreground',
|
|
101
|
+
destructive: 'text-destructive',
|
|
102
|
+
'destructive-foreground': 'text-destructive-foreground',
|
|
103
|
+
border: 'text-border',
|
|
104
|
+
ring: 'text-ring',
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const SIZE_CLASS: Record<SizeToken, string> = {
|
|
108
|
+
xs: 'text-xs',
|
|
109
|
+
sm: 'text-sm',
|
|
110
|
+
base: 'text-base',
|
|
111
|
+
lg: 'text-lg',
|
|
112
|
+
xl: 'text-xl',
|
|
113
|
+
'2xl': 'text-2xl',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Icon sizes use width/height
|
|
117
|
+
const ICON_SIZE_CLASS: Record<SizeToken, string> = {
|
|
118
|
+
xs: 'w-3 h-3',
|
|
119
|
+
sm: 'w-4 h-4',
|
|
120
|
+
base: 'w-5 h-5',
|
|
121
|
+
lg: 'w-6 h-6',
|
|
122
|
+
xl: 'w-7 h-7',
|
|
123
|
+
'2xl': 'w-8 h-8',
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const WEIGHT_CLASS: Record<WeightToken, string> = {
|
|
127
|
+
normal: 'font-normal',
|
|
128
|
+
medium: 'font-medium',
|
|
129
|
+
semibold: 'font-semibold',
|
|
130
|
+
bold: 'font-bold',
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const FIT_CLASS: Record<FitToken, string> = {
|
|
134
|
+
cover: 'object-cover',
|
|
135
|
+
contain: 'object-contain',
|
|
136
|
+
fill: 'object-fill',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface RenderContext {
|
|
140
|
+
/** Current state for slot resolution */
|
|
141
|
+
state?: string
|
|
142
|
+
/** Slots mapping for state-dependent content */
|
|
143
|
+
slots?: Slots
|
|
144
|
+
/** Props for component rendering */
|
|
145
|
+
props?: Record<string, unknown>
|
|
146
|
+
/** Callback to render component refs */
|
|
147
|
+
renderRef?: (ref: string) => string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Render a template to HTML
|
|
152
|
+
*/
|
|
153
|
+
export function renderTemplate(template: Template, context: RenderContext = {}): string {
|
|
154
|
+
return renderNode(template.root, 'root', context)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Render a single template node to HTML
|
|
159
|
+
*/
|
|
160
|
+
function renderNode(node: TemplateNode, nodeId: string, context: RenderContext): string {
|
|
161
|
+
// String node: primitive or ref
|
|
162
|
+
if (typeof node === 'string') {
|
|
163
|
+
return renderLeaf(node, nodeId, context)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Container node: has type and possibly children
|
|
167
|
+
if (isContainerNode(node)) {
|
|
168
|
+
return renderContainer(node, nodeId, context)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return `<!-- unknown node type -->`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Render a leaf node (primitive string or component ref)
|
|
176
|
+
*/
|
|
177
|
+
function renderLeaf(value: string, nodeId: string, context: RenderContext): string {
|
|
178
|
+
if (isPrimitive(value)) {
|
|
179
|
+
return renderPrimitive(value, nodeId, context)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (isRef(value)) {
|
|
183
|
+
// Delegate to ref renderer if provided
|
|
184
|
+
if (context.renderRef) {
|
|
185
|
+
return context.renderRef(value)
|
|
186
|
+
}
|
|
187
|
+
// Default: placeholder
|
|
188
|
+
return `<div data-ref="${value}" data-node-id="${nodeId}"><!-- ${value} --></div>`
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Treat as literal text
|
|
192
|
+
return escapeHtml(value)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Render a container node with children
|
|
197
|
+
*/
|
|
198
|
+
function renderContainer(
|
|
199
|
+
node: { type: string; children?: Record<string, TemplateNode> },
|
|
200
|
+
nodeId: string,
|
|
201
|
+
context: RenderContext
|
|
202
|
+
): string {
|
|
203
|
+
const parseResult = parsePrimitive(node.type)
|
|
204
|
+
if (!parseResult.success) {
|
|
205
|
+
return `<!-- invalid primitive: ${node.type} -->`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const primitive = parseResult.primitive
|
|
209
|
+
let childrenHtml = ''
|
|
210
|
+
|
|
211
|
+
if (node.children) {
|
|
212
|
+
childrenHtml = Object.entries(node.children)
|
|
213
|
+
.map(([childId, childNode]) => renderNode(childNode, childId, context))
|
|
214
|
+
.join('\n')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
switch (primitive.type) {
|
|
218
|
+
case '$col':
|
|
219
|
+
return renderCol(primitive, nodeId, childrenHtml)
|
|
220
|
+
case '$row':
|
|
221
|
+
return renderRow(primitive, nodeId, childrenHtml)
|
|
222
|
+
case '$box':
|
|
223
|
+
return renderBox(primitive, nodeId, childrenHtml)
|
|
224
|
+
default:
|
|
225
|
+
return `<div data-primitive="${primitive.type}" data-node-id="${nodeId}">${childrenHtml}</div>`
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Render a primitive string (without children)
|
|
231
|
+
*/
|
|
232
|
+
function renderPrimitive(value: string, nodeId: string, context: RenderContext): string {
|
|
233
|
+
const parseResult = parsePrimitive(value)
|
|
234
|
+
if (!parseResult.success) {
|
|
235
|
+
return `<!-- invalid primitive: ${value} -->`
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const primitive = parseResult.primitive
|
|
239
|
+
|
|
240
|
+
switch (primitive.type) {
|
|
241
|
+
case '$col':
|
|
242
|
+
return renderCol(primitive, nodeId, '')
|
|
243
|
+
case '$row':
|
|
244
|
+
return renderRow(primitive, nodeId, '')
|
|
245
|
+
case '$box':
|
|
246
|
+
return renderBox(primitive, nodeId, '')
|
|
247
|
+
case '$spacer':
|
|
248
|
+
return renderSpacer(primitive, nodeId)
|
|
249
|
+
case '$slot':
|
|
250
|
+
return renderSlot(primitive, nodeId, context)
|
|
251
|
+
case '$text':
|
|
252
|
+
return renderText(primitive, nodeId, context)
|
|
253
|
+
case '$icon':
|
|
254
|
+
return renderIcon(primitive, nodeId, context)
|
|
255
|
+
case '$image':
|
|
256
|
+
return renderImage(primitive, nodeId, context)
|
|
257
|
+
default:
|
|
258
|
+
// Exhaustive check - should never reach here
|
|
259
|
+
return `<!-- unknown primitive: ${value} -->`
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Primitive renderers
|
|
264
|
+
|
|
265
|
+
function renderCol(
|
|
266
|
+
primitive: { gap?: SpacingToken; align?: AlignToken; padding?: SpacingToken },
|
|
267
|
+
nodeId: string,
|
|
268
|
+
children: string
|
|
269
|
+
): string {
|
|
270
|
+
const classes: string[] = ['flex', 'flex-col']
|
|
271
|
+
if (primitive.gap) classes.push(SPACING_GAP[primitive.gap])
|
|
272
|
+
if (primitive.align) classes.push(ALIGN_ITEMS[primitive.align])
|
|
273
|
+
if (primitive.padding) classes.push(SPACING_PADDING[primitive.padding])
|
|
274
|
+
|
|
275
|
+
return `<div data-primitive="$col" data-node-id="${nodeId}" class="${classes.join(' ')}">${children}</div>`
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function renderRow(
|
|
279
|
+
primitive: { gap?: SpacingToken; align?: AlignToken; padding?: SpacingToken },
|
|
280
|
+
nodeId: string,
|
|
281
|
+
children: string
|
|
282
|
+
): string {
|
|
283
|
+
const classes: string[] = ['flex', 'flex-row']
|
|
284
|
+
if (primitive.gap) classes.push(SPACING_GAP[primitive.gap])
|
|
285
|
+
// $row defaults to items-center per spec
|
|
286
|
+
if (primitive.align) {
|
|
287
|
+
classes.push(ALIGN_ITEMS[primitive.align])
|
|
288
|
+
} else {
|
|
289
|
+
classes.push('items-center')
|
|
290
|
+
}
|
|
291
|
+
if (primitive.padding) classes.push(SPACING_PADDING[primitive.padding])
|
|
292
|
+
|
|
293
|
+
return `<div data-primitive="$row" data-node-id="${nodeId}" class="${classes.join(' ')}">${children}</div>`
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function renderBox(
|
|
297
|
+
primitive: { padding?: SpacingToken; bg?: BackgroundToken; radius?: RadiusToken },
|
|
298
|
+
nodeId: string,
|
|
299
|
+
children: string
|
|
300
|
+
): string {
|
|
301
|
+
const classes: string[] = []
|
|
302
|
+
if (primitive.padding) classes.push(SPACING_PADDING[primitive.padding])
|
|
303
|
+
if (primitive.bg) classes.push(BG_CLASS[primitive.bg])
|
|
304
|
+
if (primitive.radius) classes.push(RADIUS_CLASS[primitive.radius])
|
|
305
|
+
|
|
306
|
+
return `<div data-primitive="$box" data-node-id="${nodeId}" class="${classes.join(' ')}">${children}</div>`
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function renderSpacer(
|
|
310
|
+
primitive: { size?: SpacingToken },
|
|
311
|
+
nodeId: string
|
|
312
|
+
): string {
|
|
313
|
+
if (primitive.size) {
|
|
314
|
+
// Fixed size spacer
|
|
315
|
+
return `<div data-primitive="$spacer" data-node-id="${nodeId}" class="${SPACING_SIZE[primitive.size]} shrink-0"></div>`
|
|
316
|
+
}
|
|
317
|
+
// Flex spacer
|
|
318
|
+
return `<div data-primitive="$spacer" data-node-id="${nodeId}" class="flex-1"></div>`
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function renderSlot(
|
|
322
|
+
primitive: { name: string },
|
|
323
|
+
nodeId: string,
|
|
324
|
+
context: RenderContext
|
|
325
|
+
): string {
|
|
326
|
+
const slotName = primitive.name
|
|
327
|
+
const state = context.state || 'default'
|
|
328
|
+
|
|
329
|
+
// Look up slot content
|
|
330
|
+
if (context.slots && context.slots[slotName]) {
|
|
331
|
+
const stateMapping = context.slots[slotName]
|
|
332
|
+
const content = stateMapping[state] || stateMapping.default
|
|
333
|
+
|
|
334
|
+
if (content) {
|
|
335
|
+
// Render the content (ref or primitive)
|
|
336
|
+
return renderLeaf(content, `${nodeId}-content`, context)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// No slot content found
|
|
341
|
+
return `<div data-primitive="$slot" data-slot-name="${slotName}" data-node-id="${nodeId}"><!-- slot: ${slotName} --></div>`
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function renderText(
|
|
345
|
+
primitive: { content: string; size?: SizeToken; weight?: WeightToken; color?: ColorToken },
|
|
346
|
+
nodeId: string,
|
|
347
|
+
context: RenderContext
|
|
348
|
+
): string {
|
|
349
|
+
const classes: string[] = []
|
|
350
|
+
if (primitive.size) classes.push(SIZE_CLASS[primitive.size])
|
|
351
|
+
if (primitive.weight) classes.push(WEIGHT_CLASS[primitive.weight])
|
|
352
|
+
if (primitive.color) classes.push(COLOR_CLASS[primitive.color])
|
|
353
|
+
|
|
354
|
+
// Resolve content (prop reference or literal)
|
|
355
|
+
let content = primitive.content
|
|
356
|
+
if (content.startsWith('"') || content.startsWith("'")) {
|
|
357
|
+
// Quoted literal - strip quotes
|
|
358
|
+
content = content.slice(1, -1)
|
|
359
|
+
} else if (context.props && content in context.props) {
|
|
360
|
+
// Prop reference
|
|
361
|
+
content = String(context.props[content] ?? '')
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return `<span data-primitive="$text" data-node-id="${nodeId}" class="${classes.join(' ')}">${escapeHtml(content)}</span>`
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function renderIcon(
|
|
368
|
+
primitive: { name: string; size?: SizeToken; color?: ColorToken },
|
|
369
|
+
nodeId: string,
|
|
370
|
+
context: RenderContext
|
|
371
|
+
): string {
|
|
372
|
+
const classes: string[] = ['inline-flex', 'items-center', 'justify-center']
|
|
373
|
+
if (primitive.size) classes.push(ICON_SIZE_CLASS[primitive.size])
|
|
374
|
+
if (primitive.color) classes.push(COLOR_CLASS[primitive.color])
|
|
375
|
+
|
|
376
|
+
// Resolve icon name
|
|
377
|
+
let iconName = primitive.name
|
|
378
|
+
if (iconName.startsWith('"') || iconName.startsWith("'")) {
|
|
379
|
+
// Quoted literal - strip quotes
|
|
380
|
+
iconName = iconName.slice(1, -1)
|
|
381
|
+
} else if (context.props && iconName in context.props) {
|
|
382
|
+
// Prop reference
|
|
383
|
+
iconName = String(context.props[iconName] ?? '')
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return `<span data-primitive="$icon" data-icon="${escapeHtml(iconName)}" data-node-id="${nodeId}" class="${classes.join(' ')}"><!-- icon: ${escapeHtml(iconName)} --></span>`
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function renderImage(
|
|
390
|
+
primitive: { src: string; alt?: string; fit?: FitToken },
|
|
391
|
+
nodeId: string,
|
|
392
|
+
context: RenderContext
|
|
393
|
+
): string {
|
|
394
|
+
const classes: string[] = ['max-w-full']
|
|
395
|
+
if (primitive.fit) classes.push(FIT_CLASS[primitive.fit])
|
|
396
|
+
|
|
397
|
+
// Resolve src
|
|
398
|
+
let src = primitive.src
|
|
399
|
+
if (src.startsWith('"') || src.startsWith("'")) {
|
|
400
|
+
// Quoted literal - strip quotes
|
|
401
|
+
src = src.slice(1, -1)
|
|
402
|
+
} else if (context.props && src in context.props) {
|
|
403
|
+
// Prop reference
|
|
404
|
+
src = String(context.props[src] ?? '')
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const alt = primitive.alt ?? ''
|
|
408
|
+
|
|
409
|
+
return `<img data-primitive="$image" data-node-id="${nodeId}" src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="${classes.join(' ')}" />`
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Escape HTML special characters
|
|
414
|
+
*/
|
|
415
|
+
function escapeHtml(text: string): string {
|
|
416
|
+
return text
|
|
417
|
+
.replace(/&/g, '&')
|
|
418
|
+
.replace(/</g, '<')
|
|
419
|
+
.replace(/>/g, '>')
|
|
420
|
+
.replace(/"/g, '"')
|
|
421
|
+
.replace(/'/g, ''')
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Generate CSS custom properties for design tokens (shadcn-compatible)
|
|
426
|
+
*/
|
|
427
|
+
export function generateTokenCSS(): string {
|
|
428
|
+
return `
|
|
429
|
+
@tailwind base;
|
|
430
|
+
@tailwind components;
|
|
431
|
+
@tailwind utilities;
|
|
432
|
+
|
|
433
|
+
@layer base {
|
|
434
|
+
:root {
|
|
435
|
+
--background: 0 0% 100%;
|
|
436
|
+
--foreground: 222.2 84% 4.9%;
|
|
437
|
+
|
|
438
|
+
--card: 0 0% 100%;
|
|
439
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
440
|
+
|
|
441
|
+
--popover: 0 0% 100%;
|
|
442
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
443
|
+
|
|
444
|
+
--primary: 221.2 83.2% 53.3%;
|
|
445
|
+
--primary-foreground: 210 40% 98%;
|
|
446
|
+
|
|
447
|
+
--secondary: 210 40% 96.1%;
|
|
448
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
449
|
+
|
|
450
|
+
--muted: 210 40% 96.1%;
|
|
451
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
452
|
+
|
|
453
|
+
--accent: 210 40% 96.1%;
|
|
454
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
455
|
+
|
|
456
|
+
--destructive: 0 84.2% 60.2%;
|
|
457
|
+
--destructive-foreground: 210 40% 98%;
|
|
458
|
+
|
|
459
|
+
--border: 214.3 31.8% 91.4%;
|
|
460
|
+
--input: 214.3 31.8% 91.4%;
|
|
461
|
+
--ring: 221.2 83.2% 53.3%;
|
|
462
|
+
|
|
463
|
+
--radius: 0.5rem;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.dark {
|
|
467
|
+
--background: 222.2 84% 4.9%;
|
|
468
|
+
--foreground: 210 40% 98%;
|
|
469
|
+
|
|
470
|
+
--card: 222.2 84% 4.9%;
|
|
471
|
+
--card-foreground: 210 40% 98%;
|
|
472
|
+
|
|
473
|
+
--popover: 222.2 84% 4.9%;
|
|
474
|
+
--popover-foreground: 210 40% 98%;
|
|
475
|
+
|
|
476
|
+
--primary: 217.2 91.2% 59.8%;
|
|
477
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
478
|
+
|
|
479
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
480
|
+
--secondary-foreground: 210 40% 98%;
|
|
481
|
+
|
|
482
|
+
--muted: 217.2 32.6% 17.5%;
|
|
483
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
484
|
+
|
|
485
|
+
--accent: 217.2 32.6% 17.5%;
|
|
486
|
+
--accent-foreground: 210 40% 98%;
|
|
487
|
+
|
|
488
|
+
--destructive: 0 62.8% 30.6%;
|
|
489
|
+
--destructive-foreground: 210 40% 98%;
|
|
490
|
+
|
|
491
|
+
--border: 217.2 32.6% 17.5%;
|
|
492
|
+
--input: 217.2 32.6% 17.5%;
|
|
493
|
+
--ring: 224.3 76.3% 48%;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
`.trim()
|
|
497
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Design Tokens - shadcn/ui compatible
|
|
3
|
+
* These CSS variables enable Tailwind utilities like bg-background, text-foreground, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@tailwind base;
|
|
7
|
+
@tailwind components;
|
|
8
|
+
@tailwind utilities;
|
|
9
|
+
|
|
10
|
+
@layer base {
|
|
11
|
+
:root {
|
|
12
|
+
--background: 0 0% 100%;
|
|
13
|
+
--foreground: 222.2 84% 4.9%;
|
|
14
|
+
|
|
15
|
+
--card: 0 0% 100%;
|
|
16
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
17
|
+
|
|
18
|
+
--popover: 0 0% 100%;
|
|
19
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
20
|
+
|
|
21
|
+
--primary: 221.2 83.2% 53.3%;
|
|
22
|
+
--primary-foreground: 210 40% 98%;
|
|
23
|
+
|
|
24
|
+
--secondary: 210 40% 96.1%;
|
|
25
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
26
|
+
|
|
27
|
+
--muted: 210 40% 96.1%;
|
|
28
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
29
|
+
|
|
30
|
+
--accent: 210 40% 96.1%;
|
|
31
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
32
|
+
|
|
33
|
+
--destructive: 0 84.2% 60.2%;
|
|
34
|
+
--destructive-foreground: 210 40% 98%;
|
|
35
|
+
|
|
36
|
+
--border: 214.3 31.8% 91.4%;
|
|
37
|
+
--input: 214.3 31.8% 91.4%;
|
|
38
|
+
--ring: 221.2 83.2% 53.3%;
|
|
39
|
+
|
|
40
|
+
--radius: 0.5rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dark {
|
|
44
|
+
--background: 222.2 84% 4.9%;
|
|
45
|
+
--foreground: 210 40% 98%;
|
|
46
|
+
|
|
47
|
+
--card: 222.2 84% 4.9%;
|
|
48
|
+
--card-foreground: 210 40% 98%;
|
|
49
|
+
|
|
50
|
+
--popover: 222.2 84% 4.9%;
|
|
51
|
+
--popover-foreground: 210 40% 98%;
|
|
52
|
+
|
|
53
|
+
--primary: 217.2 91.2% 59.8%;
|
|
54
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
55
|
+
|
|
56
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
57
|
+
--secondary-foreground: 210 40% 98%;
|
|
58
|
+
|
|
59
|
+
--muted: 217.2 32.6% 17.5%;
|
|
60
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
61
|
+
|
|
62
|
+
--accent: 217.2 32.6% 17.5%;
|
|
63
|
+
--accent-foreground: 210 40% 98%;
|
|
64
|
+
|
|
65
|
+
--destructive: 0 62.8% 30.6%;
|
|
66
|
+
--destructive-foreground: 210 40% 98%;
|
|
67
|
+
|
|
68
|
+
--border: 217.2 32.6% 17.5%;
|
|
69
|
+
--input: 217.2 32.6% 17.5%;
|
|
70
|
+
--ring: 224.3 76.3% 48%;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Base styles */
|
|
75
|
+
@layer base {
|
|
76
|
+
* {
|
|
77
|
+
@apply border-border;
|
|
78
|
+
}
|
|
79
|
+
body {
|
|
80
|
+
@apply bg-background text-foreground;
|
|
81
|
+
}
|
|
82
|
+
}
|