@nuasite/cms-marker 0.0.63 → 0.0.65

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.
@@ -1,10 +1,9 @@
1
1
  import fs from 'node:fs/promises'
2
2
  import path from 'node:path'
3
- import type { AvailableColors, ColorClasses, TailwindColor } from './types'
3
+ import type { AvailableColors, AvailableTextStyles, ColorClasses, TailwindColor, TextStyleValue } from './types'
4
4
 
5
5
  /**
6
6
  * Default Tailwind CSS v4 color names.
7
- * These are available by default in Tailwind v4.
8
7
  */
9
8
  export const DEFAULT_TAILWIND_COLORS = [
10
9
  'slate',
@@ -41,48 +40,184 @@ export const STANDARD_SHADES = ['50', '100', '200', '300', '400', '500', '600',
41
40
  */
42
41
  export const SPECIAL_COLORS = ['transparent', 'current', 'inherit', 'white', 'black'] as const
43
42
 
43
+ /**
44
+ * Complete Tailwind v4 default color palette with all shade values.
45
+ */
46
+ const DEFAULT_COLOR_VALUES: Record<string, Record<string, string>> = {
47
+ slate: {
48
+ '50': '#f8fafc', '100': '#f1f5f9', '200': '#e2e8f0', '300': '#cbd5e1', '400': '#94a3b8',
49
+ '500': '#64748b', '600': '#475569', '700': '#334155', '800': '#1e293b', '900': '#0f172a', '950': '#020617',
50
+ },
51
+ gray: {
52
+ '50': '#f9fafb', '100': '#f3f4f6', '200': '#e5e7eb', '300': '#d1d5db', '400': '#9ca3af',
53
+ '500': '#6b7280', '600': '#4b5563', '700': '#374151', '800': '#1f2937', '900': '#111827', '950': '#030712',
54
+ },
55
+ zinc: {
56
+ '50': '#fafafa', '100': '#f4f4f5', '200': '#e4e4e7', '300': '#d4d4d8', '400': '#a1a1aa',
57
+ '500': '#71717a', '600': '#52525b', '700': '#3f3f46', '800': '#27272a', '900': '#18181b', '950': '#09090b',
58
+ },
59
+ neutral: {
60
+ '50': '#fafafa', '100': '#f5f5f5', '200': '#e5e5e5', '300': '#d4d4d4', '400': '#a3a3a3',
61
+ '500': '#737373', '600': '#525252', '700': '#404040', '800': '#262626', '900': '#171717', '950': '#0a0a0a',
62
+ },
63
+ stone: {
64
+ '50': '#fafaf9', '100': '#f5f5f4', '200': '#e7e5e4', '300': '#d6d3d1', '400': '#a8a29e',
65
+ '500': '#78716c', '600': '#57534e', '700': '#44403c', '800': '#292524', '900': '#1c1917', '950': '#0c0a09',
66
+ },
67
+ red: {
68
+ '50': '#fef2f2', '100': '#fee2e2', '200': '#fecaca', '300': '#fca5a5', '400': '#f87171',
69
+ '500': '#ef4444', '600': '#dc2626', '700': '#b91c1c', '800': '#991b1b', '900': '#7f1d1d', '950': '#450a0a',
70
+ },
71
+ orange: {
72
+ '50': '#fff7ed', '100': '#ffedd5', '200': '#fed7aa', '300': '#fdba74', '400': '#fb923c',
73
+ '500': '#f97316', '600': '#ea580c', '700': '#c2410c', '800': '#9a3412', '900': '#7c2d12', '950': '#431407',
74
+ },
75
+ amber: {
76
+ '50': '#fffbeb', '100': '#fef3c7', '200': '#fde68a', '300': '#fcd34d', '400': '#fbbf24',
77
+ '500': '#f59e0b', '600': '#d97706', '700': '#b45309', '800': '#92400e', '900': '#78350f', '950': '#451a03',
78
+ },
79
+ yellow: {
80
+ '50': '#fefce8', '100': '#fef9c3', '200': '#fef08a', '300': '#fde047', '400': '#facc15',
81
+ '500': '#eab308', '600': '#ca8a04', '700': '#a16207', '800': '#854d0e', '900': '#713f12', '950': '#422006',
82
+ },
83
+ lime: {
84
+ '50': '#f7fee7', '100': '#ecfccb', '200': '#d9f99d', '300': '#bef264', '400': '#a3e635',
85
+ '500': '#84cc16', '600': '#65a30d', '700': '#4d7c0f', '800': '#3f6212', '900': '#365314', '950': '#1a2e05',
86
+ },
87
+ green: {
88
+ '50': '#f0fdf4', '100': '#dcfce7', '200': '#bbf7d0', '300': '#86efac', '400': '#4ade80',
89
+ '500': '#22c55e', '600': '#16a34a', '700': '#15803d', '800': '#166534', '900': '#14532d', '950': '#052e16',
90
+ },
91
+ emerald: {
92
+ '50': '#ecfdf5', '100': '#d1fae5', '200': '#a7f3d0', '300': '#6ee7b7', '400': '#34d399',
93
+ '500': '#10b981', '600': '#059669', '700': '#047857', '800': '#065f46', '900': '#064e3b', '950': '#022c22',
94
+ },
95
+ teal: {
96
+ '50': '#f0fdfa', '100': '#ccfbf1', '200': '#99f6e4', '300': '#5eead4', '400': '#2dd4bf',
97
+ '500': '#14b8a6', '600': '#0d9488', '700': '#0f766e', '800': '#115e59', '900': '#134e4a', '950': '#042f2e',
98
+ },
99
+ cyan: {
100
+ '50': '#ecfeff', '100': '#cffafe', '200': '#a5f3fc', '300': '#67e8f9', '400': '#22d3ee',
101
+ '500': '#06b6d4', '600': '#0891b2', '700': '#0e7490', '800': '#155e75', '900': '#164e63', '950': '#083344',
102
+ },
103
+ sky: {
104
+ '50': '#f0f9ff', '100': '#e0f2fe', '200': '#bae6fd', '300': '#7dd3fc', '400': '#38bdf8',
105
+ '500': '#0ea5e9', '600': '#0284c7', '700': '#0369a1', '800': '#075985', '900': '#0c4a6e', '950': '#082f49',
106
+ },
107
+ blue: {
108
+ '50': '#eff6ff', '100': '#dbeafe', '200': '#bfdbfe', '300': '#93c5fd', '400': '#60a5fa',
109
+ '500': '#3b82f6', '600': '#2563eb', '700': '#1d4ed8', '800': '#1e40af', '900': '#1e3a8a', '950': '#172554',
110
+ },
111
+ indigo: {
112
+ '50': '#eef2ff', '100': '#e0e7ff', '200': '#c7d2fe', '300': '#a5b4fc', '400': '#818cf8',
113
+ '500': '#6366f1', '600': '#4f46e5', '700': '#4338ca', '800': '#3730a3', '900': '#312e81', '950': '#1e1b4b',
114
+ },
115
+ violet: {
116
+ '50': '#f5f3ff', '100': '#ede9fe', '200': '#ddd6fe', '300': '#c4b5fd', '400': '#a78bfa',
117
+ '500': '#8b5cf6', '600': '#7c3aed', '700': '#6d28d9', '800': '#5b21b6', '900': '#4c1d95', '950': '#2e1065',
118
+ },
119
+ purple: {
120
+ '50': '#faf5ff', '100': '#f3e8ff', '200': '#e9d5ff', '300': '#d8b4fe', '400': '#c084fc',
121
+ '500': '#a855f7', '600': '#9333ea', '700': '#7e22ce', '800': '#6b21a8', '900': '#581c87', '950': '#3b0764',
122
+ },
123
+ fuchsia: {
124
+ '50': '#fdf4ff', '100': '#fae8ff', '200': '#f5d0fe', '300': '#f0abfc', '400': '#e879f9',
125
+ '500': '#d946ef', '600': '#c026d3', '700': '#a21caf', '800': '#86198f', '900': '#701a75', '950': '#4a044e',
126
+ },
127
+ pink: {
128
+ '50': '#fdf2f8', '100': '#fce7f3', '200': '#fbcfe8', '300': '#f9a8d4', '400': '#f472b6',
129
+ '500': '#ec4899', '600': '#db2777', '700': '#be185d', '800': '#9d174d', '900': '#831843', '950': '#500724',
130
+ },
131
+ rose: {
132
+ '50': '#fff1f2', '100': '#ffe4e6', '200': '#fecdd3', '300': '#fda4af', '400': '#fb7185',
133
+ '500': '#f43f5e', '600': '#e11d48', '700': '#be123c', '800': '#9f1239', '900': '#881337', '950': '#4c0519',
134
+ },
135
+ }
136
+
137
+ /**
138
+ * Special color values.
139
+ */
140
+ const SPECIAL_COLOR_VALUES: Record<string, string> = {
141
+ transparent: 'transparent',
142
+ current: 'currentColor',
143
+ inherit: 'inherit',
144
+ white: '#ffffff',
145
+ black: '#000000',
146
+ }
147
+
148
+ /**
149
+ * Default Tailwind v4 font weight values.
150
+ */
151
+ const DEFAULT_FONT_WEIGHTS: TextStyleValue[] = [
152
+ { class: 'font-thin', label: 'Thin', css: { fontWeight: '100' } },
153
+ { class: 'font-extralight', label: 'Extra Light', css: { fontWeight: '200' } },
154
+ { class: 'font-light', label: 'Light', css: { fontWeight: '300' } },
155
+ { class: 'font-normal', label: 'Normal', css: { fontWeight: '400' } },
156
+ { class: 'font-medium', label: 'Medium', css: { fontWeight: '500' } },
157
+ { class: 'font-semibold', label: 'Semibold', css: { fontWeight: '600' } },
158
+ { class: 'font-bold', label: 'Bold', css: { fontWeight: '700' } },
159
+ { class: 'font-extrabold', label: 'Extra Bold', css: { fontWeight: '800' } },
160
+ { class: 'font-black', label: 'Black', css: { fontWeight: '900' } },
161
+ ]
162
+
163
+ /**
164
+ * Default Tailwind v4 font size values.
165
+ */
166
+ const DEFAULT_FONT_SIZES: TextStyleValue[] = [
167
+ { class: 'text-xs', label: 'XS', css: { fontSize: '0.75rem', lineHeight: '1rem' } },
168
+ { class: 'text-sm', label: 'SM', css: { fontSize: '0.875rem', lineHeight: '1.25rem' } },
169
+ { class: 'text-base', label: 'Base', css: { fontSize: '1rem', lineHeight: '1.5rem' } },
170
+ { class: 'text-lg', label: 'LG', css: { fontSize: '1.125rem', lineHeight: '1.75rem' } },
171
+ { class: 'text-xl', label: 'XL', css: { fontSize: '1.25rem', lineHeight: '1.75rem' } },
172
+ { class: 'text-2xl', label: '2XL', css: { fontSize: '1.5rem', lineHeight: '2rem' } },
173
+ { class: 'text-3xl', label: '3XL', css: { fontSize: '1.875rem', lineHeight: '2.25rem' } },
174
+ { class: 'text-4xl', label: '4XL', css: { fontSize: '2.25rem', lineHeight: '2.5rem' } },
175
+ { class: 'text-5xl', label: '5XL', css: { fontSize: '3rem', lineHeight: '1' } },
176
+ { class: 'text-6xl', label: '6XL', css: { fontSize: '3.75rem', lineHeight: '1' } },
177
+ { class: 'text-7xl', label: '7XL', css: { fontSize: '4.5rem', lineHeight: '1' } },
178
+ { class: 'text-8xl', label: '8XL', css: { fontSize: '6rem', lineHeight: '1' } },
179
+ { class: 'text-9xl', label: '9XL', css: { fontSize: '8rem', lineHeight: '1' } },
180
+ ]
181
+
182
+ /**
183
+ * Default text decoration values.
184
+ */
185
+ const DEFAULT_TEXT_DECORATIONS: TextStyleValue[] = [
186
+ { class: 'no-underline', label: 'None', css: { textDecoration: 'none' } },
187
+ { class: 'underline', label: 'Underline', css: { textDecoration: 'underline' } },
188
+ { class: 'overline', label: 'Overline', css: { textDecoration: 'overline' } },
189
+ { class: 'line-through', label: 'Strikethrough', css: { textDecoration: 'line-through' } },
190
+ ]
191
+
192
+ /**
193
+ * Default font style values.
194
+ */
195
+ const DEFAULT_FONT_STYLES: TextStyleValue[] = [
196
+ { class: 'not-italic', label: 'Normal', css: { fontStyle: 'normal' } },
197
+ { class: 'italic', label: 'Italic', css: { fontStyle: 'italic' } },
198
+ ]
199
+
44
200
  /**
45
201
  * Build a regex pattern for matching color classes.
46
- * Matches either:
47
- * - Known default/special colors (e.g., bg-red, text-white)
48
- * - Any color name followed by a shade number (e.g., bg-primary-500)
49
202
  */
50
203
  function buildColorPattern(prefix: string): RegExp {
51
204
  const colorNames = [...DEFAULT_TAILWIND_COLORS, ...SPECIAL_COLORS].join('|')
52
- // Match either: known-color (with optional shade) OR any-name-with-shade (to support custom colors)
53
205
  return new RegExp(`^${prefix}-((?:${colorNames})(?:-(\\d+))?|([a-z]+)-(\\d+))$`)
54
206
  }
55
207
 
56
208
  /**
57
209
  * Regex patterns to match Tailwind color classes.
58
- * These patterns are specific to color utilities and won't match other utilities
59
- * like text-lg, text-center, bg-fixed, etc.
60
210
  */
61
211
  const COLOR_CLASS_PATTERNS = {
62
- // Matches: bg-red-500, bg-primary-500, bg-white, bg-transparent
63
212
  bg: buildColorPattern('bg'),
64
- // Matches: text-red-500, text-primary-500, text-white (NOT text-lg, text-center)
65
213
  text: buildColorPattern('text'),
66
- // Matches: border-red-500, border-primary-500
67
214
  border: buildColorPattern('border'),
68
- // Matches: hover:bg-red-500
69
215
  hoverBg: buildColorPattern('hover:bg'),
70
- // Matches: hover:text-red-500
71
216
  hoverText: buildColorPattern('hover:text'),
72
217
  }
73
218
 
74
219
  /**
75
- * Parse Tailwind v4 CSS config to extract available colors.
76
- * Tailwind v4 uses CSS-based configuration with @theme directive.
77
- *
78
- * Example CSS:
79
- * ```css
80
- * @theme {
81
- * --color-primary-50: #eff6ff;
82
- * --color-primary-500: #3b82f6;
83
- * --color-accent: #f59e0b;
84
- * }
85
- * ```
220
+ * Parse Tailwind v4 CSS config to extract available colors with their values.
86
221
  */
87
222
  export async function parseTailwindConfig(projectRoot: string = process.cwd()): Promise<AvailableColors> {
88
223
  // Tailwind v4 CSS files to search
@@ -112,17 +247,17 @@ export async function parseTailwindConfig(projectRoot: string = process.cwd()):
112
247
  }
113
248
  }
114
249
 
115
- // Build default colors list
250
+ // Build default colors with values
116
251
  const defaultColors: TailwindColor[] = DEFAULT_TAILWIND_COLORS.map(name => ({
117
252
  name,
118
- shades: [...STANDARD_SHADES],
253
+ values: DEFAULT_COLOR_VALUES[name] || {},
119
254
  isCustom: false,
120
255
  }))
121
256
 
122
- // Add special colors (no shades)
257
+ // Add special colors
123
258
  const specialColors: TailwindColor[] = SPECIAL_COLORS.map(name => ({
124
259
  name,
125
- shades: [],
260
+ values: { '': SPECIAL_COLOR_VALUES[name] || name },
126
261
  isCustom: false,
127
262
  }))
128
263
 
@@ -135,95 +270,192 @@ export async function parseTailwindConfig(projectRoot: string = process.cwd()):
135
270
 
136
271
  /**
137
272
  * Extract custom colors from Tailwind v4 CSS @theme block.
138
- *
139
- * Looks for patterns like:
140
- * - --color-primary-50: #value;
141
- * - --color-primary: #value;
142
- * - --color-accent-500: oklch(...);
273
+ * Extracts both color names and their actual CSS values.
143
274
  */
144
275
  function extractColorsFromCss(content: string): TailwindColor[] {
145
- const colors = new Map<string, Set<string>>()
276
+ const colors = new Map<string, Record<string, string>>()
146
277
 
147
- // Find @theme blocks
148
- const themeBlockPattern = /@theme\s*\{([^}]+)\}/gs
278
+ // Find @theme blocks (including inline)
279
+ const themeBlockPattern = /@theme(?:\s+inline)?\s*\{([^}]+)\}/gs
149
280
  let themeMatch: RegExpExecArray | null
