@nuasite/cms 0.1.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.
Files changed (269) hide show
  1. package/README.md +237 -0
  2. package/dist/src/build-processor.d.ts +20 -0
  3. package/dist/src/build-processor.d.ts.map +1 -0
  4. package/dist/src/collection-scanner.d.ts +6 -0
  5. package/dist/src/collection-scanner.d.ts.map +1 -0
  6. package/dist/src/component-registry.d.ts +63 -0
  7. package/dist/src/component-registry.d.ts.map +1 -0
  8. package/dist/src/config.d.ts +24 -0
  9. package/dist/src/config.d.ts.map +1 -0
  10. package/dist/src/dev-middleware.d.ts +20 -0
  11. package/dist/src/dev-middleware.d.ts.map +1 -0
  12. package/dist/src/editor/ai.d.ts +60 -0
  13. package/dist/src/editor/ai.d.ts.map +1 -0
  14. package/dist/src/editor/api.d.ts +140 -0
  15. package/dist/src/editor/api.d.ts.map +1 -0
  16. package/dist/src/editor/color-utils.d.ts +106 -0
  17. package/dist/src/editor/color-utils.d.ts.map +1 -0
  18. package/dist/src/editor/components/ai-chat.d.ts +11 -0
  19. package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
  20. package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
  21. package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
  22. package/dist/src/editor/components/attribute-editor.d.ts +5 -0
  23. package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
  24. package/dist/src/editor/components/block-editor.d.ts +12 -0
  25. package/dist/src/editor/components/block-editor.d.ts.map +1 -0
  26. package/dist/src/editor/components/collections-browser.d.ts +2 -0
  27. package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
  28. package/dist/src/editor/components/color-toolbar.d.ts +12 -0
  29. package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
  30. package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
  31. package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
  32. package/dist/src/editor/components/create-page-modal.d.ts +2 -0
  33. package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
  34. package/dist/src/editor/components/editable-highlights.d.ts +9 -0
  35. package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
  36. package/dist/src/editor/components/error-boundary.d.ts +32 -0
  37. package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
  38. package/dist/src/editor/components/fields.d.ts +75 -0
  39. package/dist/src/editor/components/fields.d.ts.map +1 -0
  40. package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
  41. package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
  42. package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
  43. package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
  44. package/dist/src/editor/components/image-overlay.d.ts +12 -0
  45. package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
  46. package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
  47. package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
  48. package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
  49. package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
  50. package/dist/src/editor/components/media-library.d.ts +2 -0
  51. package/dist/src/editor/components/media-library.d.ts.map +1 -0
  52. package/dist/src/editor/components/outline.d.ts +21 -0
  53. package/dist/src/editor/components/outline.d.ts.map +1 -0
  54. package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
  55. package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
  56. package/dist/src/editor/components/seo-editor.d.ts +2 -0
  57. package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
  58. package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
  59. package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
  60. package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
  61. package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
  62. package/dist/src/editor/components/toast/toast.d.ts +7 -0
  63. package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
  64. package/dist/src/editor/components/toast/types.d.ts +7 -0
  65. package/dist/src/editor/components/toast/types.d.ts.map +1 -0
  66. package/dist/src/editor/components/toolbar.d.ts +21 -0
  67. package/dist/src/editor/components/toolbar.d.ts.map +1 -0
  68. package/dist/src/editor/config.d.ts +4 -0
  69. package/dist/src/editor/config.d.ts.map +1 -0
  70. package/dist/src/editor/constants.d.ts +101 -0
  71. package/dist/src/editor/constants.d.ts.map +1 -0
  72. package/dist/src/editor/context.d.ts +14 -0
  73. package/dist/src/editor/context.d.ts.map +1 -0
  74. package/dist/src/editor/dom.d.ts +77 -0
  75. package/dist/src/editor/dom.d.ts.map +1 -0
  76. package/dist/src/editor/editor.d.ts +64 -0
  77. package/dist/src/editor/editor.d.ts.map +1 -0
  78. package/dist/src/editor/history.d.ts +20 -0
  79. package/dist/src/editor/history.d.ts.map +1 -0
  80. package/dist/src/editor/hooks/index.d.ts +14 -0
  81. package/dist/src/editor/hooks/index.d.ts.map +1 -0
  82. package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
  83. package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
  84. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
  85. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
  86. package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
  87. package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
  88. package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
  89. package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
  90. package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
  91. package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
  92. package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
  93. package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
  94. package/dist/src/editor/hooks/utils.d.ts +32 -0
  95. package/dist/src/editor/hooks/utils.d.ts.map +1 -0
  96. package/dist/src/editor/index.d.ts +12 -0
  97. package/dist/src/editor/index.d.ts.map +1 -0
  98. package/dist/src/editor/lib/cn.d.ts +3 -0
  99. package/dist/src/editor/lib/cn.d.ts.map +1 -0
  100. package/dist/src/editor/manifest.d.ts +19 -0
  101. package/dist/src/editor/manifest.d.ts.map +1 -0
  102. package/dist/src/editor/markdown-api.d.ts +36 -0
  103. package/dist/src/editor/markdown-api.d.ts.map +1 -0
  104. package/dist/src/editor/signals.d.ts +242 -0
  105. package/dist/src/editor/signals.d.ts.map +1 -0
  106. package/dist/src/editor/storage.d.ts +27 -0
  107. package/dist/src/editor/storage.d.ts.map +1 -0
  108. package/dist/src/editor/text-styling.d.ts +350 -0
  109. package/dist/src/editor/text-styling.d.ts.map +1 -0
  110. package/dist/src/editor/themes.d.ts +38 -0
  111. package/dist/src/editor/themes.d.ts.map +1 -0
  112. package/dist/src/editor/types.d.ts +454 -0
  113. package/dist/src/editor/types.d.ts.map +1 -0
  114. package/dist/src/error-collector.d.ts +56 -0
  115. package/dist/src/error-collector.d.ts.map +1 -0
  116. package/dist/src/handlers/component-ops.d.ts +34 -0
  117. package/dist/src/handlers/component-ops.d.ts.map +1 -0
  118. package/dist/src/handlers/markdown-ops.d.ts +41 -0
  119. package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
  120. package/dist/src/handlers/request-utils.d.ts +20 -0
  121. package/dist/src/handlers/request-utils.d.ts.map +1 -0
  122. package/dist/src/handlers/source-writer.d.ts +51 -0
  123. package/dist/src/handlers/source-writer.d.ts.map +1 -0
  124. package/dist/src/html-processor.d.ts +63 -0
  125. package/dist/src/html-processor.d.ts.map +1 -0
  126. package/dist/src/index.d.ts +41 -0
  127. package/dist/src/index.d.ts.map +1 -0
  128. package/dist/src/manifest-writer.d.ts +111 -0
  129. package/dist/src/manifest-writer.d.ts.map +1 -0
  130. package/dist/src/media/contember.d.ts +15 -0
  131. package/dist/src/media/contember.d.ts.map +1 -0
  132. package/dist/src/media/local.d.ts +9 -0
  133. package/dist/src/media/local.d.ts.map +1 -0
  134. package/dist/src/media/s3.d.ts +12 -0
  135. package/dist/src/media/s3.d.ts.map +1 -0
  136. package/dist/src/media/types.d.ts +40 -0
  137. package/dist/src/media/types.d.ts.map +1 -0
  138. package/dist/src/preview-generator.d.ts +19 -0
  139. package/dist/src/preview-generator.d.ts.map +1 -0
  140. package/dist/src/seo-processor.d.ts +23 -0
  141. package/dist/src/seo-processor.d.ts.map +1 -0
  142. package/dist/src/source-finder/ast-extractors.d.ts +35 -0
  143. package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
  144. package/dist/src/source-finder/ast-parser.d.ts +16 -0
  145. package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
  146. package/dist/src/source-finder/cache.d.ts +18 -0
  147. package/dist/src/source-finder/cache.d.ts.map +1 -0
  148. package/dist/src/source-finder/collection-finder.d.ts +29 -0
  149. package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
  150. package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
  151. package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
  152. package/dist/src/source-finder/element-finder.d.ts +42 -0
  153. package/dist/src/source-finder/element-finder.d.ts.map +1 -0
  154. package/dist/src/source-finder/image-finder.d.ts +24 -0
  155. package/dist/src/source-finder/image-finder.d.ts.map +1 -0
  156. package/dist/src/source-finder/index.d.ts +9 -0
  157. package/dist/src/source-finder/index.d.ts.map +1 -0
  158. package/dist/src/source-finder/search-index.d.ts +27 -0
  159. package/dist/src/source-finder/search-index.d.ts.map +1 -0
  160. package/dist/src/source-finder/snippet-utils.d.ts +90 -0
  161. package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
  162. package/dist/src/source-finder/source-lookup.d.ts +16 -0
  163. package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
  164. package/dist/src/source-finder/types.d.ts +167 -0
  165. package/dist/src/source-finder/types.d.ts.map +1 -0
  166. package/dist/src/source-finder/variable-extraction.d.ts +37 -0
  167. package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
  168. package/dist/src/tailwind-colors.d.ts +54 -0
  169. package/dist/src/tailwind-colors.d.ts.map +1 -0
  170. package/dist/src/tsconfig.tsbuildinfo +1 -0
  171. package/dist/src/types.d.ts +367 -0
  172. package/dist/src/types.d.ts.map +1 -0
  173. package/dist/src/utils.d.ts +61 -0
  174. package/dist/src/utils.d.ts.map +1 -0
  175. package/dist/src/vite-plugin.d.ts +14 -0
  176. package/dist/src/vite-plugin.d.ts.map +1 -0
  177. package/dist/types/tsconfig.tsbuildinfo +1 -0
  178. package/package.json +80 -0
  179. package/src/build-processor.ts +784 -0
  180. package/src/collection-scanner.ts +304 -0
  181. package/src/component-registry.ts +393 -0
  182. package/src/config.ts +74 -0
  183. package/src/dev-middleware.ts +525 -0
  184. package/src/dist/src/tsconfig.tsbuildinfo +1 -0
  185. package/src/editor/ai.ts +185 -0
  186. package/src/editor/api.ts +513 -0
  187. package/src/editor/color-utils.ts +556 -0
  188. package/src/editor/components/ai-chat.tsx +632 -0
  189. package/src/editor/components/ai-tooltip.tsx +179 -0
  190. package/src/editor/components/attribute-editor.tsx +596 -0
  191. package/src/editor/components/block-editor.tsx +546 -0
  192. package/src/editor/components/collections-browser.tsx +248 -0
  193. package/src/editor/components/color-toolbar.tsx +314 -0
  194. package/src/editor/components/confirm-dialog.tsx +69 -0
  195. package/src/editor/components/create-page-modal.tsx +163 -0
  196. package/src/editor/components/editable-highlights.tsx +260 -0
  197. package/src/editor/components/error-boundary.tsx +87 -0
  198. package/src/editor/components/fields.tsx +387 -0
  199. package/src/editor/components/frontmatter-fields.tsx +469 -0
  200. package/src/editor/components/highlight-overlay.ts +229 -0
  201. package/src/editor/components/image-overlay.tsx +230 -0
  202. package/src/editor/components/markdown-editor-overlay.tsx +505 -0
  203. package/src/editor/components/markdown-inline-editor.tsx +780 -0
  204. package/src/editor/components/media-library.tsx +297 -0
  205. package/src/editor/components/outline.tsx +402 -0
  206. package/src/editor/components/redirect-countdown.tsx +45 -0
  207. package/src/editor/components/seo-editor.tsx +498 -0
  208. package/src/editor/components/text-style-toolbar.tsx +362 -0
  209. package/src/editor/components/toast/toast-container.tsx +15 -0
  210. package/src/editor/components/toast/toast.tsx +49 -0
  211. package/src/editor/components/toast/types.ts +7 -0
  212. package/src/editor/components/toolbar.tsx +366 -0
  213. package/src/editor/config.ts +12 -0
  214. package/src/editor/constants.ts +106 -0
  215. package/src/editor/context.tsx +38 -0
  216. package/src/editor/dom.ts +357 -0
  217. package/src/editor/editor.ts +1510 -0
  218. package/src/editor/env.d.ts +4 -0
  219. package/src/editor/history.ts +355 -0
  220. package/src/editor/hooks/index.ts +19 -0
  221. package/src/editor/hooks/useAIHandlers.ts +345 -0
  222. package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
  223. package/src/editor/hooks/useElementDetection.ts +284 -0
  224. package/src/editor/hooks/useImageHoverDetection.ts +102 -0
  225. package/src/editor/hooks/useTextSelection.ts +187 -0
  226. package/src/editor/hooks/useTooltipState.ts +126 -0
  227. package/src/editor/hooks/utils.ts +101 -0
  228. package/src/editor/index.tsx +481 -0
  229. package/src/editor/lib/cn.ts +4 -0
  230. package/src/editor/manifest.ts +25 -0
  231. package/src/editor/markdown-api.ts +209 -0
  232. package/src/editor/signals.ts +1351 -0
  233. package/src/editor/storage.ts +266 -0
  234. package/src/editor/styles.css +465 -0
  235. package/src/editor/text-styling.ts +773 -0
  236. package/src/editor/themes.ts +210 -0
  237. package/src/editor/types.ts +591 -0
  238. package/src/error-collector.ts +106 -0
  239. package/src/handlers/component-ops.ts +463 -0
  240. package/src/handlers/markdown-ops.ts +202 -0
  241. package/src/handlers/request-utils.ts +151 -0
  242. package/src/handlers/source-writer.ts +649 -0
  243. package/src/html-processor.ts +1108 -0
  244. package/src/index.ts +284 -0
  245. package/src/manifest-writer.ts +371 -0
  246. package/src/media/contember.ts +84 -0
  247. package/src/media/local.ts +114 -0
  248. package/src/media/s3.ts +133 -0
  249. package/src/media/types.ts +33 -0
  250. package/src/preview-generator.ts +293 -0
  251. package/src/seo-processor.ts +567 -0
  252. package/src/source-finder/ast-extractors.ts +185 -0
  253. package/src/source-finder/ast-parser.ts +150 -0
  254. package/src/source-finder/cache.ts +76 -0
  255. package/src/source-finder/collection-finder.ts +335 -0
  256. package/src/source-finder/cross-file-tracker.ts +741 -0
  257. package/src/source-finder/element-finder.ts +387 -0
  258. package/src/source-finder/image-finder.ts +283 -0
  259. package/src/source-finder/index.ts +37 -0
  260. package/src/source-finder/search-index.ts +525 -0
  261. package/src/source-finder/snippet-utils.ts +668 -0
  262. package/src/source-finder/source-lookup.ts +200 -0
  263. package/src/source-finder/types.ts +210 -0
  264. package/src/source-finder/variable-extraction.ts +406 -0
  265. package/src/tailwind-colors.ts +874 -0
  266. package/src/tsconfig.json +25 -0
  267. package/src/types.ts +406 -0
  268. package/src/utils.ts +186 -0
  269. package/src/vite-plugin.ts +42 -0
