@tamagui/cli 2.7.7 → 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/add.cjs +76 -87
- package/dist/build.cjs +425 -278
- package/dist/cli.cjs +361 -340
- package/dist/generate-prompt.cjs +484 -362
- package/dist/generate.cjs +45 -54
- package/dist/index.cjs +1 -1
- package/dist/migrate.cjs +437 -0
- package/dist/setup-prompt.cjs +216 -0
- package/dist/to-tailwind-default-config.cjs +789 -0
- package/dist/to-tailwind.cjs +213 -0
- package/dist/update-template.cjs +43 -52
- package/dist/update.cjs +14 -18
- package/dist/upgrade.cjs +417 -401
- package/dist/utils.cjs +84 -101
- package/metro.cjs +1 -0
- package/metro.d.ts +1 -0
- package/metro.mjs +1 -0
- package/package.json +43 -13
- package/src/build.ts +594 -362
- package/src/cli.ts +113 -12
- package/src/generate-prompt.ts +428 -329
- package/src/migrate.ts +422 -0
- package/src/setup-prompt.ts +192 -0
- package/src/to-tailwind-default-config.ts +767 -0
- package/src/to-tailwind.ts +313 -0
- package/src/upgrade.ts +30 -33
- package/src/utils.ts +11 -9
- package/types/add.d.ts +1 -1
- package/types/add.d.ts.map +1 -1
- package/types/build.d.ts +2 -1
- package/types/build.d.ts.map +1 -1
- package/types/generate-prompt.d.ts +6 -2
- package/types/generate-prompt.d.ts.map +1 -1
- package/types/migrate.d.ts +7 -0
- package/types/migrate.d.ts.map +1 -0
- package/types/setup-prompt.d.ts +5 -0
- package/types/setup-prompt.d.ts.map +1 -0
- package/types/to-tailwind-default-config.d.ts +53 -0
- package/types/to-tailwind-default-config.d.ts.map +1 -0
- package/types/to-tailwind.d.ts +16 -0
- package/types/to-tailwind.d.ts.map +1 -0
- package/types/upgrade.d.ts.map +1 -1
- package/types/utils.d.ts.map +1 -1
- package/vite.cjs +1 -0
- package/vite.d.ts +1 -0
- package/vite.mjs +1 -0
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,349 +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
|
-
sections.push('## Configuration Settings\n\n')
|
|
70
|
-
sections.push(
|
|
71
|
-
'**IMPORTANT:** These settings affect how you write Tamagui code in this project.\n\n'
|
|
72
|
-
)
|
|
81
|
+
const syntaxChoice = options?.styleValueSyntax || settings.styleValueSyntax || 'both'
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
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
|
+
)
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
}
|
|
89
|
+
if (settings.defaultFont) {
|
|
90
|
+
sections.push(`### Default Font: \`${settings.defaultFont}\`\n\n`)
|
|
91
|
+
sections.push(
|
|
92
|
+
`All text components will use the "${settings.defaultFont}" font family by default.\n\n`
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
95
|
|
|
96
|
-
|
|
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')
|
|
97
103
|
sections.push(
|
|
98
|
-
|
|
104
|
+
'See the Shorthand Properties section below for all available shorthands.\n\n'
|
|
99
105
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Check for platform-specific settings
|
|
106
|
-
const platform = settings.platform || settings.defaultProps?.platform
|
|
107
|
-
if (platform) {
|
|
108
|
-
sections.push(`### Platform Mode: \`${platform}\`\n\n`)
|
|
109
|
-
|
|
110
|
-
if (platform === 'web') {
|
|
111
|
-
sections.push('This project is configured for **web only**.\n\n')
|
|
112
|
-
} else if (platform === 'native') {
|
|
113
|
-
sections.push('This project is configured for **React Native only**.\n\n')
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Check for web-specific optimizations
|
|
118
|
-
if (settings.webContainerType) {
|
|
119
|
-
sections.push(`### Web Container Type: \`${settings.webContainerType}\`\n\n`)
|
|
120
|
-
sections.push('Enables web-specific container query optimizations.\n\n')
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Check for strictness settings (common patterns)
|
|
124
|
-
const configString = JSON.stringify(config.tamaguiConfig)
|
|
125
|
-
if (configString.includes('semi-strict-web')) {
|
|
126
|
-
sections.push('### Mode: `semi-strict-web`\n\n')
|
|
127
|
-
sections.push('This configuration uses semi-strict-web mode, which:\n')
|
|
128
|
-
sections.push('- Optimizes for web performance\n')
|
|
129
|
-
sections.push('- May have limited React Native API support\n')
|
|
130
|
-
sections.push('- Focuses on web-first development\n\n')
|
|
106
|
+
} else {
|
|
107
|
+
sections.push('You can use either shorthand or full property names.\n\n')
|
|
131
108
|
}
|
|
132
109
|
}
|
|
133
110
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const componentNames = Object.keys(componentModule.nameToInfo)
|
|
140
|
-
allComponents.push(...componentNames)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Group components by prefix (e.g., Dialog, DialogClose -> Dialog.Close)
|
|
144
|
-
// Strategy: Find potential base components and check if others follow the pattern
|
|
145
|
-
const componentGroups = new Map<string, Set<string>>()
|
|
146
|
-
const processed = new Set<string>()
|
|
147
|
-
|
|
148
|
-
// Sort components to process shorter names first (potential base components)
|
|
149
|
-
const sortedComponents = [...allComponents].sort((a, b) => a.length - b.length)
|
|
150
|
-
|
|
151
|
-
for (const name of sortedComponents) {
|
|
152
|
-
if (processed.has(name)) continue
|
|
153
|
-
|
|
154
|
-
// Check if other components start with this name followed by an uppercase letter
|
|
155
|
-
const children = allComponents.filter(
|
|
156
|
-
(other) =>
|
|
157
|
-
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'
|
|
158
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
|
+
}
|
|
159
126
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
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')
|
|
165
131
|
}
|
|
166
132
|
}
|
|
167
133
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
}
|
|
173
143
|
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
)
|
|
179
161
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
componentsSection.push(`- ${name}\n`)
|
|
162
|
+
const bgProp = getPropName('background')
|
|
163
|
+
const pProp = getPropName('padding')
|
|
183
164
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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')
|
|
192
191
|
}
|
|
193
192
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// Shorthands
|
|
193
|
+
// shorthands
|
|
197
194
|
sections.push('## Shorthand Properties\n\n')
|
|
198
195
|
sections.push('These shorthand properties are available for styling:\n\n')
|
|
199
|
-
|
|
200
196
|
const shorthandEntries = Object.entries(shorthands).sort(([a], [b]) =>
|
|
201
197
|
a.localeCompare(b)
|
|
202
198
|
)
|
|
203
|
-
|
|
204
199
|
sections.push(
|
|
205
200
|
shorthandEntries.map(([short, full]) => `- \`${short}\` → \`${full}\``).join('\n')
|
|
206
201
|
)
|
|
207
202
|
sections.push('\n\n')
|
|
208
203
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
level1: new Set(),
|
|
225
|
-
level2: new Set(),
|
|
226
|
-
level3: new Set(),
|
|
227
|
-
components: new Set(),
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
for (const themeName of themeNames) {
|
|
231
|
-
const parts = themeName.split('_')
|
|
232
|
-
|
|
233
|
-
// Level 1: light/dark
|
|
234
|
-
if (parts[0] === 'light' || parts[0] === 'dark') {
|
|
235
|
-
hierarchy.level1.add(parts[0])
|
|
236
|
-
|
|
237
|
-
// Level 2: color names (blue, red, green, etc.)
|
|
238
|
-
if (
|
|
239
|
-
parts.length > 1 &&
|
|
240
|
-
parts[1] &&
|
|
241
|
-
!parts[1].startsWith('alt') &&
|
|
242
|
-
parts[1] !== 'active'
|
|
243
|
-
) {
|
|
244
|
-
// Check if it's not a component by looking if it starts with uppercase
|
|
245
|
-
if (parts[1][0] === parts[1][0].toLowerCase()) {
|
|
246
|
-
hierarchy.level2.add(parts[1])
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Level 3: variants (alt1, alt2, etc.)
|
|
251
|
-
for (const part of parts) {
|
|
252
|
-
if (part.startsWith('alt') || part === 'active') {
|
|
253
|
-
hierarchy.level3.add(part)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Components: parts that start with uppercase
|
|
258
|
-
for (const part of parts) {
|
|
259
|
-
if (
|
|
260
|
-
part[0] &&
|
|
261
|
-
part[0] === part[0].toUpperCase() &&
|
|
262
|
-
part[0] !== part[0].toLowerCase()
|
|
263
|
-
) {
|
|
264
|
-
hierarchy.components.add(part)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
} else {
|
|
268
|
-
// Base theme without light/dark prefix
|
|
269
|
-
if (parts.length === 1) {
|
|
270
|
-
hierarchy.level1.add(themeName)
|
|
271
|
-
}
|
|
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`)
|
|
272
219
|
}
|
|
220
|
+
sections.push('\n')
|
|
273
221
|
}
|
|
274
222
|
|
|
275
|
-
|
|
223
|
+
// themes
|
|
224
|
+
sections.push('## Themes\n\n')
|
|
225
|
+
const themes = config.tamaguiConfig?.themes || {}
|
|
226
|
+
const themeNames = Object.keys(themes).sort()
|
|
276
227
|
|
|
277
|
-
|
|
278
|
-
|
|
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')
|
|
279
234
|
sections.push(
|
|
280
|
-
|
|
281
|
-
.sort()
|
|
282
|
-
.map((name) => `- ${name}`)
|
|
283
|
-
.join('\n')
|
|
235
|
+
`\`\`\`tsx\n<Theme name=${JSON.stringify(themeNames[0])}>\n <Button>Uses this theme</Button>\n</Theme>\n\`\`\`\n\n`
|
|
284
236
|
)
|
|
285
|
-
sections.push('\n\n')
|
|
286
237
|
}
|
|
287
238
|
|
|
288
|
-
|
|
289
|
-
|
|
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')
|
|
290
244
|
sections.push(
|
|
291
|
-
|
|
292
|
-
.sort()
|
|
293
|
-
.map((name) => `- ${name}`)
|
|
294
|
-
.join('\n')
|
|
245
|
+
`<View ${bgProp}="background hover:background-hover" ${colorProp}="color" />\n`
|
|
295
246
|
)
|
|
296
|
-
sections.push('
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (hierarchy.level3.size > 0) {
|
|
300
|
-
sections.push('**Level 3 (Variants):**\n\n')
|
|
247
|
+
sections.push('```\n\n')
|
|
248
|
+
} else if (syntaxChoice === 'object') {
|
|
249
|
+
sections.push('```tsx\n')
|
|
301
250
|
sections.push(
|
|
302
|
-
|
|
303
|
-
.sort()
|
|
304
|
-
.map((name) => `- ${name}`)
|
|
305
|
-
.join('\n')
|
|
251
|
+
`<View ${bgProp}={{ default: 'background', hover: 'background-hover' }} ${colorProp}={{ default: 'color' }} />\n`
|
|
306
252
|
)
|
|
307
|
-
sections.push('
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
sections.push('**Component Themes:**\n\n')
|
|
253
|
+
sections.push('```\n\n')
|
|
254
|
+
} else {
|
|
255
|
+
sections.push('```tsx\n')
|
|
256
|
+
sections.push('// string form\n')
|
|
312
257
|
sections.push(
|
|
313
|
-
|
|
314
|
-
.sort()
|
|
315
|
-
.map((name) => `- ${name}`)
|
|
316
|
-
.join('\n')
|
|
258
|
+
`<View ${bgProp}="background hover:background-hover" ${colorProp}="color" />\n\n`
|
|
317
259
|
)
|
|
318
|
-
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')
|
|
319
265
|
}
|
|
320
266
|
|
|
321
|
-
// Add usage documentation
|
|
322
|
-
sections.push('### Theme Usage\n\n')
|
|
323
|
-
sections.push(
|
|
324
|
-
'Themes are combined hierarchically. For example, `light_blue_alt1_Button` combines:\n'
|
|
325
|
-
)
|
|
326
|
-
sections.push('- Base: `light`\n')
|
|
327
|
-
sections.push('- Color: `blue`\n')
|
|
328
|
-
sections.push('- Variant: `alt1`\n')
|
|
329
|
-
sections.push('- Component: `Button`\n\n')
|
|
330
|
-
|
|
331
|
-
sections.push('**Basic usage:**\n\n')
|
|
332
|
-
sections.push('```tsx\n')
|
|
333
|
-
sections.push('// Apply a theme to components\n')
|
|
334
|
-
sections.push('export default () => (\n')
|
|
335
|
-
sections.push(' <Theme name="dark">\n')
|
|
336
|
-
sections.push(" <Button>I'm a dark button</Button>\n")
|
|
337
|
-
sections.push(' </Theme>\n')
|
|
338
|
-
sections.push(')\n\n')
|
|
339
|
-
sections.push('// Themes nest and combine automatically\n')
|
|
340
|
-
sections.push('export default () => (\n')
|
|
341
|
-
sections.push(' <Theme name="dark">\n')
|
|
342
|
-
sections.push(' <Theme name="blue">\n')
|
|
343
|
-
sections.push(' <Button>Uses dark_blue theme</Button>\n')
|
|
344
|
-
sections.push(' </Theme>\n')
|
|
345
|
-
sections.push(' </Theme>\n')
|
|
346
|
-
sections.push(')\n')
|
|
347
|
-
sections.push('```\n\n')
|
|
348
|
-
|
|
349
|
-
sections.push('**Accessing theme values:**\n\n')
|
|
350
|
-
sections.push('Components can access theme values using `$` token syntax:\n\n')
|
|
351
|
-
sections.push('```tsx\n')
|
|
352
|
-
sections.push(
|
|
353
|
-
`<View ${getPropName('backgroundColor')}="$background" ${getPropName('color')}="$color" />\n`
|
|
354
|
-
)
|
|
355
|
-
sections.push('```\n\n')
|
|
356
|
-
|
|
357
267
|
sections.push('**Special props:**\n\n')
|
|
358
|
-
sections.push('- `inverse`:
|
|
359
|
-
sections.push('- `reset`: Reverts to grandparent theme\n\n')
|
|
268
|
+
sections.push('- `theme="inverse"`: Uses the opposite light or dark sub-theme\n')
|
|
360
269
|
|
|
361
|
-
//
|
|
270
|
+
// tokens
|
|
362
271
|
sections.push('## Tokens\n\n')
|
|
363
|
-
sections.push(
|
|
364
|
-
'Tokens are design system values that can be referenced using the `$` prefix.\n\n'
|
|
365
|
-
)
|
|
272
|
+
sections.push('Tokens are design system values referenced by their bare names.\n\n')
|
|
366
273
|
|
|
367
274
|
const tokens = config.tamaguiConfig?.tokens || {}
|
|
368
275
|
|
|
369
|
-
//
|
|
370
|
-
if (tokens.space) {
|
|
276
|
+
// space tokens (only if present and non-empty)
|
|
277
|
+
if (tokens.space && Object.keys(tokens.space).length > 0) {
|
|
371
278
|
sections.push('### Space Tokens\n\n')
|
|
372
279
|
const spaceTokens = Object.entries(tokens.space).sort(([a], [b]) => {
|
|
373
|
-
// Sort numerically where possible
|
|
374
280
|
const numA = parseFloat(a)
|
|
375
281
|
const numB = parseFloat(b)
|
|
376
282
|
if (!isNaN(numA) && !isNaN(numB)) {
|
|
@@ -386,8 +292,8 @@ function generateMarkdown(config: any): string {
|
|
|
386
292
|
sections.push('\n\n')
|
|
387
293
|
}
|
|
388
294
|
|
|
389
|
-
//
|
|
390
|
-
if (tokens.size) {
|
|
295
|
+
// size tokens (only if present and non-empty)
|
|
296
|
+
if (tokens.size && Object.keys(tokens.size).length > 0) {
|
|
391
297
|
sections.push('### Size Tokens\n\n')
|
|
392
298
|
const sizeTokens = Object.entries(tokens.size).sort(([a], [b]) => {
|
|
393
299
|
const numA = parseFloat(a)
|
|
@@ -405,8 +311,8 @@ function generateMarkdown(config: any): string {
|
|
|
405
311
|
sections.push('\n\n')
|
|
406
312
|
}
|
|
407
313
|
|
|
408
|
-
//
|
|
409
|
-
if (tokens.radius) {
|
|
314
|
+
// radius tokens (only if present and non-empty)
|
|
315
|
+
if (tokens.radius && Object.keys(tokens.radius).length > 0) {
|
|
410
316
|
sections.push('### Radius Tokens\n\n')
|
|
411
317
|
const radiusTokens = Object.entries(tokens.radius).sort(([a], [b]) => {
|
|
412
318
|
const numA = parseFloat(a)
|
|
@@ -424,8 +330,8 @@ function generateMarkdown(config: any): string {
|
|
|
424
330
|
sections.push('\n\n')
|
|
425
331
|
}
|
|
426
332
|
|
|
427
|
-
// zIndex tokens
|
|
428
|
-
if (tokens.zIndex) {
|
|
333
|
+
// zIndex tokens (only if present and non-empty)
|
|
334
|
+
if (tokens.zIndex && Object.keys(tokens.zIndex).length > 0) {
|
|
429
335
|
sections.push('### Z-Index Tokens\n\n')
|
|
430
336
|
const zIndexTokens = Object.entries(tokens.zIndex).sort(([a], [b]) => {
|
|
431
337
|
const numA = parseFloat(a)
|
|
@@ -443,41 +349,124 @@ function generateMarkdown(config: any): string {
|
|
|
443
349
|
sections.push('\n\n')
|
|
444
350
|
}
|
|
445
351
|
|
|
446
|
-
//
|
|
447
|
-
|
|
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) {
|
|
448
356
|
sections.push('### Color Tokens\n\n')
|
|
449
|
-
const
|
|
450
|
-
|
|
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'
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// token usage examples
|
|
410
|
+
sections.push('### Token Usage\n\n')
|
|
411
|
+
sections.push('Tokens can be used in component props by their bare names:\n\n')
|
|
412
|
+
|
|
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`
|
|
451
427
|
)
|
|
452
428
|
sections.push(
|
|
453
|
-
|
|
454
|
-
.map(([key, value]) => `- \`${key}\`: ${formatTokenValue(value)}`)
|
|
455
|
-
.join('\n')
|
|
429
|
+
`// Size tokens - for width, height, dimensions\n<View ${widthProp}="10 sm:12" ${heightProp}="6" />\n\n`
|
|
456
430
|
)
|
|
457
|
-
sections.push(
|
|
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')
|
|
458
467
|
}
|
|
459
468
|
|
|
460
|
-
//
|
|
461
|
-
sections.push('### Token Usage\n\n')
|
|
462
|
-
sections.push('Tokens can be used in component props with the `$` prefix:\n\n')
|
|
463
|
-
sections.push('```tsx\n')
|
|
464
|
-
sections.push('// Space tokens - for margin, padding, gap\n')
|
|
465
|
-
sections.push(
|
|
466
|
-
`<View ${getPropName('padding')}="$4" ${getPropName('gap')}="$2" ${getPropName('margin')}="$3" />\n\n`
|
|
467
|
-
)
|
|
468
|
-
sections.push('// Size tokens - for width, height, dimensions\n')
|
|
469
|
-
sections.push(
|
|
470
|
-
`<View ${getPropName('width')}="$10" ${getPropName('height')}="$6" />\n\n`
|
|
471
|
-
)
|
|
472
|
-
sections.push('// Color tokens - for colors and backgrounds\n')
|
|
473
|
-
sections.push(
|
|
474
|
-
`<View ${getPropName('backgroundColor')}="$blue5" ${getPropName('color')}="$gray12" />\n\n`
|
|
475
|
-
)
|
|
476
|
-
sections.push('// Radius tokens - for border-radius\n')
|
|
477
|
-
sections.push(`<View ${getPropName('borderRadius')}="$4" />\n`)
|
|
478
|
-
sections.push('```\n\n')
|
|
479
|
-
|
|
480
|
-
// Media queries
|
|
469
|
+
// media queries
|
|
481
470
|
if (config.tamaguiConfig?.media) {
|
|
482
471
|
sections.push('## Media Queries\n\n')
|
|
483
472
|
sections.push('Available responsive breakpoints:\n\n')
|
|
@@ -486,7 +475,7 @@ function generateMarkdown(config: any): string {
|
|
|
486
475
|
const mediaEntries = Object.entries(media).sort(([a], [b]) => a.localeCompare(b))
|
|
487
476
|
|
|
488
477
|
for (const [name, query] of mediaEntries) {
|
|
489
|
-
sections.push(`- **${name}**: ${
|
|
478
|
+
sections.push(`- **${name}**: ${formatMediaQuery(query)}\n`)
|
|
490
479
|
}
|
|
491
480
|
sections.push('\n')
|
|
492
481
|
|
|
@@ -494,43 +483,59 @@ function generateMarkdown(config: any): string {
|
|
|
494
483
|
sections.push(
|
|
495
484
|
'Media queries can be used as style props or with the `useMedia` hook:\n\n'
|
|
496
485
|
)
|
|
497
|
-
sections.push('```tsx\n')
|
|
498
|
-
sections.push('// As style props (prefix with $)\n')
|
|
499
486
|
|
|
500
|
-
//
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
+
}
|
|
507
515
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
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`)
|
|
512
519
|
sections.push(' // Render for this breakpoint\n')
|
|
513
520
|
sections.push('}\n')
|
|
521
|
+
sections.push('```\n\n')
|
|
514
522
|
}
|
|
515
|
-
sections.push('```\n\n')
|
|
516
523
|
}
|
|
517
524
|
|
|
518
|
-
//
|
|
525
|
+
// fonts
|
|
519
526
|
if (config.tamaguiConfig?.fonts) {
|
|
520
527
|
sections.push('## Fonts\n\n')
|
|
521
528
|
sections.push('Available font families:\n\n')
|
|
522
|
-
|
|
523
529
|
const fonts = config.tamaguiConfig.fonts
|
|
524
530
|
const fontNames = Object.keys(fonts).sort()
|
|
525
531
|
sections.push(fontNames.map((name) => `- ${name}`).join('\n'))
|
|
526
532
|
sections.push('\n\n')
|
|
527
533
|
}
|
|
528
534
|
|
|
529
|
-
//
|
|
535
|
+
// animations
|
|
530
536
|
if (config.tamaguiConfig?.animations) {
|
|
531
537
|
sections.push('## Animations\n\n')
|
|
532
538
|
sections.push('Available animation presets:\n\n')
|
|
533
|
-
|
|
534
539
|
const animations = config.tamaguiConfig.animations
|
|
535
540
|
if (animations.animations) {
|
|
536
541
|
const animationNames = Object.keys(animations.animations).sort()
|
|
@@ -539,17 +544,111 @@ function generateMarkdown(config: any): string {
|
|
|
539
544
|
}
|
|
540
545
|
}
|
|
541
546
|
|
|
542
|
-
//
|
|
543
|
-
|
|
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
|
+
}
|
|
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')
|
|
544
612
|
|
|
613
|
+
sections.push(...componentsSection)
|
|
545
614
|
return sections.join('')
|
|
546
615
|
}
|
|
547
616
|
|
|
548
617
|
function formatTokenValue(value: any): string {
|
|
549
|
-
// If it's an object with a 'val' property (token object), extract the value
|
|
550
618
|
if (typeof value === 'object' && value !== null && 'val' in value) {
|
|
551
619
|
return String(value.val)
|
|
552
620
|
}
|
|
553
|
-
// Otherwise, stringify it
|
|
554
621
|
return String(value)
|
|
555
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
|
+
}
|