150
281
 
151
282
  while ((themeMatch = themeBlockPattern.exec(content)) !== null) {
152
283
  const themeContent = themeMatch[1]
153
284
  if (!themeContent) continue
154
285
 
155
- // Find all --color-* definitions
286
+ // Find all --color-* definitions with their values
156
287
  // Pattern: --color-{name}-{shade}: value; or --color-{name}: value;
157
- const colorVarPattern = /--color-([a-z]+)(?:-(\d+))?:/gi
288
+ const colorVarPattern = /--color-([a-z]+)(?:-(\d+))?:\s*([^;]+);/gi
158
289
  let colorMatch: RegExpExecArray | null
159
290
 
160
291
  while ((colorMatch = colorVarPattern.exec(themeContent)) !== null) {
161
292
  const colorName = colorMatch[1]?.toLowerCase()
162
- const shade = colorMatch[2]
293
+ const shade = colorMatch[2] || ''
294
+ const value = colorMatch[3]?.trim()
163
295
 
164
- if (!colorName) continue
296
+ if (!colorName || !value) continue
165
297
 
166
- // Skip if it's a default color
298
+ // Skip if it's a default color (we already have values for those)
167
299
  if (DEFAULT_TAILWIND_COLORS.includes(colorName as any)) {
168
300
  continue
169
301
  }
170
302
 
171
303
  if (!colors.has(colorName)) {
172
- colors.set(colorName, new Set())
304
+ colors.set(colorName, {})
173
305
  }
174
306
 
175
- if (shade) {
176
- colors.get(colorName)!.add(shade)
177
- }
307
+ colors.get(colorName)![shade] = value
178
308
  }
179
309
  }