@@ -0,0 +1,874 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { getProjectRoot } from './config'
4
+ import type { Attribute, AvailableColors, AvailableTextStyles, TailwindColor, TextStyleValue } from './types'
5
+
6
+ /**
7
+ * Default Tailwind CSS v4 color names.
8
+ */
9
+ export const DEFAULT_TAILWIND_COLORS = [
10
+ 'slate',
11
+ 'gray',
12
+ 'zinc',
13
+ 'neutral',
14
+ 'stone',
15
+ 'red',
16
+ 'orange',
17
+ 'amber',
18
+ 'yellow',
19
+ 'lime',
20
+ 'green',
21
+ 'emerald',
22
+ 'teal',
23
+ 'cyan',
24
+ 'sky',
25
+ 'blue',
26
+ 'indigo',
27
+ 'violet',
28
+ 'purple',
29
+ 'fuchsia',
30
+ 'pink',
31
+ 'rose',
32
+ ] as const
33
+
34
+ /**
35
+ * Standard Tailwind color shades.
36
+ */
37
+ export const STANDARD_SHADES = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] as const
38
+
39
+ /**
40
+ * Special color values that don't have shades.
41
+ */
42
+ export const SPECIAL_COLORS = ['transparent', 'current', 'inherit', 'white', 'black'] as const
43
+
44
+ /**
45
+ * Complete Tailwind v4 default color palette with all shade values.
46
+ */
47
+ const DEFAULT_COLOR_VALUES: Record<string, Record<string, string>> = {
48
+ slate: {
49
+ '50': '#f8fafc',
50
+ '100': '#f1f5f9',
51
+ '200': '#e2e8f0',
52
+ '300': '#cbd5e1',
53
+ '400': '#94a3b8',
54
+ '500': '#64748b',
55
+ '600': '#475569',
56
+ '700': '#334155',
57
+ '800': '#1e293b',
58
+ '900': '#0f172a',
59
+ '950': '#020617',
60
+ },
61
+ gray: {
62
+ '50': '#f9fafb',
63
+ '100': '#f3f4f6',
64
+ '200': '#e5e7eb',
65
+ '300': '#d1d5db',
66
+ '400': '#9ca3af',
67
+ '500': '#6b7280',
68
+ '600': '#4b5563',
69
+ '700': '#374151',
70
+ '800': '#1f2937',
71
+ '900': '#111827',
72
+ '950': '#030712',
73
+ },
74
+ zinc: {
75
+ '50': '#fafafa',
76
+ '100': '#f4f4f5',
77
+ '200': '#e4e4e7',
78
+ '300': '#d4d4d8',
79
+ '400': '#a1a1aa',
80
+ '500': '#71717a',
81
+ '600': '#52525b',
82
+ '700': '#3f3f46',
83
+ '800': '#27272a',
84
+ '900': '#18181b',
85
+ '950': '#09090b',
86
+ },
87
+ neutral: {
88
+ '50': '#fafafa',
89
+ '100': '#f5f5f5',
90
+ '200': '#e5e5e5',
91
+ '300': '#d4d4d4',
92
+ '400': '#a3a3a3',
93
+ '500': '#737373',
94
+ '600': '#525252',
95
+ '700': '#404040',
96
+ '800': '#262626',
97
+ '900': '#171717',
98
+ '950': '#0a0a0a',
99
+ },
100
+ stone: {
101
+ '50': '#fafaf9',
102
+ '100': '#f5f5f4',
103
+ '200': '#e7e5e4',
104
+ '300': '#d6d3d1',
105
+ '400': '#a8a29e',
106
+ '500': '#78716c',
107
+ '600': '#57534e',
108
+ '700': '#44403c',
109
+ '800': '#292524',
110
+ '900': '#1c1917',
111
+ '950': '#0c0a09',
112
+ },
113
+ red: {
114
+ '50': '#fef2f2',
115
+ '100': '#fee2e2',
116
+ '200': '#fecaca',
117
+ '300': '#fca5a5',
118
+ '400': '#f87171',
119
+ '500': '#ef4444',
120
+ '600': '#dc2626',
121
+ '700': '#b91c1c',
122
+ '800': '#991b1b',
123
+ '900': '#7f1d1d',
124
+ '950': '#450a0a',
125
+ },
126
+ orange: {
127
+ '50': '#fff7ed',
128
+ '100': '#ffedd5',
129
+ '200': '#fed7aa',
130
+ '300': '#fdba74',
131
+ '400': '#fb923c',
132
+ '500': '#f97316',
133
+ '600': '#ea580c',
134
+ '700': '#c2410c',
135
+ '800': '#9a3412',
136
+ '900': '#7c2d12',
137
+ '950': '#431407',
138
+ },
139
+ amber: {
140
+ '50': '#fffbeb',
141
+ '100': '#fef3c7',
142
+ '200': '#fde68a',
143
+ '300': '#fcd34d',
144
+ '400': '#fbbf24',
145
+ '500': '#f59e0b',
146
+ '600': '#d97706',
147
+ '700': '#b45309',
148
+ '800': '#92400e',
149
+ '900': '#78350f',
150
+ '950': '#451a03',
151
+ },
152
+ yellow: {
153
+ '50': '#fefce8',
154
+ '100': '#fef9c3',
155
+ '200': '#fef08a',
156
+ '300': '#fde047',
157
+ '400': '#facc15',
158
+ '500': '#eab308',
159
+ '600': '#ca8a04',
160
+ '700': '#a16207',
161
+ '800': '#854d0e',
162
+ '900': '#713f12',
163
+ '950': '#422006',
164
+ },
165
+ lime: {
166
+ '50': '#f7fee7',
167
+ '100': '#ecfccb',
168
+ '200': '#d9f99d',
169
+ '300': '#bef264',
170
+ '400': '#a3e635',
171
+ '500': '#84cc16',
172
+ '600': '#65a30d',
173
+ '700': '#4d7c0f',
174
+ '800': '#3f6212',
175
+ '900': '#365314',
176
+ '950': '#1a2e05',
177
+ },
178
+ green: {
179
+ '50': '#f0fdf4',
180
+ '100': '#dcfce7',
181
+ '200': '#bbf7d0',
182
+ '300': '#86efac',
183
+ '400': '#4ade80',
184
+ '500': '#22c55e',
185
+ '600': '#16a34a',
186
+ '700': '#15803d',
187
+ '800': '#166534',
188
+ '900': '#14532d',
189
+ '950': '#052e16',
190
+ },
191
+ emerald: {
192
+ '50': '#ecfdf5',
193
+ '100': '#d1fae5',
194
+ '200': '#a7f3d0',
195
+ '300': '#6ee7b7',
196
+ '400': '#34d399',
197
+ '500': '#10b981',
198
+ '600': '#059669',
199
+ '700': '#047857',
200
+ '800': '#065f46',
201
+ '900': '#064e3b',
202
+ '950': '#022c22',
203
+ },
204
+ teal: {
205
+ '50': '#f0fdfa',
206
+ '100': '#ccfbf1',
207
+ '200': '#99f6e4',
208
+ '300': '#5eead4',
209
+ '400': '#2dd4bf',
210
+ '500': '#14b8a6',
211
+ '600': '#0d9488',
212
+ '700': '#0f766e',
213
+ '800': '#115e59',
214
+ '900': '#134e4a',
215
+ '950': '#042f2e',
216
+ },
217
+ cyan: {
218
+ '50': '#ecfeff',
219
+ '100': '#cffafe',
220
+ '200': '#a5f3fc',
221
+ '300': '#67e8f9',
222
+ '400': '#22d3ee',
223
+ '500': '#06b6d4',
224
+ '600': '#0891b2',
225
+ '700': '#0e7490',
226
+ '800': '#155e75',
227
+ '900': '#164e63',
228
+ '950': '#083344',
229
+ },
230
+ sky: {
231
+ '50': '#f0f9ff',
232
+ '100': '#e0f2fe',
233
+ '200': '#bae6fd',
234
+ '300': '#7dd3fc',
235
+ '400': '#38bdf8',
236
+ '500': '#0ea5e9',
237
+ '600': '#0284c7',
238
+ '700': '#0369a1',
239
+ '800': '#075985',
240
+ '900': '#0c4a6e',
241
+ '950': '#082f49',
242
+ },
243
+ blue: {
244
+ '50': '#eff6ff',
245
+ '100': '#dbeafe',
246
+ '200': '#bfdbfe',
247
+ '300': '#93c5fd',
248
+ '400': '#60a5fa',
249
+ '500': '#3b82f6',
250
+ '600': '#2563eb',
251
+ '700': '#1d4ed8',
252
+ '800': '#1e40af',
253
+ '900': '#1e3a8a',
254
+ '950': '#172554',
255
+ },
256
+ indigo: {
257
+ '50': '#eef2ff',
258
+ '100': '#e0e7ff',
259
+ '200': '#c7d2fe',
260
+ '300': '#a5b4fc',
261
+ '400': '#818cf8',
262
+ '500': '#6366f1',
263
+ '600': '#4f46e5',
264
+ '700': '#4338ca',
265
+ '800': '#3730a3',
266
+ '900': '#312e81',
267
+ '950': '#1e1b4b',
268
+ },
269
+ violet: {
270
+ '50': '#f5f3ff',
271
+ '100': '#ede9fe',
272
+ '200': '#ddd6fe',
273
+ '300': '#c4b5fd',
274
+ '400': '#a78bfa',
275
+ '500': '#8b5cf6',
276
+ '600': '#7c3aed',
277
+ '700': '#6d28d9',
278
+ '800': '#5b21b6',
279
+ '900': '#4c1d95',
280
+ '950': '#2e1065',
281
+ },
282
+ purple: {
283
+ '50': '#faf5ff',
284
+ '100': '#f3e8ff',
285
+ '200': '#e9d5ff',
286
+ '300': '#d8b4fe',
287
+ '400': '#c084fc',
288
+ '500': '#a855f7',
289
+ '600': '#9333ea',
290
+ '700': '#7e22ce',
291
+ '800': '#6b21a8',
292
+ '900': '#581c87',
293
+ '950': '#3b0764',
294
+ },
295
+ fuchsia: {
296
+ '50': '#fdf4ff',
297
+ '100': '#fae8ff',
298
+ '200': '#f5d0fe',
299
+ '300': '#f0abfc',
300
+ '400': '#e879f9',
301
+ '500': '#d946ef',
302
+ '600': '#c026d3',
303
+ '700': '#a21caf',
304
+ '800': '#86198f',
305
+ '900': '#701a75',
306
+ '950': '#4a044e',
307
+ },
308
+ pink: {
309
+ '50': '#fdf2f8',
310
+ '100': '#fce7f3',
311
+ '200': '#fbcfe8',
312
+ '300': '#f9a8d4',
313
+ '400': '#f472b6',
314
+ '500': '#ec4899',
315
+ '600': '#db2777',
316
+ '700': '#be185d',
317
+ '800': '#9d174d',
318
+ '900': '#831843',
319
+ '950': '#500724',
320
+ },
321
+ rose: {
322
+ '50': '#fff1f2',
323
+ '100': '#ffe4e6',
324
+ '200': '#fecdd3',
325
+ '300': '#fda4af',
326
+ '400': '#fb7185',
327
+ '500': '#f43f5e',
328
+ '600': '#e11d48',
329
+ '700': '#be123c',
330
+ '800': '#9f1239',
331
+ '900': '#881337',
332
+ '950': '#4c0519',
333
+ },
334
+ }
335
+
336
+ /**
337
+ * Special color values.
338
+ */
339
+ const SPECIAL_COLOR_VALUES: Record<string, string> = {
340
+ transparent: 'transparent',
341
+ current: 'currentColor',
342
+ inherit: 'inherit',
343
+ white: '#ffffff',
344
+ black: '#000000',
345
+ }
346
+
347
+ /**
348
+ * Default Tailwind v4 font weight values.
349
+ */
350
+ const DEFAULT_FONT_WEIGHTS: TextStyleValue[] = [
351
+ { class: 'font-thin', label: 'Thin', css: { fontWeight: '100' } },
352
+ { class: 'font-extralight', label: 'Extra Light', css: { fontWeight: '200' } },
353
+ { class: 'font-light', label: 'Light', css: { fontWeight: '300' } },
354
+ { class: 'font-normal', label: 'Normal', css: { fontWeight: '400' } },
355
+ { class: 'font-medium', label: 'Medium', css: { fontWeight: '500' } },
356
+ { class: 'font-semibold', label: 'Semibold', css: { fontWeight: '600' } },
357
+ { class: 'font-bold', label: 'Bold', css: { fontWeight: '700' } },
358
+ { class: 'font-extrabold', label: 'Extra Bold', css: { fontWeight: '800' } },
359
+ { class: 'font-black', label: 'Black', css: { fontWeight: '900' } },
360
+ ]
361
+
362
+ /**
363
+ * Default Tailwind v4 font size values.
364
+ */
365
+ const DEFAULT_FONT_SIZES: TextStyleValue[] = [
366
+ { class: 'text-xs', label: 'XS', css: { fontSize: '0.75rem', lineHeight: '1rem' } },
367
+ { class: 'text-sm', label: 'SM', css: { fontSize: '0.875rem', lineHeight: '1.25rem' } },
368
+ { class: 'text-base', label: 'Base', css: { fontSize: '1rem', lineHeight: '1.5rem' } },
369
+ { class: 'text-lg', label: 'LG', css: { fontSize: '1.125rem', lineHeight: '1.75rem' } },
370
+ { class: 'text-xl', label: 'XL', css: { fontSize: '1.25rem', lineHeight: '1.75rem' } },
371
+ { class: 'text-2xl', label: '2XL', css: { fontSize: '1.5rem', lineHeight: '2rem' } },
372
+ { class: 'text-3xl', label: '3XL', css: { fontSize: '1.875rem', lineHeight: '2.25rem' } },
373
+ { class: 'text-4xl', label: '4XL', css: { fontSize: '2.25rem', lineHeight: '2.5rem' } },
374
+ { class: 'text-5xl', label: '5XL', css: { fontSize: '3rem', lineHeight: '1' } },
375
+ { class: 'text-6xl', label: '6XL', css: { fontSize: '3.75rem', lineHeight: '1' } },
376
+ { class: 'text-7xl', label: '7XL', css: { fontSize: '4.5rem', lineHeight: '1' } },
377
+ { class: 'text-8xl', label: '8XL', css: { fontSize: '6rem', lineHeight: '1' } },
378
+ { class: 'text-9xl', label: '9XL', css: { fontSize: '8rem', lineHeight: '1' } },
379
+ ]
380
+
381
+ /**
382
+ * Default text decoration values.
383
+ */
384
+ const DEFAULT_TEXT_DECORATIONS: TextStyleValue[] = [
385
+ { class: 'no-underline', label: 'None', css: { textDecoration: 'none' } },
386
+ { class: 'underline', label: 'Underline', css: { textDecoration: 'underline' } },
387
+ { class: 'overline', label: 'Overline', css: { textDecoration: 'overline' } },
388
+ { class: 'line-through', label: 'Strikethrough', css: { textDecoration: 'line-through' } },
389
+ ]
390
+
391
+ /**
392
+ * Default font style values.
393
+ */
394
+ const DEFAULT_FONT_STYLES: TextStyleValue[] = [
395
+ { class: 'not-italic', label: 'Normal', css: { fontStyle: 'normal' } },
396
+ { class: 'italic', label: 'Italic', css: { fontStyle: 'italic' } },
397
+ ]
398
+
399
+ /**
400
+ * Non-color utility suffixes that should not be matched as custom colors.
401
+ * These follow the pattern `prefix-word-number` but are not colors.
402
+ */
403
+ const NON_COLOR_SUFFIXES = ['opacity'] as const
404
+
405
+ /**
406
+ * Build a regex pattern for matching color classes.
407
+ * Matches:
408
+ * - Default colors with optional shades: bg-blue-500, bg-white
409
+ * - Custom theme colors with shades: bg-primary-500
410
+ * - Arbitrary hex values: bg-[#41b883], bg-[#fff]
411
+ * - Arbitrary rgb/hsl values: bg-[rgb(255,0,0)], bg-[hsl(0,100%,50%)]
412
+ * Excludes non-color utilities like opacity.
413
+ */
414
+ function buildColorPattern(prefix: string): RegExp {
415
+ const colorNames = [...DEFAULT_TAILWIND_COLORS, ...SPECIAL_COLORS].join('|')
416
+ const excluded = NON_COLOR_SUFFIXES.join('|')
417
+ // Arbitrary value patterns for colors
418
+ const arbitraryHex = '\\[#[0-9a-fA-F]{3,8}\\]'
419
+ const arbitraryFunc = '\\[(?:rgba?|hsla?)\\([^\\]]+\\)\\]'
420
+ // Match: prefix-(colorName[-shade]?) OR prefix-(customColor-shade) OR prefix-[arbitrary] but NOT prefix-(excluded-number)
421
+ return new RegExp(`^${prefix}-((?:${colorNames})(?:-(\\d+))?|(?!(?:${excluded})-)(\\w+)-(\\d+)|${arbitraryHex}|${arbitraryFunc})$`)
422
+ }
423
+
424
+ /**
425
+ * Build a regex pattern for matching opacity classes.
426
+ */
427
+ function buildOpacityPattern(prefix: string): RegExp {
428
+ return new RegExp(`^${prefix}-opacity-(\\d+)$`)
429
+ }
430
+
431
+ /**
432
+ * Regex patterns to match Tailwind color classes.
433
+ */
434
+ const COLOR_CLASS_PATTERNS = {
435
+ bg: buildColorPattern('bg'),
436
+ text: buildColorPattern('text'),
437
+ border: buildColorPattern('border'),
438
+ hoverBg: buildColorPattern('hover:bg'),
439
+ hoverText: buildColorPattern('hover:text'),
440
+ hoverBorder: buildColorPattern('hover:border'),
441
+ }
442
+
443
+ /**
444
+ * Regex patterns to match Tailwind opacity classes.
445
+ */
446
+ const OPACITY_CLASS_PATTERNS = {
447
+ bgOpacity: buildOpacityPattern('bg'),
448
+ textOpacity: buildOpacityPattern('text'),
449
+ borderOpacity: buildOpacityPattern('border'),
450
+ }
451
+
452
+ /**
453
+ * Regex patterns to match Tailwind gradient color classes.
454
+ */
455
+ const GRADIENT_CLASS_PATTERNS = {
456
+ from: buildColorPattern('from'),
457
+ via: buildColorPattern('via'),
458
+ to: buildColorPattern('to'),
459
+ hoverFrom: buildColorPattern('hover:from'),
460
+ hoverVia: buildColorPattern('hover:via'),
461
+ hoverTo: buildColorPattern('hover:to'),
462
+ }
463
+
464
+ /**
465
+ * Parse Tailwind v4 CSS config to extract available colors with their values.
466
+ */
467
+ export async function parseTailwindConfig(projectRoot: string = getProjectRoot()): Promise<AvailableColors> {
468
+ // Tailwind v4 CSS files to search
469
+ const cssFiles = [
470
+ 'src/styles/global.css',
471
+ 'src/styles/tailwind.css',
472
+ 'src/styles/app.css',
473
+ 'src/app.css',
474
+ 'src/global.css',
475
+ 'src/index.css',
476
+ 'app/globals.css',
477
+ 'styles/globals.css',
478
+ ]
479
+
480
+ let customColors: TailwindColor[] = []
481
+
482
+ for (const cssFile of cssFiles) {
483
+ const fullPath = path.join(projectRoot, cssFile)
484
+ try {
485
+ const content = await fs.readFile(fullPath, 'utf-8')
486
+ customColors = extractColorsFromCss(content)
487
+ if (customColors.length > 0) {
488
+ break
489
+ }
490
+ } catch {
491
+ // File doesn't exist, continue
492
+ }
493
+ }
494
+
495
+ // Build default colors with values
496
+ const defaultColors: TailwindColor[] = DEFAULT_TAILWIND_COLORS.map(name => ({
497
+ name,
498
+ values: DEFAULT_COLOR_VALUES[name] || {},
499
+ isCustom: false,
500
+ }))
501
+
502
+ // Add special colors
503
+ const specialColors: TailwindColor[] = SPECIAL_COLORS.map(name => ({
504
+ name,
505
+ values: { '': SPECIAL_COLOR_VALUES[name] || name },
506
+ isCustom: false,
507
+ }))
508
+
509
+ return {
510
+ colors: [...specialColors, ...defaultColors, ...customColors],
511
+ defaultColors: [...SPECIAL_COLORS, ...DEFAULT_TAILWIND_COLORS],
512
+ customColors: customColors.map(c => c.name),
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Extract custom colors from Tailwind v4 CSS @theme block.
518
+ * Extracts both color names and their actual CSS values.
519
+ */
520
+ function extractColorsFromCss(content: string): TailwindColor[] {
521
+ const colors = new Map<string, Record<string, string>>()
522
+
523
+ // Find @theme blocks (including inline)
524
+ const themeBlockPattern = /@theme(?:\s+inline)?\s*\{([^}]+)\}/gs
525
+ let themeMatch: RegExpExecArray | null
526
+
527
+ while ((themeMatch = themeBlockPattern.exec(content)) !== null) {
528
+ const themeContent = themeMatch[1]
529
+ if (!themeContent) continue
530
+
531
+ // Find all --color-* definitions with their values
532
+ // Pattern: --color-{name}-{shade}: value; or --color-{name}: value;
533
+ const colorVarPattern = /--color-([a-z]+)(?:-(\d+))?:\s*([^;]+);/gi
534
+ let colorMatch: RegExpExecArray | null
535
+
536
+ while ((colorMatch = colorVarPattern.exec(themeContent)) !== null) {
537
+ const colorName = colorMatch[1]?.toLowerCase()
538
+ const shade = colorMatch[2] || ''
539
+ const value = colorMatch[3]?.trim()
540
+
541
+ if (!colorName || !value) continue
542
+
543
+ // Skip if it's a default color (we already have values for those)
544
+ if (DEFAULT_TAILWIND_COLORS.includes(colorName as any)) {
545
+ continue
546
+ }
547
+
548
+ if (!colors.has(colorName)) {
549
+ colors.set(colorName, {})
550
+ }
551
+
552
+ colors.get(colorName)![shade] = value
553
+ }
554
+ }
555
+
556
+ // Convert to TailwindColor array
557
+ const result: TailwindColor[] = []
558
+ for (const [name, values] of colors) {
559
+ result.push({
560
+ name,
561
+ values,
562
+ isCustom: true,
563
+ })
564
+ }
565
+
566
+ return result
567
+ }
568
+
569
+ /**
570
+ * Parse Tailwind v4 CSS config to extract available text styles.
571
+ */
572
+ export async function parseTextStyles(projectRoot: string = getProjectRoot()): Promise<AvailableTextStyles> {
573
+ // Tailwind v4 CSS files to search
574
+ const cssFiles = [
575
+ 'src/styles/global.css',
576
+ 'src/styles/tailwind.css',
577
+ 'src/styles/app.css',
578
+ 'src/app.css',
579
+ 'src/global.css',
580
+ 'src/index.css',
581
+ 'app/globals.css',
582
+ 'styles/globals.css',
583
+ ]
584
+
585
+ let customTextStyles: Partial<AvailableTextStyles> = {}
586
+
587
+ for (const cssFile of cssFiles) {
588
+ const fullPath = path.join(projectRoot, cssFile)
589
+ try {
590
+ const content = await fs.readFile(fullPath, 'utf-8')
591
+ customTextStyles = extractTextStylesFromCss(content)
592
+ // If we found any custom styles, use this file
593
+ if (Object.values(customTextStyles).some(arr => arr && arr.length > 0)) {
594
+ break
595
+ }
596
+ } catch {
597
+ // File doesn't exist, continue
598
+ }
599
+ }
600
+
601
+ // Merge custom styles with defaults (custom overrides default)
602
+ return {
603
+ fontWeight: mergeTextStyles(DEFAULT_FONT_WEIGHTS, customTextStyles.fontWeight),
604
+ fontSize: mergeTextStyles(DEFAULT_FONT_SIZES, customTextStyles.fontSize),
605
+ textDecoration: mergeTextStyles(DEFAULT_TEXT_DECORATIONS, customTextStyles.textDecoration),
606
+ fontStyle: mergeTextStyles(DEFAULT_FONT_STYLES, customTextStyles.fontStyle),
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Merge custom text styles with defaults.
612
+ * Custom styles with matching class names override defaults.
613
+ */
614
+ function mergeTextStyles(defaults: TextStyleValue[], custom?: TextStyleValue[]): TextStyleValue[] {
615
+ if (!custom || custom.length === 0) {
616
+ return defaults
617
+ }
618
+
619
+ const customByClass = new Map(custom.map(s => [s.class, s]))
620
+ const result: TextStyleValue[] = []
621
+
622
+ // Update defaults with custom overrides
623
+ for (const def of defaults) {
624
+ const customStyle = customByClass.get(def.class)
625
+ if (customStyle) {
626
+ result.push(customStyle)
627
+ customByClass.delete(def.class)
628
+ } else {
629
+ result.push(def)
630
+ }
631
+ }
632
+
633
+ // Add any remaining custom styles that weren't overrides
634
+ for (const style of customByClass.values()) {
635
+ result.push(style)
636
+ }
637
+
638
+ return result
639
+ }
640
+
641
+ /**
642
+ * Extract custom text styles from Tailwind v4 CSS @theme block.
643
+ */
644
+ function extractTextStylesFromCss(content: string): Partial<AvailableTextStyles> {
645
+ const fontWeights: TextStyleValue[] = []
646
+ const fontSizes: TextStyleValue[] = []
647
+
648
+ // Find @theme blocks (including inline)
649
+ const themeBlockPattern = /@theme(?:\s+inline)?\s*\{([^}]+)\}/gs
650
+ let themeMatch: RegExpExecArray | null
651
+
652
+ while ((themeMatch = themeBlockPattern.exec(content)) !== null) {
653
+ const themeContent = themeMatch[1]
654
+ if (!themeContent) continue
655
+
656
+ // Extract font-weight overrides: --font-weight-{name}: value;
657
+ const fontWeightPattern = /--font-weight-([a-z]+):\s*([^;]+);/gi
658
+ let weightMatch: RegExpExecArray | null
659
+ while ((weightMatch = fontWeightPattern.exec(themeContent)) !== null) {
660
+ const name = weightMatch[1]?.toLowerCase()
661
+ const value = weightMatch[2]?.trim()
662
+ if (!name || !value) continue
663
+
664
+ fontWeights.push({
665
+ class: `font-${name}`,
666
+ label: name.charAt(0).toUpperCase() + name.slice(1),
667
+ css: { fontWeight: value },
668
+ })
669
+ }
670
+
671
+ // Extract font-size overrides: --font-size-{name}: value;
672
+ // Also look for corresponding line-height: --line-height-{name}: value;
673
+ const fontSizePattern = /--font-size-([a-z0-9]+):\s*([^;]+);/gi
674
+ let sizeMatch: RegExpExecArray | null
675
+ while ((sizeMatch = fontSizePattern.exec(themeContent)) !== null) {
676
+ const name = sizeMatch[1]?.toLowerCase()
677
+ const value = sizeMatch[2]?.trim()
678
+ if (!name || !value) continue
679
+
680
+ // Try to find matching line-height
681
+ const lineHeightPattern = new RegExp(`--line-height-${name}:\\s*([^;]+);`, 'i')
682
+ const lineHeightMatch = themeContent.match(lineHeightPattern)
683
+ const lineHeight = lineHeightMatch?.[1]?.trim()
684
+
685
+ const css: Record<string, string> = { fontSize: value }
686
+ if (lineHeight) {
687
+ css.lineHeight = lineHeight
688
+ }
689
+
690
+ fontSizes.push({
691
+ class: `text-${name}`,
692
+ label: name.toUpperCase(),
693
+ css,
694
+ })
695
+ }
696
+ }
697
+
698
+ return {
699
+ fontWeight: fontWeights.length > 0 ? fontWeights : undefined,
700
+ fontSize: fontSizes.length > 0 ? fontSizes : undefined,
701
+ }
702
+ }
703
+
704
+ /** Flat key names for color class categories */
705
+ const COLOR_FLAT_KEYS: Record<string, string> = {
706
+ // COLOR_CLASS_PATTERNS keys map directly
707
+ bg: 'bg',
708
+ text: 'text',
709
+ border: 'border',
710
+ hoverBg: 'hoverBg',
711
+ hoverText: 'hoverText',
712
+ hoverBorder: 'hoverBorder',
713
+ }
714
+
715
+ const GRADIENT_FLAT_KEYS: Record<string, string> = {
716
+ from: 'gradientFrom',
717
+ via: 'gradientVia',
718
+ to: 'gradientTo',
719
+ hoverFrom: 'hoverGradientFrom',
720
+ hoverVia: 'hoverGradientVia',
721
+ hoverTo: 'hoverGradientTo',
722
+ }
723
+
724
+ /**
725
+ * Extract color classes from an element's class attribute.
726
+ * Returns a flat Record<string, Attribute> with keys like bg, text, gradientFrom, bgOpacity, etc.
727
+ */
728
+ export function extractColorClasses(classAttr: string | null | undefined): Record<string, Attribute> | undefined {
729
+ if (!classAttr) return undefined
730
+
731
+ const classes = classAttr.split(/\s+/).filter(Boolean)
732
+ const result: Record<string, Attribute> = {}
733
+
734
+ for (const cls of classes) {
735
+ let matched = false
736
+
737
+ // Check color patterns
738
+ for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
739
+ if (pattern.test(cls)) {
740
+ const flatKey = COLOR_FLAT_KEYS[key]
741
+ if (flatKey && !(flatKey in result)) {
742
+ result[flatKey] = { value: cls }
743
+ }
744
+ matched = true
745
+ break
746
+ }
747
+ }
748
+
749
+ // Check gradient patterns
750
+ if (!matched) {
751
+ for (const [key, pattern] of Object.entries(GRADIENT_CLASS_PATTERNS)) {
752
+ if (pattern.test(cls)) {
753
+ const flatKey = GRADIENT_FLAT_KEYS[key]
754
+ if (flatKey && !(flatKey in result)) {
755
+ result[flatKey] = { value: cls }
756
+ }
757
+ matched = true
758
+ break
759
+ }
760
+ }
761
+ }
762
+
763
+ // Check opacity patterns
764
+ if (!matched) {
765
+ for (const [key, pattern] of Object.entries(OPACITY_CLASS_PATTERNS)) {
766
+ if (pattern.test(cls)) {
767
+ if (!(key in result)) {
768
+ result[key] = { value: cls }
769
+ }
770
+ break
771
+ }
772
+ }
773
+ }
774
+ }
775
+
776
+ return Object.keys(result).length > 0 ? result : undefined
777
+ }
778
+
779
+ /**
780
+ * Check if a class is a color class (including gradient colors).
781
+ */
782
+ export function isColorClass(className: string): boolean {
783
+ return Object.values(COLOR_CLASS_PATTERNS).some(pattern => pattern.test(className))
784
+ || Object.values(GRADIENT_CLASS_PATTERNS).some(pattern => pattern.test(className))
785
+ }
786
+
787
+ /**
788
+ * Generate a new class string with a color class replaced.
789
+ */
790
+ export function replaceColorClass(
791
+ currentClasses: string,
792
+ oldColorClass: string,
793
+ newColorClass: string,
794
+ ): string {
795
+ const classes = currentClasses.split(/\s+/).filter(Boolean)
796
+ const newClasses = classes.map(cls => cls === oldColorClass ? newColorClass : cls)
797
+ return newClasses.join(' ')
798
+ }
799
+
800
+ /**
801
+ * Get the color type from a color class.
802
+ * Returns the flat key name (e.g., 'bg', 'gradientFrom', 'bgOpacity').
803
+ */
804
+ export function getColorType(colorClass: string): string | undefined {
805
+ for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
806
+ if (pattern.test(colorClass)) {
807
+ return COLOR_FLAT_KEYS[key]
808
+ }
809
+ }
810
+ for (const [key, pattern] of Object.entries(GRADIENT_CLASS_PATTERNS)) {
811
+ if (pattern.test(colorClass)) {
812
+ return GRADIENT_FLAT_KEYS[key]
813
+ }
814
+ }
815
+ for (const [key, pattern] of Object.entries(OPACITY_CLASS_PATTERNS)) {
816
+ if (pattern.test(colorClass)) {
817
+ return key
818
+ }
819
+ }
820
+ return undefined
821
+ }
822
+
823
+ /**
824
+ * Parse a color class into its components.
825
+ */
826
+ export function parseColorClass(colorClass: string): {
827
+ prefix: string
828
+ colorName: string
829
+ shade?: string
830
+ isHover: boolean
831
+ isArbitrary?: boolean
832
+ } | undefined {
833
+ const isHover = colorClass.startsWith('hover:')
834
+ const classWithoutHover = isHover ? colorClass.slice(6) : colorClass
835
+
836
+ // Try matching standard color classes (default colors, custom theme colors, and gradients)
837
+ const standardMatch = classWithoutHover.match(/^(bg|text|border|from|via|to)-([a-z]+)(?:-(\d+))?$/)
838
+ if (standardMatch) {
839
+ return {
840
+ prefix: isHover ? `hover:${standardMatch[1]}` : standardMatch[1]!,
841
+ colorName: standardMatch[2]!,
842
+ shade: standardMatch[3],
843
+ isHover,
844
+ }
845
+ }
846
+
847
+ // Try matching arbitrary value classes like bg-[#41b883] or from-[#41b883]
848
+ const arbitraryMatch = classWithoutHover.match(/^(bg|text|border|from|via|to)-(\[.+\])$/)
849
+ if (arbitraryMatch) {
850
+ return {
851
+ prefix: isHover ? `hover:${arbitraryMatch[1]}` : arbitraryMatch[1]!,
852
+ colorName: arbitraryMatch[2]!,
853
+ shade: undefined,
854
+ isHover,
855
+ isArbitrary: true,
856
+ }
857
+ }
858
+
859
+ return undefined
860
+ }
861
+
862
+ /**
863
+ * Build a color class from components.
864
+ */
865
+ export function buildColorClass(
866
+ prefix: string,
867
+ colorName: string,
868
+ shade?: string,
869
+ ): string {
870
+ if (shade) {
871
+ return `${prefix}-${colorName}-${shade}`
872
+ }
873
+ return `${prefix}-${colorName}`
874
+ }