@nuasite/cms-marker 0.0.64 → 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.
- package/dist/types/manifest-writer.d.ts +11 -2
- package/dist/types/manifest-writer.d.ts.map +1 -1
- package/dist/types/tailwind-colors.d.ts +6 -30
- package/dist/types/tailwind-colors.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dist/types/types.d.ts +25 -5
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/manifest-writer.ts +27 -6
- package/src/tailwind-colors.ts +308 -118
- package/src/types.ts +27 -5
package/src/tailwind-colors.ts
CHANGED
|
@@ -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
|
|
250
|
+
// Build default colors with values
|
|
116
251
|
const defaultColors: TailwindColor[] = DEFAULT_TAILWIND_COLORS.map(name => ({
|
|
117
252
|
name,
|
|
118
|
-
|
|
253
|
+
values: DEFAULT_COLOR_VALUES[name] || {},
|
|
119
254
|
isCustom: false,
|
|
120
255
|
}))
|
|
121
256
|
|
|
122
|
-
// Add special colors
|
|
257
|
+
// Add special colors
|
|
123
258
|
const specialColors: TailwindColor[] = SPECIAL_COLORS.map(name => ({
|
|
124
259
|
name,
|
|
125
|
-
|
|
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,
|
|
276
|
+
const colors = new Map<string, Record<string, string>>()
|
|
146
277
|
|
|
147
|
-
// Find @theme blocks
|
|
148
|
-
const themeBlockPattern = /@theme
|
|
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+))
|
|
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,
|
|
304
|
+
colors.set(colorName, {})
|
|
173
305
|
}
|
|
174
306
|
|
|
175
|
-
|
|
176
|
-
colors.get(colorName)!.add(shade)
|
|
177
|
-
}
|
|
307
|
+
colors.get(colorName)![shade] = value
|
|
178
308
|
}
|
|
179
309
|
}
|
|
180
310
|
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
191
|
-
|
|
321
|
+
return result
|
|
322
|
+
}
|
|
192
323
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
340
|
+
let customTextStyles: Partial<AvailableTextStyles> = {}
|
|
198
341
|
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
//
|
|
214
|
-
const
|
|
215
|
-
|
|
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
|
|
@@ -336,33 +556,3 @@ export function buildColorClass(
|
|
|
336
556
|
}
|
|
337
557
|
return `${prefix}-${colorName}`
|
|
338
558
|
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Generate a hidden HTML string containing all color classes for Tailwind safelist.
|
|
342
|
-
* This ensures Tailwind includes all color utility classes during build,
|
|
343
|
-
* even if they're only used dynamically via the CMS color editor.
|
|
344
|
-
*
|
|
345
|
-
* @param availableColors - The available colors from Tailwind config
|
|
346
|
-
* @returns HTML string with a hidden div containing all color classes
|
|
347
|
-
*/
|
|
348
|
-
export function generateColorSafelistHtml(availableColors: AvailableColors): string {
|
|
349
|
-
const classes: string[] = []
|
|
350
|
-
|
|
351
|
-
for (const color of availableColors.colors) {
|
|
352
|
-
if (color.shades.length === 0) {
|
|
353
|
-
// Special colors without shades (white, black, transparent, etc.)
|
|
354
|
-
if (color.name !== 'transparent' && color.name !== 'current' && color.name !== 'inherit') {
|
|
355
|
-
classes.push(`bg-${color.name}`)
|
|
356
|
-
classes.push(`text-${color.name}`)
|
|
357
|
-
}
|
|
358
|
-
} else {
|
|
359
|
-
// Colors with shades
|
|
360
|
-
for (const shade of color.shades) {
|
|
361
|
-
classes.push(`bg-${color.name}-${shade}`)
|
|
362
|
-
classes.push(`text-${color.name}-${shade}`)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return `<div aria-hidden="true" class="!hidden ${classes.join(' ')}"></div>`
|
|
368
|
-
}
|
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
|
-
/**
|
|
77
|
-
|
|
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
|
}
|
|
@@ -103,8 +103,28 @@ export interface AvailableColors {
|
|
|
103
103
|
defaultColors: string[]
|
|
104
104
|
/** Custom/theme color names */
|
|
105
105
|
customColors: string[]
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
}
|
|
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[]
|
|
108
128
|
}
|
|
109
129
|
|
|
110
130
|
export interface ManifestEntry {
|
|
@@ -203,4 +223,6 @@ export interface CmsManifest {
|
|
|
203
223
|
collections?: Record<string, CollectionEntry>
|
|
204
224
|
/** Available Tailwind colors from the project's config */
|
|
205
225
|
availableColors?: AvailableColors
|
|
226
|
+
/** Available text styles from the project's Tailwind config */
|
|
227
|
+
availableTextStyles?: AvailableTextStyles
|
|
206
228
|
}
|