@tamagui/cli 3.0.0-beta.924.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.ts CHANGED
@@ -15,28 +15,45 @@ const COMMAND_MAP = {
15
15
  '--verbose': Boolean,
16
16
  '--styles-only': Boolean,
17
17
  '--deps-only': Boolean,
18
+ '--strict': Boolean,
18
19
  },
19
20
  async run() {
20
21
  const { _, ...flags } = arg(this.flags)
21
22
  const options = await getOptions({
22
23
  debug: flags['--debug'] ? (flags['--verbose'] ? 'verbose' : true) : false,
24
+ loadTamaguiOptions: flags['--strict'] && !flags['--deps-only'],
23
25
  })
24
26
  if (!flags['--styles-only']) {
25
27
  const { checkDeps } = require('@tamagui/static/checkDeps')
26
28
  await checkDeps(options.paths.root)
27
29
  }
28
30
  if (flags['--deps-only']) return
31
+ if (flags['--strict']) {
32
+ if (!options.tamaguiOptions.config) {
33
+ throw new Error('Strict style checking requires a Tamagui config.')
34
+ }
35
+ const { loadTamagui } = require('@tamagui/static/loadTamagui')
36
+ process.env.TAMAGUI_KEEP_THEMES = '1'
37
+ const loaded = await loadTamagui(
38
+ { ...options.tamaguiOptions, platform: 'web' },
39
+ true
40
+ )
41
+ if (!loaded?.tamaguiConfig) {
42
+ throw new Error('Unable to load the Tamagui config for strict style checking.')
43
+ }
44
+ }
29
45
  const { checkStyleFiles, formatCheckResults, MissingConfigArtifactError } =
30
- require('@tamagui/language-service/check') as typeof import('@tamagui/language-service/check')
46
+ require('@tamagui/language-service/check') as any
31
47
  try {
32
48
  const result = checkStyleFiles({
33
49
  root: options.paths.root,
34
50
  configPath: options.paths.conf,
51
+ strict: flags['--strict'],
35
52
  })
36
53
  console.info(formatCheckResults(result))
37
54
  if (result.diagnosticCount > 0) process.exitCode = 1
38
- } catch (error) {
39
- if (error instanceof MissingConfigArtifactError) {
55
+ } catch (error: any) {
56
+ if (error instanceof MissingConfigArtifactError && !flags['--strict']) {
40
57
  console.warn(chalk.yellow(`skipping flat value check: ${error.message}`))
41
58
  return
42
59
  }
@@ -61,18 +78,7 @@ const COMMAND_MAP = {
61
78
  })
62
79
  const { loadTamagui } = require('@tamagui/static/loadTamagui')
63
80
  process.env.TAMAGUI_KEEP_THEMES = '1'
64
- await loadTamagui({
65
- ...options.tamaguiOptions,
66
- platform: 'web',
67
- })
68
-
69
- // also generate prompt to .tamagui/prompt.md
70
- const { generatePrompt } = require('./generate-prompt')
71
- const { join } = require('node:path')
72
- await generatePrompt({
73
- ...options,
74
- output: join(options.paths.dotDir, 'prompt.md'),
75
- })
81
+ await loadTamagui({ ...options.tamaguiOptions, platform: 'web' }, true)
76
82
  },
77
83
  },
78
84
 
@@ -161,6 +167,38 @@ const COMMAND_MAP = {
161
167
  },
162
168
  },
163
169
 
