@tamagui/cli 3.0.0-beta.1093.1 → 3.0.0-beta.1097.1
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.cjs +23 -4
- package/dist/generate-prompt.cjs +328 -186
- package/dist/migrate.cjs +11 -1
- package/dist/setup-prompt.cjs +73 -14
- package/dist/upgrade.cjs +29 -21
- package/package.json +9 -9
- package/src/cli.ts +18 -1
- package/src/generate-prompt.ts +426 -313
- package/src/migrate.ts +11 -1
- package/src/setup-prompt.ts +68 -15
- package/src/upgrade.ts +30 -33
- package/types/generate-prompt.d.ts +6 -2
- package/types/generate-prompt.d.ts.map +1 -1
- package/types/setup-prompt.d.ts +4 -2
- package/types/setup-prompt.d.ts.map +1 -1
- package/types/upgrade.d.ts.map +1 -1
package/src/generate-prompt.ts
CHANGED
|
@@ -2,22 +2,20 @@ import { join } from 'node:path'
|
|
|
2
2
|
import * as FS from 'fs-extra'
|
|
3
3
|
import type { CLIResolvedOptions } from '@tamagui/types'
|
|
4
4
|
|
|
5
|
-
interface GeneratePromptOptions extends CLIResolvedOptions {
|
|
5
|
+
export interface GeneratePromptOptions extends CLIResolvedOptions {
|
|
6
6
|
output?: string
|
|
7
|
+
styleValueSyntax?: 'string' | 'object' | 'both'
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export async function generatePrompt(options: GeneratePromptOptions) {
|
|
10
11
|
const { paths, output } = options
|
|
11
12
|
|
|
12
|
-
//
|
|
13
|
+
// regenerate the config first
|
|
13
14
|
const { loadTamagui } = require('@tamagui/static/loadTamagui')
|
|
14
15
|
process.env.TAMAGUI_KEEP_THEMES = '1'
|
|
15
|
-
await loadTamagui({
|
|
16
|
-
...options.tamaguiOptions,
|
|
17
|
-
platform: 'web',
|
|
18
|
-
})
|
|
16
|
+
await loadTamagui({ ...options.tamaguiOptions, platform: 'web' }, true)
|
|
19
17
|
|
|
20
|
-
//
|
|
18
|
+
// read the generated config
|
|
21
19
|
const configPath = join(paths.dotDir, 'tamagui.config.json')
|
|
22
20
|
|
|
23
21
|
if (!FS.existsSync(configPath)) {
|
|
@@ -28,339 +26,257 @@ export async function generatePrompt(options: GeneratePromptOptions) {
|
|
|
28
26
|
|
|
29
27
|
const config = await FS.readJSON(configPath)
|
|
30
28
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
29
|
+
// resolve styleValueSyntax: options -> env -> config setting -> interactive prompt if tty / default 'both'
|
|
30
|
+
const configSetting = config.tamaguiConfig?.settings?.styleValueSyntax
|
|
31
|
+
const explicitChoice =
|
|
32
|
+
options.styleValueSyntax ||
|
|
33
|
+
(process.env.TAMAGUI_STYLE_VALUE_SYNTAX as any) ||
|
|
34
|
+
configSetting
|
|
35
|
+
const { resolveStyleValueSyntax } = require('./setup-prompt')
|
|
36
|
+
const resolvedSyntax = await resolveStyleValueSyntax(explicitChoice)
|
|
37
|
+
|
|
38
|
+
// generate markdown
|
|
39
|
+
const markdown = generateMarkdown(config, { styleValueSyntax: resolvedSyntax })
|
|
33
40
|
|
|
34
|
-
//
|
|
41
|
+
// write to file
|
|
35
42
|
const outputPath = output || join(process.cwd(), 'tamagui-prompt.md')
|
|
36
43
|
await FS.writeFile(outputPath, markdown, 'utf-8')
|
|
37
44
|
|
|
38
45
|
console.info(`\n ✓ Generated prompt file at ${outputPath}\n`)
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
export interface GenerateMarkdownOptions {
|
|
49
|
+
styleValueSyntax?: 'string' | 'object' | 'both'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function generateMarkdown(config: any, options?: GenerateMarkdownOptions): string {
|
|
42
53
|
const sections: string[] = []
|
|
43
54
|
|
|
44
|
-
//
|
|
55
|
+
// header
|
|
45
56
|
sections.push('# Tamagui Configuration\n\n')
|
|
46
57
|
sections.push(
|
|
47
58
|
'This document provides an overview of the Tamagui configuration for this project.\n\n'
|
|
48
59
|
)
|
|
49
60
|
|
|
50
|
-
//
|
|
61
|
+
// get shorthands for use throughout the document
|
|
51
62
|
const shorthands = config.tamaguiConfig?.shorthands || {}
|
|
52
63
|
const reverseShorthands: Record<string, string> = {}
|
|
53
64
|
for (const [short, full] of Object.entries(shorthands)) {
|
|
54
65
|
reverseShorthands[full as string] = short
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
//
|
|
68
|
+
// helper function to get the correct property name based on settings
|
|
58
69
|
const getPropName = (fullProp: string): string => {
|
|
59
70
|
const settings = config.tamaguiConfig?.settings || {}
|
|
60
|
-
if (settings.onlyAllowShorthands
|
|
61
|
-
return reverseShorthands[fullProp]
|
|
71
|
+
if (settings.onlyAllowShorthands) {
|
|
72
|
+
if (reverseShorthands[fullProp]) return reverseShorthands[fullProp]
|
|
73
|
+
if (fullProp === 'backgroundColor' && reverseShorthands['background']) {
|
|
74
|
+
return reverseShorthands['background']
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
return fullProp
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
// Settings (moved to top)
|
|
67
80
|
const settings = config.tamaguiConfig?.settings || {}
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
const syntaxChoice = options?.styleValueSyntax || settings.styleValueSyntax || 'both'
|
|
82
|
+
|
|
83
|
+
// configuration settings
|
|
84
|
+
sections.push('## Configuration Settings\n\n')
|
|
85
|
+
sections.push(
|
|
86
|
+
'**IMPORTANT:** These settings affect how you write Tamagui code in this project.\n\n'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if (settings.defaultFont) {
|
|
90
|
+
sections.push(`### Default Font: \`${settings.defaultFont}\`\n\n`)
|
|
70
91
|
sections.push(
|
|
71
|
-
|
|
92
|
+
`All text components will use the "${settings.defaultFont}" font family by default.\n\n`
|
|
72
93
|
)
|
|
94
|
+
}
|
|
73
95
|
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
if (settings.onlyAllowShorthands !== undefined) {
|
|
97
|
+
sections.push(`### Only Allow Shorthands: \`${settings.onlyAllowShorthands}\`\n\n`)
|
|
98
|
+
if (settings.onlyAllowShorthands) {
|
|
99
|
+
sections.push('**You MUST use shorthand properties in this project.**\n\n')
|
|
100
|
+
sections.push('Full property names are not allowed. For example:\n')
|
|
101
|
+
sections.push('- ✅ `<View w="10" />` (correct)\n')
|
|
102
|
+
sections.push('- ❌ `<View width="10" />` (will error)\n\n')
|
|
76
103
|
sections.push(
|
|
77
|
-
|
|
104
|
+
'See the Shorthand Properties section below for all available shorthands.\n\n'
|
|
78
105
|
)
|
|
106
|
+
} else {
|
|
107
|
+
sections.push('You can use either shorthand or full property names.\n\n')
|
|
79
108
|
}
|
|
80
|
-
|
|
81
|
-
if (settings.onlyAllowShorthands !== undefined) {
|
|
82
|
-
sections.push(`### Only Allow Shorthands: \`${settings.onlyAllowShorthands}\`\n\n`)
|
|
83
|
-
if (settings.onlyAllowShorthands) {
|
|
84
|
-
sections.push('**You MUST use shorthand properties in this project.**\n\n')
|
|
85
|
-
sections.push('Full property names are not allowed. For example:\n')
|
|
86
|
-
sections.push('- ✅ `<View w="10" />` (correct)\n')
|
|
87
|
-
sections.push('- ❌ `<View width="10" />` (will error)\n\n')
|
|
88
|
-
sections.push(
|
|
89
|
-
'See the Shorthand Properties section below for all available shorthands.\n\n'
|
|
90
|
-
)
|
|
91
|
-
} else {
|
|
92
|
-
sections.push('You can use either shorthand or full property names.\n\n')
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (settings.addThemeClassName !== undefined) {
|
|
97
|
-
sections.push(`### Theme Class Name: \`${settings.addThemeClassName}\`\n\n`)
|
|
98
|
-
if (settings.addThemeClassName === 'html') {
|
|
99
|
-
sections.push('Theme classes are applied to the root HTML element.\n\n')
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Check for platform-specific settings
|
|
104
|
-
const platform = settings.platform || settings.defaultProps?.platform
|
|
105
|
-
if (platform) {
|
|
106
|
-
sections.push(`### Platform Mode: \`${platform}\`\n\n`)
|
|
107
|
-
|
|
108
|
-
if (platform === 'web') {
|
|
109
|
-
sections.push('This project is configured for **web only**.\n\n')
|
|
110
|
-
} else if (platform === 'native') {
|
|
111
|
-
sections.push('This project is configured for **React Native only**.\n\n')
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Check for strictness settings (common patterns)
|
|
116
|
-
const configString = JSON.stringify(config.tamaguiConfig)
|
|
117
|
-
if (configString.includes('semi-strict-web')) {
|
|
118
|
-
sections.push('### Mode: `semi-strict-web`\n\n')
|
|
119
|
-
sections.push('This configuration uses semi-strict-web mode, which:\n')
|
|
120
|
-
sections.push('- Optimizes for web performance\n')
|
|
121
|
-
sections.push('- May have limited React Native API support\n')
|
|
122
|
-
sections.push('- Focuses on web-first development\n\n')
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Store components section for later (will be output at the end)
|
|
127
|
-
const componentsSection: string[] = []
|
|
128
|
-
|
|
129
|
-
const allComponents: string[] = []
|
|
130
|
-
for (const componentModule of config.components) {
|
|
131
|
-
const componentNames = Object.keys(componentModule.nameToInfo)
|
|
132
|
-
allComponents.push(...componentNames)
|
|
133
109
|
}
|
|
134
110
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// Sort components to process shorter names first (potential base components)
|
|
141
|
-
const sortedComponents = [...allComponents].sort((a, b) => a.length - b.length)
|
|
142
|
-
|
|
143
|
-
for (const name of sortedComponents) {
|
|
144
|
-
if (processed.has(name)) continue
|
|
145
|
-
|
|
146
|
-
// Check if other components start with this name followed by an uppercase letter
|
|
147
|
-
const children = allComponents.filter(
|
|
148
|
-
(other) =>
|
|
149
|
-
other !== name && other.startsWith(name) && other[name.length]?.match(/[A-Z]/)
|
|
111
|
+
// style value syntax section
|
|
112
|
+
if (syntaxChoice === 'string') {
|
|
113
|
+
sections.push('### Style value syntax: `string`\n\n')
|
|
114
|
+
sections.push(
|
|
115
|
+
'Only the string form is allowed in this project (e.g. `bg="red hover:blue"`).\n\n'
|
|
150
116
|
)
|
|
117
|
+
} else if (syntaxChoice === 'object') {
|
|
118
|
+
sections.push('### Style value syntax: `object`\n\n')
|
|
119
|
+
sections.push(
|
|
120
|
+
"Only the object form is allowed in this project (e.g. `bg={{ default: 'red', hover: 'blue' }}`).\n\n"
|
|
121
|
+
)
|
|
122
|
+
} else {
|
|
123
|
+
sections.push('### Style value syntax\n\n')
|
|
124
|
+
sections.push('Both string and object style value syntax are allowed.\n\n')
|
|
125
|
+
}
|
|
151
126
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
children.forEach((child) => processed.add(child))
|
|
127
|
+
if (settings.addThemeClassName !== undefined) {
|
|
128
|
+
sections.push(`### Theme Class Name: \`${settings.addThemeClassName}\`\n\n`)
|
|
129
|
+
if (settings.addThemeClassName === 'html') {
|
|
130
|
+
sections.push('Theme classes are applied to the root HTML element.\n\n')
|
|
157
131
|
}
|
|
158
132
|
}
|
|
159
133
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
134
|
+
if (settings.allowedStyleValues) {
|
|
135
|
+
sections.push('### Allowed Style Values\n\n')
|
|
136
|
+
sections.push(
|
|
137
|
+
`Type validation: \`${JSON.stringify(settings.allowedStyleValues)}\`.\n\n`
|
|
138
|
+
)
|
|
139
|
+
sections.push(
|
|
140
|
+
'Single-token values are type-checked. Run `tamagui check --strict` to also validate conditional payloads.\n\n'
|
|
141
|
+
)
|
|
142
|
+
}
|
|
165
143
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
144
|
+
// flat value grammar section
|
|
145
|
+
sections.push('## Flat Value Grammar\n\n')
|
|
146
|
+
sections.push('Conditional style values follow this grammar:\n\n')
|
|
147
|
+
sections.push('```txt\n')
|
|
148
|
+
sections.push('value := base? clause*\n')
|
|
149
|
+
sections.push('clause := modifier(:modifier)*:payload\n')
|
|
150
|
+
sections.push('```\n\n')
|
|
151
|
+
sections.push(
|
|
152
|
+
'- No `$` sigils: bare token names (`bg="background"`, not `bg="$background"`).\n'
|
|
153
|
+
)
|
|
154
|
+
sections.push(
|
|
155
|
+
'- Kebab-case theme names: `background-hover`, `border-color`, `shadow-color`.\n'
|
|
156
|
+
)
|
|
157
|
+
sections.push('- Numbers are px: `p={4}` is 4px, while `p="4"` is space token 4.\n')
|
|
158
|
+
sections.push(
|
|
159
|
+
'- Specificity precedence: platform (`ios:` > `native:` > bare) > condition count > category (media < container < theme < group < state).\n\n'
|
|
160
|
+
)
|
|
171
161
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
componentsSection.push(`- ${name}\n`)
|
|
162
|
+
const bgProp = getPropName('background')
|
|
163
|
+
const pProp = getPropName('padding')
|
|
175
164
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
165
|
+
if (syntaxChoice === 'string') {
|
|
166
|
+
sections.push('**String form:**\n\n')
|
|
167
|
+
sections.push('```tsx\n')
|
|
168
|
+
sections.push(
|
|
169
|
+
`<View ${bgProp}="background hover:background-hover dark:blue-500" ${pProp}="4 sm:6 max-sm:2" />\n`
|
|
170
|
+
)
|
|
171
|
+
sections.push('```\n\n')
|
|
172
|
+
} else if (syntaxChoice === 'object') {
|
|
173
|
+
sections.push('**Object form:**\n\n')
|
|
174
|
+
sections.push('```tsx\n')
|
|
175
|
+
sections.push(
|
|
176
|
+
`<View ${bgProp}={{ default: 'background', hover: 'background-hover', dark: 'blue-500' }} ${pProp}={{ default: '4', sm: '6', 'max-sm': '2' }} />\n`
|
|
177
|
+
)
|
|
178
|
+
sections.push('```\n\n')
|
|
179
|
+
} else {
|
|
180
|
+
sections.push('Both forms are allowed:\n\n')
|
|
181
|
+
sections.push('```tsx\n')
|
|
182
|
+
sections.push('// string form\n')
|
|
183
|
+
sections.push(
|
|
184
|
+
`<View ${bgProp}="background hover:background-hover dark:blue-500" ${pProp}="4 sm:6 max-sm:2" />\n\n`
|
|
185
|
+
)
|
|
186
|
+
sections.push('// object form\n')
|
|
187
|
+
sections.push(
|
|
188
|
+
`<View ${bgProp}={{ default: 'background', hover: 'background-hover', dark: 'blue-500' }} ${pProp}={{ default: '4', sm: '6', 'max-sm': '2' }} />\n`
|
|
189
|
+
)
|
|
190
|
+
sections.push('```\n\n')
|
|
184
191
|
}
|
|
185
192
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Shorthands
|
|
193
|
+
// shorthands
|
|
189
194
|
sections.push('## Shorthand Properties\n\n')
|
|
190
195
|
sections.push('These shorthand properties are available for styling:\n\n')
|
|
191
|
-
|
|
192
196
|
const shorthandEntries = Object.entries(shorthands).sort(([a], [b]) =>
|
|
193
197
|
a.localeCompare(b)
|
|
194
198
|
)
|
|
195
|
-
|
|
196
199
|
sections.push(
|
|
197
200
|
shorthandEntries.map(([short, full]) => `- \`${short}\` → \`${full}\``).join('\n')
|
|
198
201
|
)
|
|
199
202
|
sections.push('\n\n')
|
|
200
203
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
level1: new Set(),
|
|
217
|
-
level2: new Set(),
|
|
218
|
-
level3: new Set(),
|
|
219
|
-
components: new Set(),
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
for (const themeName of themeNames) {
|
|
223
|
-
const parts = themeName.split('_')
|
|
224
|
-
|
|
225
|
-
// Level 1: light/dark
|
|
226
|
-
if (parts[0] === 'light' || parts[0] === 'dark') {
|
|
227
|
-
hierarchy.level1.add(parts[0])
|
|
228
|
-
|
|
229
|
-
// Level 2: color names (blue, red, green, etc.)
|
|
230
|
-
if (
|
|
231
|
-
parts.length > 1 &&
|
|
232
|
-
parts[1] &&
|
|
233
|
-
!parts[1].startsWith('alt') &&
|
|
234
|
-
parts[1] !== 'active'
|
|
235
|
-
) {
|
|
236
|
-
// Check if it's not a component by looking if it starts with uppercase
|
|
237
|
-
if (parts[1][0] === parts[1][0].toLowerCase()) {
|
|
238
|
-
hierarchy.level2.add(parts[1])
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Level 3: variants (alt1, alt2, etc.)
|
|
243
|
-
for (const part of parts) {
|
|
244
|
-
if (part.startsWith('alt') || part === 'active') {
|
|
245
|
-
hierarchy.level3.add(part)
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Components: parts that start with uppercase
|
|
250
|
-
for (const part of parts) {
|
|
251
|
-
if (
|
|
252
|
-
part[0] &&
|
|
253
|
-
part[0] === part[0].toUpperCase() &&
|
|
254
|
-
part[0] !== part[0].toLowerCase()
|
|
255
|
-
) {
|
|
256
|
-
hierarchy.components.add(part)
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
// Base theme without light/dark prefix
|
|
261
|
-
if (parts.length === 1) {
|
|
262
|
-
hierarchy.level1.add(themeName)
|
|
263
|
-
}
|
|
204
|
+
// named control sizes table
|
|
205
|
+
const sizes = config.tamaguiConfig?.sizes
|
|
206
|
+
if (sizes && typeof sizes === 'object') {
|
|
207
|
+
sections.push('## Named Control Sizes\n\n')
|
|
208
|
+
sections.push(
|
|
209
|
+
`Control components (Button, Input, etc.) use the configured names below. Default is \`${sizes.default ?? 'md'}\`.\n\n`
|
|
210
|
+
)
|
|
211
|
+
sections.push('| Size | Configuration |\n')
|
|
212
|
+
sections.push('|---|---|\n')
|
|
213
|
+
for (const [sizeKey, val] of Object.entries(sizes)) {
|
|
214
|
+
if (sizeKey === 'default') continue
|
|
215
|
+
const valStr =
|
|
216
|
+
typeof val === 'object' && val !== null ? JSON.stringify(val) : String(val)
|
|
217
|
+
const isDefault = sizes.default === sizeKey ? ' (default)' : ''
|
|
218
|
+
sections.push(`| \`${sizeKey}\`${isDefault} | ${valStr} |\n`)
|
|
264
219
|
}
|
|
220
|
+
sections.push('\n')
|
|
265
221
|
}
|
|
266
222
|
|
|
267
|
-
|
|
223
|
+
// themes
|
|
224
|
+
sections.push('## Themes\n\n')
|
|
225
|
+
const themes = config.tamaguiConfig?.themes || {}
|
|
226
|
+
const themeNames = Object.keys(themes).sort()
|
|
268
227
|
|
|
269
|
-
|
|
270
|
-
|
|
228
|
+
sections.push(themeNames.map((name) => `- \`${name}\``).join('\n'))
|
|
229
|
+
sections.push(
|
|
230
|
+
'\n\nTheme names above are exact configured names. Nested themes resolve relative to their parent. Use an explicit theme boundary in a component skin.\n\n'
|
|
231
|
+
)
|
|
232
|
+
if (themeNames.length) {
|
|
233
|
+
sections.push('### Theme Usage\n\n')
|
|
271
234
|
sections.push(
|
|
272
|
-
|
|
273
|
-
.sort()
|
|
274
|
-
.map((name) => `- ${name}`)
|
|
275
|
-
.join('\n')
|
|
235
|
+
`\`\`\`tsx\n<Theme name=${JSON.stringify(themeNames[0])}>\n <Button>Uses this theme</Button>\n</Theme>\n\`\`\`\n\n`
|
|
276
236
|
)
|
|
277
|
-
sections.push('\n\n')
|
|
278
237
|
}
|
|
279
238
|
|
|
280
|
-
|
|
281
|
-
|
|
239
|
+
sections.push('**Accessing theme values:**\n\n')
|
|
240
|
+
sections.push('Components access theme values by their bare names:\n\n')
|
|
241
|
+
const colorProp = getPropName('color')
|
|
242
|
+
if (syntaxChoice === 'string') {
|
|
243
|
+
sections.push('```tsx\n')
|
|
282
244
|
sections.push(
|
|
283
|
-
|
|
284
|
-
.sort()
|
|
285
|
-
.map((name) => `- ${name}`)
|
|
286
|
-
.join('\n')
|
|
245
|
+
`<View ${bgProp}="background hover:background-hover" ${colorProp}="color" />\n`
|
|
287
246
|
)
|
|
288
|
-
sections.push('
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (hierarchy.level3.size > 0) {
|
|
292
|
-
sections.push('**Level 3 (Variants):**\n\n')
|
|
247
|
+
sections.push('```\n\n')
|
|
248
|
+
} else if (syntaxChoice === 'object') {
|
|
249
|
+
sections.push('```tsx\n')
|
|
293
250
|
sections.push(
|
|
294
|
-
|
|
295
|
-
.sort()
|
|
296
|
-
.map((name) => `- ${name}`)
|
|
297
|
-
.join('\n')
|
|
251
|
+
`<View ${bgProp}={{ default: 'background', hover: 'background-hover' }} ${colorProp}={{ default: 'color' }} />\n`
|
|
298
252
|
)
|
|
299
|
-
sections.push('
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
sections.push('**Component Themes:**\n\n')
|
|
253
|
+
sections.push('```\n\n')
|
|
254
|
+
} else {
|
|
255
|
+
sections.push('```tsx\n')
|
|
256
|
+
sections.push('// string form\n')
|
|
304
257
|
sections.push(
|
|
305
|
-
|
|
306
|
-
.sort()
|
|
307
|
-
.map((name) => `- ${name}`)
|
|
308
|
-
.join('\n')
|
|
258
|
+
`<View ${bgProp}="background hover:background-hover" ${colorProp}="color" />\n\n`
|
|
309
259
|
)
|
|
310
|
-
sections.push('\n
|
|
260
|
+
sections.push('// object form\n')
|
|
261
|
+
sections.push(
|
|
262
|
+
`<View ${bgProp}={{ default: 'background', hover: 'background-hover' }} ${colorProp}={{ default: 'color' }} />\n`
|
|
263
|
+
)
|
|
264
|
+
sections.push('```\n\n')
|
|
311
265
|
}
|
|
312
266
|
|
|
313
|
-
// Add usage documentation
|
|
314
|
-
sections.push('### Theme Usage\n\n')
|
|
315
|
-
sections.push(
|
|
316
|
-
'Themes are combined hierarchically. For example, `light_blue_alt1_Button` combines:\n'
|
|
317
|
-
)
|
|
318
|
-
sections.push('- Base: `light`\n')
|
|
319
|
-
sections.push('- Color: `blue`\n')
|
|
320
|
-
sections.push('- Variant: `alt1`\n')
|
|
321
|
-
sections.push('- Component: `Button`\n\n')
|
|
322
|
-
|
|
323
|
-
sections.push('**Basic usage:**\n\n')
|
|
324
|
-
sections.push('```tsx\n')
|
|
325
|
-
sections.push('// Apply a theme to components\n')
|
|
326
|
-
sections.push('export default () => (\n')
|
|
327
|
-
sections.push(' <Theme name="dark">\n')
|
|
328
|
-
sections.push(" <Button>I'm a dark button</Button>\n")
|
|
329
|
-
sections.push(' </Theme>\n')
|
|
330
|
-
sections.push(')\n\n')
|
|
331
|
-
sections.push('// Themes nest and combine automatically\n')
|
|
332
|
-
sections.push('export default () => (\n')
|
|
333
|
-
sections.push(' <Theme name="dark">\n')
|
|
334
|
-
sections.push(' <Theme name="blue">\n')
|
|
335
|
-
sections.push(' <Button>Uses dark_blue theme</Button>\n')
|
|
336
|
-
sections.push(' </Theme>\n')
|
|
337
|
-
sections.push(' </Theme>\n')
|
|
338
|
-
sections.push(')\n')
|
|
339
|
-
sections.push('```\n\n')
|
|
340
|
-
|
|
341
|
-
sections.push('**Accessing theme values:**\n\n')
|
|
342
|
-
sections.push('Components access theme values by their bare names:\n\n')
|
|
343
|
-
sections.push('```tsx\n')
|
|
344
|
-
sections.push(
|
|
345
|
-
`<View ${getPropName('backgroundColor')}="background" ${getPropName('color')}="color" />\n`
|
|
346
|
-
)
|
|
347
|
-
sections.push('```\n\n')
|
|
348
|
-
|
|
349
267
|
sections.push('**Special props:**\n\n')
|
|
350
268
|
sections.push('- `theme="inverse"`: Uses the opposite light or dark sub-theme\n')
|
|
351
|
-
sections.push('- `reset`: Reverts to grandparent theme\n\n')
|
|
352
269
|
|
|
353
|
-
//
|
|
270
|
+
// tokens
|
|
354
271
|
sections.push('## Tokens\n\n')
|
|
355
272
|
sections.push('Tokens are design system values referenced by their bare names.\n\n')
|
|
356
273
|
|
|
357
274
|
const tokens = config.tamaguiConfig?.tokens || {}
|
|
358
275
|
|
|
359
|
-
//
|
|
360
|
-
if (tokens.space) {
|
|
276
|
+
// space tokens (only if present and non-empty)
|
|
277
|
+
if (tokens.space && Object.keys(tokens.space).length > 0) {
|
|
361
278
|
sections.push('### Space Tokens\n\n')
|
|
362
279
|
const spaceTokens = Object.entries(tokens.space).sort(([a], [b]) => {
|
|
363
|
-
// Sort numerically where possible
|
|
364
280
|
const numA = parseFloat(a)
|
|
365
281
|
const numB = parseFloat(b)
|
|
366
282
|
if (!isNaN(numA) && !isNaN(numB)) {
|
|
@@ -376,8 +292,8 @@ function generateMarkdown(config: any): string {
|
|
|
376
292
|
sections.push('\n\n')
|
|
377
293
|
}
|
|
378
294
|
|
|
379
|
-
//
|
|
380
|
-
if (tokens.size) {
|
|
295
|
+
// size tokens (only if present and non-empty)
|
|
296
|
+
if (tokens.size && Object.keys(tokens.size).length > 0) {
|
|
381
297
|
sections.push('### Size Tokens\n\n')
|
|
382
298
|
const sizeTokens = Object.entries(tokens.size).sort(([a], [b]) => {
|
|
383
299
|
const numA = parseFloat(a)
|
|
@@ -395,8 +311,8 @@ function generateMarkdown(config: any): string {
|
|
|
395
311
|
sections.push('\n\n')
|
|
396
312
|
}
|
|
397
313
|
|
|
398
|
-
//
|
|
399
|
-
if (tokens.radius) {
|
|
314
|
+
// radius tokens (only if present and non-empty)
|
|
315
|
+
if (tokens.radius && Object.keys(tokens.radius).length > 0) {
|
|
400
316
|
sections.push('### Radius Tokens\n\n')
|
|
401
317
|
const radiusTokens = Object.entries(tokens.radius).sort(([a], [b]) => {
|
|
402
318
|
const numA = parseFloat(a)
|
|
@@ -414,8 +330,8 @@ function generateMarkdown(config: any): string {
|
|
|
414
330
|
sections.push('\n\n')
|
|
415
331
|
}
|
|
416
332
|
|
|
417
|
-
// zIndex tokens
|
|
418
|
-
if (tokens.zIndex) {
|
|
333
|
+
// zIndex tokens (only if present and non-empty)
|
|
334
|
+
if (tokens.zIndex && Object.keys(tokens.zIndex).length > 0) {
|
|
419
335
|
sections.push('### Z-Index Tokens\n\n')
|
|
420
336
|
const zIndexTokens = Object.entries(tokens.zIndex).sort(([a], [b]) => {
|
|
421
337
|
const numA = parseFloat(a)
|
|
@@ -433,39 +349,124 @@ function generateMarkdown(config: any): string {
|
|
|
433
349
|
sections.push('\n\n')
|
|
434
350
|
}
|
|
435
351
|
|
|
436
|
-
//
|
|
437
|
-
|
|
352
|
+
// color tokens (only if present and non-empty; summarized if large palette set)
|
|
353
|
+
let sampleColorBg = 'blue-500'
|
|
354
|
+
let sampleColorText = 'color'
|
|
355
|
+
if (tokens.color && Object.keys(tokens.color).length > 0) {
|
|
438
356
|
sections.push('### Color Tokens\n\n')
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
357
|
+
const colorKeys = Object.keys(tokens.color).sort()
|
|
358
|
+
|
|
359
|
+
// detect tailwind style palette keys: <palette>-<step>
|
|
360
|
+
const paletteMap = new Map<string, Set<string>>()
|
|
361
|
+
const standaloneColors: Array<[string, any]> = []
|
|
362
|
+
|
|
363
|
+
for (const key of colorKeys) {
|
|
364
|
+
const match = key.match(/^([a-z]+)-(\d+)$/)
|
|
365
|
+
if (match) {
|
|
366
|
+
const [, palette, step] = match
|
|
367
|
+
if (!paletteMap.has(palette)) {
|
|
368
|
+
paletteMap.set(palette, new Set())
|
|
369
|
+
}
|
|
370
|
+
paletteMap.get(palette)!.add(step)
|
|
371
|
+
} else {
|
|
372
|
+
standaloneColors.push([key, tokens.color[key]])
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (paletteMap.size >= 4) {
|
|
377
|
+
// summarize palettes compactly instead of 300+ line dump
|
|
378
|
+
const palettes = Array.from(paletteMap.keys()).sort().join(', ')
|
|
379
|
+
sections.push(`**Tailwind Palettes (\`<name>-<50..950>\`):** ${palettes}\n\n`)
|
|
380
|
+
if (standaloneColors.length > 0) {
|
|
381
|
+
sections.push('**Named Colors:**\n\n')
|
|
382
|
+
sections.push(
|
|
383
|
+
standaloneColors
|
|
384
|
+
.map(([k, v]) => `- \`${k}\`: ${formatTokenValue(v)}`)
|
|
385
|
+
.join('\n')
|
|
386
|
+
)
|
|
387
|
+
sections.push('\n\n')
|
|
388
|
+
}
|
|
389
|
+
sampleColorBg = colorKeys.find((k) => k.startsWith('blue-')) || colorKeys[0]
|
|
390
|
+
sampleColorText =
|
|
391
|
+
colorKeys.find((k) => k.startsWith('gray-') || k.startsWith('zinc-')) ||
|
|
392
|
+
colorKeys[1] ||
|
|
393
|
+
'color'
|
|
394
|
+
} else {
|
|
395
|
+
sections.push(
|
|
396
|
+
colorKeys
|
|
397
|
+
.map((key) => `- \`${key}\`: ${formatTokenValue(tokens.color[key])}`)
|
|
398
|
+
.join('\n')
|
|
399
|
+
)
|
|
400
|
+
sections.push('\n\n')
|
|
401
|
+
sampleColorBg = colorKeys[0] || 'background'
|
|
402
|
+
sampleColorText = colorKeys[1] || 'color'
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
sampleColorBg = 'background'
|
|
406
|
+
sampleColorText = 'color'
|
|
448
407
|
}
|
|
449
408
|
|
|
450
|
-
//
|
|
409
|
+
// token usage examples
|
|
451
410
|
sections.push('### Token Usage\n\n')
|
|
452
411
|
sections.push('Tokens can be used in component props by their bare names:\n\n')
|
|
453
|
-
sections.push('```tsx\n')
|
|
454
|
-
sections.push('// Space tokens - for margin, padding, gap\n')
|
|
455
|
-
sections.push(
|
|
456
|
-
`<View ${getPropName('padding')}="4" ${getPropName('gap')}="2" ${getPropName('margin')}="3" />\n\n`
|
|
457
|
-
)
|
|
458
|
-
sections.push('// Size tokens - for width, height, dimensions\n')
|
|
459
|
-
sections.push(`<View ${getPropName('width')}="10" ${getPropName('height')}="6" />\n\n`)
|
|
460
|
-
sections.push('// Color tokens - for colors and backgrounds\n')
|
|
461
|
-
sections.push(
|
|
462
|
-
`<View ${getPropName('backgroundColor')}="blue5" ${getPropName('color')}="gray12" />\n\n`
|
|
463
|
-
)
|
|
464
|
-
sections.push('// Radius tokens - for border-radius\n')
|
|
465
|
-
sections.push(`<View ${getPropName('borderRadius')}="4" />\n`)
|
|
466
|
-
sections.push('```\n\n')
|
|
467
412
|
|
|
468
|
-
|
|
413
|
+
const paddingProp = getPropName('padding')
|
|
414
|
+
const gapProp = getPropName('gap')
|
|
415
|
+
const marginProp = getPropName('margin')
|
|
416
|
+
const widthProp = getPropName('width')
|
|
417
|
+
const heightProp = getPropName('height')
|
|
418
|
+
const radiusProp = getPropName('borderRadius')
|
|
419
|
+
const sampleRadius = tokens.radius?.md
|
|
420
|
+
? 'md'
|
|
421
|
+
: Object.keys(tokens.radius || {})[0] || '0px'
|
|
422
|
+
|
|
423
|
+
if (syntaxChoice === 'string') {
|
|
424
|
+
sections.push('```tsx\n')
|
|
425
|
+
sections.push(
|
|
426
|
+
`// Space tokens - for margin, padding, gap\n<View ${paddingProp}="4 sm:6" ${gapProp}="2" ${marginProp}="3" />\n\n`
|
|
427
|
+
)
|
|
428
|
+
sections.push(
|
|
429
|
+
`// Size tokens - for width, height, dimensions\n<View ${widthProp}="10 sm:12" ${heightProp}="6" />\n\n`
|
|
430
|
+
)
|
|
431
|
+
sections.push(
|
|
432
|
+
`// Color tokens - for colors and backgrounds\n<View ${bgProp}="${sampleColorBg} hover:${sampleColorBg}" ${colorProp}="${sampleColorText}" />\n\n`
|
|
433
|
+
)
|
|
434
|
+
sections.push(
|
|
435
|
+
`// Radius tokens - for border-radius\n<View ${radiusProp}="${sampleRadius}" />\n`
|
|
436
|
+
)
|
|
437
|
+
sections.push('```\n\n')
|
|
438
|
+
} else if (syntaxChoice === 'object') {
|
|
439
|
+
sections.push('```tsx\n')
|
|
440
|
+
sections.push(
|
|
441
|
+
`// Space tokens - for margin, padding, gap\n<View ${paddingProp}={{ default: '4', sm: '6' }} ${gapProp}="2" ${marginProp}="3" />\n\n`
|
|
442
|
+
)
|
|
443
|
+
sections.push(
|
|
444
|
+
`// Size tokens - for width, height, dimensions\n<View ${widthProp}={{ default: '10', sm: '12' }} ${heightProp}="6" />\n\n`
|
|
445
|
+
)
|
|
446
|
+
sections.push(
|
|
447
|
+
`// Color tokens - for colors and backgrounds\n<View ${bgProp}={{ default: '${sampleColorBg}', hover: '${sampleColorBg}' }} ${colorProp}={{ default: '${sampleColorText}' }} />\n\n`
|
|
448
|
+
)
|
|
449
|
+
sections.push(
|
|
450
|
+
`// Radius tokens - for border-radius\n<View ${radiusProp}="${sampleRadius}" />\n`
|
|
451
|
+
)
|
|
452
|
+
sections.push('```\n\n')
|
|
453
|
+
} else {
|
|
454
|
+
sections.push('```tsx\n')
|
|
455
|
+
sections.push('// String form\n')
|
|
456
|
+
sections.push(
|
|
457
|
+
`<View ${paddingProp}="4 sm:6" ${widthProp}="10 sm:12" ${bgProp}="${sampleColorBg}" />\n\n`
|
|
458
|
+
)
|
|
459
|
+
sections.push('// Object form\n')
|
|
460
|
+
sections.push(
|
|
461
|
+
`<View ${paddingProp}={{ default: '4', sm: '6' }} ${widthProp}={{ default: '10', sm: '12' }} ${bgProp}={{ default: '${sampleColorBg}' }} />\n\n`
|
|
462
|
+
)
|
|
463
|
+
sections.push(
|
|
464
|
+
`// Space and radius tokens\n<View ${gapProp}="2" ${marginProp}="3" ${heightProp}="6" ${radiusProp}="${sampleRadius}" />\n`
|
|
465
|
+
)
|
|
466
|
+
sections.push('```\n\n')
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// media queries
|
|
469
470
|
if (config.tamaguiConfig?.media) {
|
|
470
471
|
sections.push('## Media Queries\n\n')
|
|
471
472
|
sections.push('Available responsive breakpoints:\n\n')
|
|
@@ -474,7 +475,7 @@ function generateMarkdown(config: any): string {
|
|
|
474
475
|
const mediaEntries = Object.entries(media).sort(([a], [b]) => a.localeCompare(b))
|
|
475
476
|
|
|
476
477
|
for (const [name, query] of mediaEntries) {
|
|
477
|
-
sections.push(`- **${name}**: ${
|
|
478
|
+
sections.push(`- **${name}**: ${formatMediaQuery(query)}\n`)
|
|
478
479
|
}
|
|
479
480
|
sections.push('\n')
|
|
480
481
|
|
|
@@ -482,41 +483,59 @@ function generateMarkdown(config: any): string {
|
|
|
482
483
|
sections.push(
|
|
483
484
|
'Media queries can be used as style props or with the `useMedia` hook:\n\n'
|
|
484
485
|
)
|
|
485
|
-
sections.push('```tsx\n')
|
|
486
|
-
sections.push('// As a clause in the same style value\n')
|
|
487
486
|
|
|
488
|
-
//
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
487
|
+
// pick a representative media query (prefer sm or md over height-*)
|
|
488
|
+
const representative =
|
|
489
|
+
mediaEntries.find(([n]) => n === 'sm' || n === 'md')?.[0] ||
|
|
490
|
+
mediaEntries.find(([n]) => !n.includes('-'))?.[0] ||
|
|
491
|
+
mediaEntries[0]?.[0]
|
|
492
|
+
|
|
493
|
+
if (representative) {
|
|
494
|
+
const isIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(representative)
|
|
495
|
+
const mediaAccess = isIdentifier
|
|
496
|
+
? `media.${representative}`
|
|
497
|
+
: `media['${representative}']`
|
|
498
|
+
|
|
499
|
+
sections.push('```tsx\n')
|
|
500
|
+
if (syntaxChoice === 'string') {
|
|
501
|
+
sections.push('// As a clause in the same style value\n')
|
|
502
|
+
sections.push(`<View ${widthProp}="100% ${representative}:50%" />\n\n`)
|
|
503
|
+
} else if (syntaxChoice === 'object') {
|
|
504
|
+
sections.push('// Using the object form\n')
|
|
505
|
+
sections.push(
|
|
506
|
+
`<View ${widthProp}={{ default: '100%', '${representative}': '50%' }} />\n\n`
|
|
507
|
+
)
|
|
508
|
+
} else {
|
|
509
|
+
sections.push('// String form and object form\n')
|
|
510
|
+
sections.push(`<View ${widthProp}="100% ${representative}:50%" />\n`)
|
|
511
|
+
sections.push(
|
|
512
|
+
`<View ${widthProp}={{ default: '100%', '${representative}': '50%' }} />\n\n`
|
|
513
|
+
)
|
|
514
|
+
}
|
|
493
515
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
sections.push(`if (media.${firstMediaName}) {\n`)
|
|
516
|
+
sections.push('// Using the useMedia hook\n')
|
|
517
|
+
sections.push('const media = useMedia()\n')
|
|
518
|
+
sections.push(`if (${mediaAccess}) {\n`)
|
|
498
519
|
sections.push(' // Render for this breakpoint\n')
|
|
499
520
|
sections.push('}\n')
|
|
521
|
+
sections.push('```\n\n')
|
|
500
522
|
}
|
|
501
|
-
sections.push('```\n\n')
|
|
502
523
|
}
|
|
503
524
|
|
|
504
|
-
//
|
|
525
|
+
// fonts
|
|
505
526
|
if (config.tamaguiConfig?.fonts) {
|
|
506
527
|
sections.push('## Fonts\n\n')
|
|
507
528
|
sections.push('Available font families:\n\n')
|
|
508
|
-
|
|
509
529
|
const fonts = config.tamaguiConfig.fonts
|
|
510
530
|
const fontNames = Object.keys(fonts).sort()
|
|
511
531
|
sections.push(fontNames.map((name) => `- ${name}`).join('\n'))
|
|
512
532
|
sections.push('\n\n')
|
|
513
533
|
}
|
|
514
534
|
|
|
515
|
-
//
|
|
535
|
+
// animations
|
|
516
536
|
if (config.tamaguiConfig?.animations) {
|
|
517
537
|
sections.push('## Animations\n\n')
|
|
518
538
|
sections.push('Available animation presets:\n\n')
|
|
519
|
-
|
|
520
539
|
const animations = config.tamaguiConfig.animations
|
|
521
540
|
if (animations.animations) {
|
|
522
541
|
const animationNames = Object.keys(animations.animations).sort()
|
|
@@ -525,17 +544,111 @@ function generateMarkdown(config: any): string {
|
|
|
525
544
|
}
|
|
526
545
|
}
|
|
527
546
|
|
|
528
|
-
//
|
|
529
|
-
|
|
547
|
+
// components section: emit barrel's public import surface
|
|
548
|
+
const componentsSection: string[] = []
|
|
549
|
+
const componentSet = new Set<string>()
|
|
550
|
+
|
|
551
|
+
// try loading public exports from tamagui or candidate modules
|
|
552
|
+
let barrelExported = false
|
|
553
|
+
const candidateModules = [
|
|
554
|
+
'tamagui',
|
|
555
|
+
...(config.components || []).map((c: any) => c.moduleName),
|
|
556
|
+
]
|
|
557
|
+
for (const modName of candidateModules) {
|
|
558
|
+
if (!modName) continue
|
|
559
|
+
try {
|
|
560
|
+
const mod = require(modName)
|
|
561
|
+
for (const [key, val] of Object.entries(mod)) {
|
|
562
|
+
if (
|
|
563
|
+
/^[A-Z]/.test(key) &&
|
|
564
|
+
(typeof val === 'function' || (typeof val === 'object' && val !== null))
|
|
565
|
+
) {
|
|
566
|
+
if (
|
|
567
|
+
!key.endsWith('Context') &&
|
|
568
|
+
!key.endsWith('Provider') &&
|
|
569
|
+
key !== 'Fragment'
|
|
570
|
+
) {
|
|
571
|
+
componentSet.add(key)
|
|
572
|
+
barrelExported = true
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (barrelExported) break
|
|
577
|
+
} catch {
|
|
578
|
+
// fallback to config.components below
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (!barrelExported && config.components) {
|
|
583
|
+
for (const componentModule of config.components) {
|
|
584
|
+
for (const name of Object.keys(componentModule.nameToInfo || {})) {
|
|
585
|
+
componentSet.add(name)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
530
589
|
|
|
590
|
+
// filter internal frames ending with Frame when parent or part exists
|
|
591
|
+
const allComponents = Array.from(componentSet).filter((name) => {
|
|
592
|
+
if (name.endsWith('Frame')) {
|
|
593
|
+
const base = name.replace(/Frame$/, '')
|
|
594
|
+
if (
|
|
595
|
+
componentSet.has(base) ||
|
|
596
|
+
name.startsWith('Popper') ||
|
|
597
|
+
name.startsWith('DialogPortal') ||
|
|
598
|
+
name.startsWith('SelectScrollButton')
|
|
599
|
+
) {
|
|
600
|
+
return false
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return true
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
componentsSection.push('## Components\n\n')
|
|
607
|
+
componentsSection.push('Available named exports (import these names directly):\n\n')
|
|
608
|
+
for (const name of allComponents.sort()) {
|
|
609
|
+
componentsSection.push(`- ${name}\n`)
|
|
610
|
+
}
|
|
611
|
+
componentsSection.push('\n')
|
|
612
|
+
|
|
613
|
+
sections.push(...componentsSection)
|
|
531
614
|
return sections.join('')
|
|
532
615
|
}
|
|
533
616
|
|
|
534
617
|
function formatTokenValue(value: any): string {
|
|
535
|
-
// If it's an object with a 'val' property (token object), extract the value
|
|
536
618
|
if (typeof value === 'object' && value !== null && 'val' in value) {
|
|
537
619
|
return String(value.val)
|
|
538
620
|
}
|
|
539
|
-
// Otherwise, stringify it
|
|
540
621
|
return String(value)
|
|
541
622
|
}
|
|
623
|
+
|
|
624
|
+
function formatMediaQuery(query: any): string {
|
|
625
|
+
if (typeof query !== 'object' || query === null) {
|
|
626
|
+
return String(query)
|
|
627
|
+
}
|
|
628
|
+
const parts: string[] = []
|
|
629
|
+
if (query.minWidth !== undefined) {
|
|
630
|
+
parts.push(`min-width: ${query.minWidth}px (screens >= ${query.minWidth}px wide)`)
|
|
631
|
+
}
|
|
632
|
+
if (query.maxWidth !== undefined) {
|
|
633
|
+
parts.push(`max-width: ${query.maxWidth}px (screens <= ${query.maxWidth}px wide)`)
|
|
634
|
+
}
|
|
635
|
+
if (query.minHeight !== undefined) {
|
|
636
|
+
parts.push(`min-height: ${query.minHeight}px (screens >= ${query.minHeight}px tall)`)
|
|
637
|
+
}
|
|
638
|
+
if (query.maxHeight !== undefined) {
|
|
639
|
+
parts.push(`max-height: ${query.maxHeight}px (screens <= ${query.maxHeight}px tall)`)
|
|
640
|
+
}
|
|
641
|
+
if (query.hover !== undefined) {
|
|
642
|
+
parts.push(`pointer: hover (${query.hover})`)
|
|
643
|
+
}
|
|
644
|
+
if (query.pointer !== undefined) {
|
|
645
|
+
parts.push(`pointer: ${query.pointer}`)
|
|
646
|
+
}
|
|
647
|
+
if (query.prefersReducedMotion !== undefined) {
|
|
648
|
+
parts.push(`prefers-reduced-motion: ${query.prefersReducedMotion}`)
|
|
649
|
+
}
|
|
650
|
+
if (parts.length === 0) {
|
|
651
|
+
return JSON.stringify(query)
|
|
652
|
+
}
|
|
653
|
+
return parts.join(', ')
|
|
654
|
+
}
|