180
310
 
181
- // Also check for inline @theme definitions (Tailwind v4 can be inline too)
182
- // Pattern: @theme inline { ... }
183
- const inlineThemePattern = /@theme\s+inline\s*\{([^}]+)\}/gs
184
- let inlineMatch: RegExpExecArray | null
185
-
186
- while ((inlineMatch = inlineThemePattern.exec(content)) !== null) {
187
- const themeContent = inlineMatch[1]
188
- if (!themeContent) continue
311
+ // Convert to TailwindColor array
312
+ const result: TailwindColor[] = []
313
+ for (const [name, values] of colors) {
314
+ result.push({
315
+ name,
316
+ values,
317
+ isCustom: true,
318
+ })
319
+ }
189
320
 
190
- const colorVarPattern = /--color-([a-z]+)(?:-(\d+))?:/gi
191
- let colorMatch: RegExpExecArray | null
321
+ return result
322
+ }
192
323
 
193
- while ((colorMatch = colorVarPattern.exec(themeContent)) !== null) {
194
- const colorName = colorMatch[1]?.toLowerCase()
195
- const shade = colorMatch[2]
324
+ /**
325
+ * Parse Tailwind v4 CSS config to extract available text styles.
326
+ */
327
+ export async function parseTextStyles(projectRoot: string = process.cwd()): Promise<AvailableTextStyles> {
328
+ // Tailwind v4 CSS files to search
329
+ const cssFiles = [
330
+ 'src/styles/global.css',
331
+ 'src/styles/tailwind.css',
332
+ 'src/styles/app.css',
333
+ 'src/app.css',
334
+ 'src/global.css',
335
+ 'src/index.css',
336
+ 'app/globals.css',
337
+ 'styles/globals.css',
338
+ ]
196
339
 
197
- if (!colorName) continue
340
+ let customTextStyles: Partial<AvailableTextStyles> = {}
198
341
 
199
- if (DEFAULT_TAILWIND_COLORS.includes(colorName as any)) {
200
- continue
342
+ for (const cssFile of cssFiles) {
343
+ const fullPath = path.join(projectRoot, cssFile)
344
+ try {
345
+ const content = await fs.readFile(fullPath, 'utf-8')
346
+ customTextStyles = extractTextStylesFromCss(content)
347
+ // If we found any custom styles, use this file
348
+ if (Object.values(customTextStyles).some(arr => arr && arr.length > 0)) {
349
+ break
201
350
  }
351
+ } catch {
352
+ // File doesn't exist, continue
353
+ }
354
+ }
202
355
 
203
- if (!colors.has(colorName)) {
204
- colors.set(colorName, new Set())
205
- }
356
+ // Merge custom styles with defaults (custom overrides default)
357
+ return {
358
+ fontWeight: mergeTextStyles(DEFAULT_FONT_WEIGHTS, customTextStyles.fontWeight),
359
+ fontSize: mergeTextStyles(DEFAULT_FONT_SIZES, customTextStyles.fontSize),
360
+ textDecoration: mergeTextStyles(DEFAULT_TEXT_DECORATIONS, customTextStyles.textDecoration),
361
+ fontStyle: mergeTextStyles(DEFAULT_FONT_STYLES, customTextStyles.fontStyle),
362
+ }
363
+ }
206
364
 
207
- if (shade) {
208
- colors.get(colorName)!.add(shade)
209
- }
365
+ /**
366
+ * Merge custom text styles with defaults.
367
+ * Custom styles with matching class names override defaults.
368
+ */
369
+ function mergeTextStyles(defaults: TextStyleValue[], custom?: TextStyleValue[]): TextStyleValue[] {
370
+ if (!custom || custom.length === 0) {
371
+ return defaults
372
+ }
373
+
374
+ const customByClass = new Map(custom.map(s => [s.class, s]))
375
+ const result: TextStyleValue[] = []
376
+
377
+ // Update defaults with custom overrides
378
+ for (const def of defaults) {
379
+ const customStyle = customByClass.get(def.class)
380
+ if (customStyle) {
381
+ result.push(customStyle)
382
+ customByClass.delete(def.class)
383
+ } else {
384
+ result.push(def)
210
385
  }
211
386
  }
212
387
 
213
- // Convert to TailwindColor array
214
- const result: TailwindColor[] = []
215
- for (const [name, shades] of colors) {
216
- const sortedShades = Array.from(shades).sort((a, b) => parseInt(a) - parseInt(b))
217
- result.push({
218
- name,
219
- shades: sortedShades.length > 0 ? sortedShades : [],
220
- isCustom: true,
221
- })
388
+ // Add any remaining custom styles that weren't overrides
389
+ for (const style of customByClass.values()) {
390
+ result.push(style)
222
391
  }
223
392
 
224
393
  return result
225
394
  }
226
395
 
396
+ /**
397
+ * Extract custom text styles from Tailwind v4 CSS @theme block.
398
+ */
399
+ function extractTextStylesFromCss(content: string): Partial<AvailableTextStyles> {
400
+ const fontWeights: TextStyleValue[] = []
401
+ const fontSizes: TextStyleValue[] = []
402
+
403
+ // Find @theme blocks (including inline)
404
+ const themeBlockPattern = /@theme(?:\s+inline)?\s*\{([^}]+)\}/gs
405
+ let themeMatch: RegExpExecArray | null
406
+
407
+ while ((themeMatch = themeBlockPattern.exec(content)) !== null) {
408
+ const themeContent = themeMatch[1]
409
+ if (!themeContent) continue
410
+
411
+ // Extract font-weight overrides: --font-weight-{name}: value;
412
+ const fontWeightPattern = /--font-weight-([a-z]+):\s*([^;]+);/gi
413
+ let weightMatch: RegExpExecArray | null
414
+ while ((weightMatch = fontWeightPattern.exec(themeContent)) !== null) {
415
+ const name = weightMatch[1]?.toLowerCase()
416
+ const value = weightMatch[2]?.trim()
417
+ if (!name || !value) continue
418
+
419
+ fontWeights.push({
420
+ class: `font-${name}`,
421
+ label: name.charAt(0).toUpperCase() + name.slice(1),
422
+ css: { fontWeight: value },
423
+ })
424
+ }
425
+
426
+ // Extract font-size overrides: --font-size-{name}: value;
427
+ // Also look for corresponding line-height: --line-height-{name}: value;
428
+ const fontSizePattern = /--font-size-([a-z0-9]+):\s*([^;]+);/gi
429
+ let sizeMatch: RegExpExecArray | null
430
+ while ((sizeMatch = fontSizePattern.exec(themeContent)) !== null) {
431
+ const name = sizeMatch[1]?.toLowerCase()
432
+ const value = sizeMatch[2]?.trim()
433
+ if (!name || !value) continue
434
+
435
+ // Try to find matching line-height
436
+ const lineHeightPattern = new RegExp(`--line-height-${name}:\\s*([^;]+);`, 'i')
437
+ const lineHeightMatch = themeContent.match(lineHeightPattern)
438
+ const lineHeight = lineHeightMatch?.[1]?.trim()
439
+
440
+ const css: Record<string, string> = { fontSize: value }
441
+ if (lineHeight) {
442
+ css.lineHeight = lineHeight
443
+ }
444
+
445
+ fontSizes.push({
446
+ class: `text-${name}`,
447
+ label: name.toUpperCase(),
448
+ css,
449
+ })
450
+ }
451
+ }
452
+
453
+ return {
454
+ fontWeight: fontWeights.length > 0 ? fontWeights : undefined,
455
+ fontSize: fontSizes.length > 0 ? fontSizes : undefined,
456
+ }
457
+ }
458
+
227
459
  /**
228
460
  * Extract color classes from an element's class attribute.
229
461
  */
@@ -235,12 +467,10 @@ export function extractColorClasses(classAttr: string | null | undefined): Color
235
467
  const allColorClasses: string[] = []
236
468
 
237
469
  for (const cls of classes) {
238
- // Check each pattern
239
470
  for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
240
471
  const match = cls.match(pattern)
241
472
  if (match) {
242
473
  allColorClasses.push(cls)
243
- // Assign to appropriate field
244
474
  if (!(key in colorClasses)) {
245
475
  ;(colorClasses as any)[key] = cls
246
476
  }
@@ -266,10 +496,6 @@ export function isColorClass(className: string): boolean {
266
496
 
267
497
  /**
268
498
  * Generate a new class string with a color class replaced.
269
- * @param currentClasses - Current class attribute value
270
- * @param oldColorClass - The color class to replace (e.g., 'bg-blue-500')
271
- * @param newColorClass - The new color class (e.g., 'bg-red-500')
272
- * @returns New class string with the replacement
273
499
  */
274
500
  export function replaceColorClass(
275
501
  currentClasses: string,
@@ -283,8 +509,6 @@ export function replaceColorClass(
283
509
 
284
510
  /**
285
511
  * Get the color type from a color class.
286
- * @param colorClass - e.g., 'bg-blue-500', 'text-white', 'hover:bg-red-600'
287
- * @returns The type: 'bg', 'text', 'border', 'hoverBg', 'hoverText', or undefined
288
512
  */
289
513
  export function getColorType(colorClass: string): keyof ColorClasses | undefined {
290
514
  for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
@@ -297,8 +521,6 @@ export function getColorType(colorClass: string): keyof ColorClasses | undefined
297
521
 
298
522
  /**
299
523
  * Parse a color class into its components.
300
- * @param colorClass - e.g., 'bg-blue-500', 'text-white', 'hover:bg-red-600'
301
- * @returns Object with prefix, colorName, and shade (if any)
302
524
  */
303
525
  export function parseColorClass(colorClass: string): {
304
526
  prefix: string
@@ -306,11 +528,9 @@ export function parseColorClass(colorClass: string): {
306
528
  shade?: string
307
529
  isHover: boolean
308
530
  } | undefined {
309
- // Handle hover prefix
310
531
  const isHover = colorClass.startsWith('hover:')
311
532
  const classWithoutHover = isHover ? colorClass.slice(6) : colorClass
312
533
 
313
- // Match prefix-color-shade or prefix-color
314
534
  const match = classWithoutHover.match(/^(bg|text|border)-([a-z]+)(?:-(\d+))?$/)
315
535
 
316
536
  if (!match) return undefined
package/src/types.ts CHANGED
@@ -69,12 +69,12 @@ export interface ContentConstraints {
69
69
  allowedTags?: string[]
70
70
  }
71
71
 
72
- /** Represents a single Tailwind color with its shades */
72
+ /** Represents a single Tailwind color with its shades and values */
73
73
  export interface TailwindColor {
74
74
  /** Color name (e.g., 'red', 'blue', 'primary') */
75
75
  name: string
76
- /** Available shades (e.g., ['50', '100', '200', ..., '900', '950']) */
77
- shades: string[]
76
+ /** Map of shade to CSS color value (e.g., { '500': '#ef4444', '600': '#dc2626' }) */
77
+ values: Record<string, string>
78
78
  /** Whether this is a custom/theme color vs default Tailwind */
79
79
  isCustom?: boolean
80
80
  }
@@ -105,6 +105,28 @@ export interface AvailableColors {
105
105
  customColors: string[]
106
106
  }
107
107
 
108
+ /** Text style value with class name and CSS properties */
109
+ export interface TextStyleValue {
110
+ /** Tailwind class name (e.g., 'font-bold', 'text-xl') */
111
+ class: string
112
+ /** Display label for UI */
113
+ label: string
114
+ /** CSS properties to apply (e.g., { fontWeight: '700' }) */
115
+ css: Record<string, string>
116
+ }
117
+
118
+ /** Available text styles from Tailwind config */
119
+ export interface AvailableTextStyles {
120
+ /** Font weight options (font-normal, font-bold, etc.) */
121
+ fontWeight: TextStyleValue[]
122
+ /** Font size options (text-xs, text-sm, text-base, etc.) */
123
+ fontSize: TextStyleValue[]
124
+ /** Text decoration options (underline, line-through, etc.) */
125
+ textDecoration: TextStyleValue[]
126
+ /** Font style options (italic, not-italic) */
127
+ fontStyle: TextStyleValue[]
128
+ }
129
+
108
130
  export interface ManifestEntry {
109
131
  id: string
110
132
  tag: string
@@ -201,4 +223,6 @@ export interface CmsManifest {
201
223
  collections?: Record<string, CollectionEntry>
202
224
  /** Available Tailwind colors from the project's config */
203
225
  availableColors?: AvailableColors
226
+ /** Available text styles from the project's Tailwind config */
227
+ availableTextStyles?: AvailableTextStyles
204
228
  }