170
+ icons: {
171
+ shorthands: [],
172
+ description: `Generate themed SVG icon components from Lucide, Heroicons, Phosphor, or a folder of svgs.`,
173
+ usage: `$ tamagui icons add Search ChevronDown
174
+ $ tamagui icons add Copy --from phosphor --weight bold
175
+ $ tamagui icons add Clipboard --from heroicons --variant solid
176
+ $ tamagui icons add Logo --from ./assets/icons --out src/icons
177
+ $ tamagui icons add Search=magnifying-glass --from phosphor`,
178
+ flags: {
179
+ '--help': Boolean,
180
+ '--from': String,
181
+ '--weight': String,
182
+ '--variant': String,
183
+ '--out': String,
184
+ },
185
+ async run() {
186
+ const { _, ...flags } = arg(this.flags)
187
+ const [_cmd, subcommand, ...names] = _
188
+ if (subcommand !== 'add') {
189
+ throw new Error('usage: tamagui icons add <Name...> [--from] [--out]')
190
+ }
191
+ const { addIcons } = require('./icons')
192
+ await addIcons({
193
+ names,
194
+ from: flags['--from'],
195
+ weight: flags['--weight'],
196
+ variant: flags['--variant'],
197
+ out: flags['--out'],
198
+ })
199
+ },
200
+ },
201
+
164
202
  build: {
165
203
  shorthands: ['b'],
166
204
  description: `Use to pre-build a Tamagui component directory. Use -- to run a command after optimization, then auto-restore files.`,
@@ -346,6 +384,31 @@ $ tamagui migrate --from v1`,
346
384
  })
347
385
  },
348
386
  },
387
+
388
+ 'preview-theme': {
389
+ shorthands: ['preview'],
390
+ description: `Preview a theme configuration in the browser on tamagui.dev/theme`,
391
+ usage: `$ tamagui preview-theme ./theme.json
392
+ $ tamagui preview-theme --theme '{"palettes": ...}'
393
+ $ tamagui preview-theme ./theme.json --print`,
394
+ flags: {
395
+ '--help': Boolean,
396
+ '--theme': String,
397
+ '--url': String,
398
+ '--print': Boolean,
399
+ },
400
+ async run() {
401
+ const { _, ...flags } = arg(this.flags)
402
+ const [_cmd, themeArg] = _
403
+ const { previewTheme } = require('./preview-theme')
404
+ await previewTheme({
405
+ theme: flags['--theme'],
406
+ themeArg,
407
+ url: flags['--url'],
408
+ print: flags['--print'],
409
+ })
410
+ },
411
+ },
349
412
  }
350
413
 
351
414
  type CommandDefinitions = typeof COMMAND_MAP
@@ -1,23 +1,22 @@
1
+ import { generateConfigMarkdown } from '@tamagui/static'
1
2
  import { join } from 'node:path'
2
3
  import * as FS from 'fs-extra'
3
4
  import type { CLIResolvedOptions } from '@tamagui/types'
4
5
 
5
- interface GeneratePromptOptions extends CLIResolvedOptions {
6
+ export interface GeneratePromptOptions extends CLIResolvedOptions {
6
7
  output?: string
8
+ styleValueSyntax?: 'string' | 'object' | 'both'
7
9
  }
8
10
 
9
11
  export async function generatePrompt(options: GeneratePromptOptions) {
10
12
  const { paths, output } = options
11
13
 
12
- // Regenerate the config first
14
+ // regenerate the config first
13
15
  const { loadTamagui } = require('@tamagui/static/loadTamagui')
14
16
  process.env.TAMAGUI_KEEP_THEMES = '1'
15
- await loadTamagui({
16
- ...options.tamaguiOptions,
17
- platform: 'web',
18
- })
17
+ await loadTamagui({ ...options.tamaguiOptions, platform: 'web' }, true)
19
18
 
20
- // Read the generated config
19
+ // read the generated config
21
20
  const configPath = join(paths.dotDir, 'tamagui.config.json')
22
21
 
23
22
  if (!FS.existsSync(configPath)) {
@@ -28,514 +27,21 @@ export async function generatePrompt(options: GeneratePromptOptions) {
28
27
 
29
28
  const config = await FS.readJSON(configPath)
30
29
 
31
- // Generate markdown
32
- const markdown = generateMarkdown(config)
30
+ // resolve styleValueSyntax: options -> env -> config setting -> interactive prompt if tty / default 'both'
31
+ const configSetting = config.tamaguiConfig?.settings?.styleValueSyntax
32
+ const explicitChoice =
33
+ options.styleValueSyntax ||
34
+ (process.env.TAMAGUI_STYLE_VALUE_SYNTAX as any) ||
35
+ configSetting
36
+ const { resolveStyleValueSyntax } = require('./setup-prompt')
37
+ const resolvedSyntax = await resolveStyleValueSyntax(explicitChoice)
33
38
 
34
- // Write to file
39
+ // generate markdown
40
+ const markdown = generateConfigMarkdown(config, { styleValueSyntax: resolvedSyntax })
41
+
42
+ // write to file
35
43
  const outputPath = output || join(process.cwd(), 'tamagui-prompt.md')
36
44
  await FS.writeFile(outputPath, markdown, 'utf-8')
37
45
 
38
46
  console.info(`\n ✓ Generated prompt file at ${outputPath}\n`)
39
47
  }
40
-
41
- function generateMarkdown(config: any): string {
42
- const sections: string[] = []
43
-
44
- // Header
45
- sections.push('# Tamagui Configuration\n\n')
46
- sections.push(
47
- 'This document provides an overview of the Tamagui configuration for this project.\n\n'
48
- )
49
-
50
- // Get shorthands for use throughout the document
51
- const shorthands = config.tamaguiConfig?.shorthands || {}
52
- const reverseShorthands: Record<string, string> = {}
53
- for (const [short, full] of Object.entries(shorthands)) {
54
- reverseShorthands[full as string] = short
55
- }
56
-
57
- // Helper function to get the correct property name based on settings
58
- const getPropName = (fullProp: string): string => {
59
- const settings = config.tamaguiConfig?.settings || {}
60
- if (settings.onlyAllowShorthands && reverseShorthands[fullProp]) {
61
- return reverseShorthands[fullProp]
62
- }
63
- return fullProp
64
- }
65
-
66
- // Settings (moved to top)
67
- const settings = config.tamaguiConfig?.settings || {}
68
- if (Object.keys(settings).length > 0) {
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
- )
73
-
74
- if (settings.defaultFont) {
75
- sections.push(`### Default Font: \`${settings.defaultFont}\`\n\n`)
76
- sections.push(
77
- `All text components will use the "${settings.defaultFont}" font family by default.\n\n`
78
- )
79
- }
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
- }
134
-
135
- // Group components by prefix (e.g., Dialog, DialogClose -> Dialog.Close)
136
- // Strategy: Find potential base components and check if others follow the pattern
137
- const componentGroups = new Map<string, Set<string>>()
138
- const processed = new Set<string>()
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]/)
150
- )
151
-
152
- if (children.length > 0) {
153
- // This is a base component with children
154
- componentGroups.set(name, new Set(children))
155
- processed.add(name)
156
- children.forEach((child) => processed.add(child))
157
- }
158
- }
159
-
160
- // Collect standalone components (not part of any group)
161
- const standaloneComponents = allComponents.filter((name) => !processed.has(name))
162
-
163
- componentsSection.push('## Components\n\n')
164
- componentsSection.push('The following components are available:\n\n')
165
-
166
- // Combine and sort all base components (both standalone and those with children)
167
- const allBaseComponents = [
168
- ...standaloneComponents,
169
- ...Array.from(componentGroups.keys()),
170
- ].sort()
171
-
172
- // Output components
173
- for (const name of allBaseComponents) {
174
- componentsSection.push(`- ${name}\n`)
175
-
176
- // If this component has children, output them
177
- if (componentGroups.has(name)) {
178
- const children = Array.from(componentGroups.get(name)!).sort()
179
- for (const child of children) {
180
- const suffix = child.slice(name.length)
181
- componentsSection.push(` - ${name}.${suffix}\n`)
182
- }
183
- }
184
- }
185
-
186
- componentsSection.push('\n')
187
-
188
- // Shorthands
189
- sections.push('## Shorthand Properties\n\n')
190
- sections.push('These shorthand properties are available for styling:\n\n')
191
-
192
- const shorthandEntries = Object.entries(shorthands).sort(([a], [b]) =>
193
- a.localeCompare(b)
194
- )
195
-
196
- sections.push(
197
- shorthandEntries.map(([short, full]) => `- \`${short}\` → \`${full}\``).join('\n')
198
- )
199
- sections.push('\n\n')
200
-
201
- // Themes
202
- sections.push('## Themes\n\n')
203
-
204
- const themes = config.tamaguiConfig?.themes || {}
205
- const themeNames = Object.keys(themes).sort()
206
-
207
- // Parse and hierarchically organize themes
208
- interface ThemeHierarchy {
209
- level1: Set<string> // light/dark
210
- level2: Set<string> // color names (blue, red, etc)
211
- level3: Set<string> // variants (alt1, alt2, etc)
212
- components: Set<string> // Component names
213
- }
214
-
215
- const hierarchy: ThemeHierarchy = {
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
- }
264
- }
265
- }
266
-
267
- sections.push('Themes are organized hierarchically and can be combined:\n\n')
268
-
269
- if (hierarchy.level1.size > 0) {
270
- sections.push('**Level 1 (Base):**\n\n')
271
- sections.push(
272
- Array.from(hierarchy.level1)
273
- .sort()
274
- .map((name) => `- ${name}`)
275
- .join('\n')
276
- )
277
- sections.push('\n\n')
278
- }
279
-
280
- if (hierarchy.level2.size > 0) {
281
- sections.push('**Level 2 (Color Schemes):**\n\n')
282
- sections.push(
283
- Array.from(hierarchy.level2)
284
- .sort()
285
- .map((name) => `- ${name}`)
286
- .join('\n')
287
- )
288
- sections.push('\n\n')
289
- }
290
-
291
- if (hierarchy.level3.size > 0) {
292
- sections.push('**Level 3 (Variants):**\n\n')
293
- sections.push(
294
- Array.from(hierarchy.level3)
295
- .sort()
296
- .map((name) => `- ${name}`)
297
- .join('\n')
298
- )
299
- sections.push('\n\n')
300
- }
301
-
302
- if (hierarchy.components.size > 0) {
303
- sections.push('**Component Themes:**\n\n')
304
- sections.push(
305
- Array.from(hierarchy.components)
306
- .sort()
307
- .map((name) => `- ${name}`)
308
- .join('\n')
309
- )
310
- sections.push('\n\n')
311
- }
312
-
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
- sections.push('**Special props:**\n\n')
350
- sections.push('- `theme="inverse"`: Uses the opposite light or dark sub-theme\n')
351
- sections.push('- `reset`: Reverts to grandparent theme\n\n')
352
-
353
- // Tokens
354
- sections.push('## Tokens\n\n')
355
- sections.push('Tokens are design system values referenced by their bare names.\n\n')
356
-
357
- const tokens = config.tamaguiConfig?.tokens || {}
358
-
359
- // Space tokens
360
- if (tokens.space) {
361
- sections.push('### Space Tokens\n\n')
362
- const spaceTokens = Object.entries(tokens.space).sort(([a], [b]) => {
363
- // Sort numerically where possible
364
- const numA = parseFloat(a)
365
- const numB = parseFloat(b)
366
- if (!isNaN(numA) && !isNaN(numB)) {
367
- return numA - numB
368
- }
369
- return a.localeCompare(b)
370
- })
371
- sections.push(
372
- spaceTokens
373
- .map(([key, value]) => `- \`${key}\`: ${formatTokenValue(value)}`)
374
- .join('\n')
375
- )
376
- sections.push('\n\n')
377
- }
378
-
379
- // Size tokens
380
- if (tokens.size) {
381
- sections.push('### Size Tokens\n\n')
382
- const sizeTokens = Object.entries(tokens.size).sort(([a], [b]) => {
383
- const numA = parseFloat(a)
384
- const numB = parseFloat(b)
385
- if (!isNaN(numA) && !isNaN(numB)) {
386
- return numA - numB
387
- }
388
- return a.localeCompare(b)
389
- })
390
- sections.push(
391
- sizeTokens
392
- .map(([key, value]) => `- \`${key}\`: ${formatTokenValue(value)}`)
393
- .join('\n')
394
- )
395
- sections.push('\n\n')
396
- }
397
-
398
- // Radius tokens
399
- if (tokens.radius) {
400
- sections.push('### Radius Tokens\n\n')
401
- const radiusTokens = Object.entries(tokens.radius).sort(([a], [b]) => {
402
- const numA = parseFloat(a)
403
- const numB = parseFloat(b)
404
- if (!isNaN(numA) && !isNaN(numB)) {
405
- return numA - numB
406
- }
407
- return a.localeCompare(b)
408
- })
409
- sections.push(
410
- radiusTokens
411
- .map(([key, value]) => `- \`${key}\`: ${formatTokenValue(value)}`)
412
- .join('\n')
413
- )
414
- sections.push('\n\n')
415
- }
416
-
417
- // zIndex tokens
418
- if (tokens.zIndex) {
419
- sections.push('### Z-Index Tokens\n\n')
420
- const zIndexTokens = Object.entries(tokens.zIndex).sort(([a], [b]) => {
421
- const numA = parseFloat(a)
422
- const numB = parseFloat(b)
423
- if (!isNaN(numA) && !isNaN(numB)) {
424
- return numA - numB
425
- }
426
- return a.localeCompare(b)
427
- })
428
- sections.push(
429
- zIndexTokens
430
- .map(([key, value]) => `- \`${key}\`: ${formatTokenValue(value)}`)
431
- .join('\n')
432
- )
433
- sections.push('\n\n')
434
- }
435
-
436
- // Color tokens
437
- if (tokens.color) {
438
- sections.push('### Color Tokens\n\n')
439
- const colorTokens = Object.entries(tokens.color).sort(([a], [b]) =>
440
- a.localeCompare(b)
441
- )
442
- sections.push(
443
- colorTokens
444
- .map(([key, value]) => `- \`${key}\`: ${formatTokenValue(value)}`)
445
- .join('\n')
446
- )
447
- sections.push('\n\n')
448
- }
449
-
450
- // Token usage examples
451
- sections.push('### Token Usage\n\n')
452
- 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
-
468
- // Media queries
469
- if (config.tamaguiConfig?.media) {
470
- sections.push('## Media Queries\n\n')
471
- sections.push('Available responsive breakpoints:\n\n')
472
-
473
- const media = config.tamaguiConfig.media
474
- const mediaEntries = Object.entries(media).sort(([a], [b]) => a.localeCompare(b))
475
-
476
- for (const [name, query] of mediaEntries) {
477
- sections.push(`- **${name}**: ${JSON.stringify(query)}\n`)
478
- }
479
- sections.push('\n')
480
-
481
- sections.push('### Media Query Usage\n\n')
482
- sections.push(
483
- 'Media queries can be used as style props or with the `useMedia` hook:\n\n'
484
- )
485
- sections.push('```tsx\n')
486
- sections.push('// As a clause in the same style value\n')
487
-
488
- // Get first media query name as example
489
- const firstMediaName = mediaEntries[0]?.[0]
490
- if (firstMediaName) {
491
- sections.push(`<View ${getPropName('width')}="100% ${firstMediaName}:50%" />\n\n`)
492
- }
493
-
494
- sections.push('// Using the useMedia hook\n')
495
- sections.push('const media = useMedia()\n')
496
- if (firstMediaName) {
497
- sections.push(`if (media.${firstMediaName}) {\n`)
498
- sections.push(' // Render for this breakpoint\n')
499
- sections.push('}\n')
500
- }
501
- sections.push('```\n\n')
502
- }
503
-
504
- // Fonts
505
- if (config.tamaguiConfig?.fonts) {
506
- sections.push('## Fonts\n\n')
507
- sections.push('Available font families:\n\n')
508
-
509
- const fonts = config.tamaguiConfig.fonts
510
- const fontNames = Object.keys(fonts).sort()
511
- sections.push(fontNames.map((name) => `- ${name}`).join('\n'))
512
- sections.push('\n\n')
513
- }
514
-
515
- // Animations
516
- if (config.tamaguiConfig?.animations) {
517
- sections.push('## Animations\n\n')
518
- sections.push('Available animation presets:\n\n')
519
-
520
- const animations = config.tamaguiConfig.animations
521
- if (animations.animations) {
522
- const animationNames = Object.keys(animations.animations).sort()
523
- sections.push(animationNames.map((name) => `- ${name}`).join('\n'))
524
- sections.push('\n\n')
525
- }
526
- }
527
-
528
- // Add components section at the end
529
- sections.push(...componentsSection)
530
-
531
- return sections.join('')
532
- }
533
-
534
- function formatTokenValue(value: any): string {
535
- // If it's an object with a 'val' property (token object), extract the value
536
- if (typeof value === 'object' && value !== null && 'val' in value) {
537
- return String(value.val)
538
- }
539
- // Otherwise, stringify it
540
- return String(value)
541
- }