@iclips/ui 2.0.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/MIGRATION-v3.md +244 -0
  2. package/dist/alert-theme.css +20 -79
  3. package/dist/components/ui/charts/donut-chart.cjs +1 -1
  4. package/dist/components/ui/charts/donut-chart.cjs.map +1 -1
  5. package/dist/components/ui/charts/donut-chart.js +39 -60
  6. package/dist/components/ui/charts/donut-chart.js.map +1 -1
  7. package/dist/components/ui/charts/kpi-card.cjs +1 -1
  8. package/dist/components/ui/charts/kpi-card.cjs.map +1 -1
  9. package/dist/components/ui/charts/kpi-card.js +19 -19
  10. package/dist/components/ui/charts/kpi-card.js.map +1 -1
  11. package/dist/components/ui/data/comment-system.cjs +1 -1
  12. package/dist/components/ui/data/comment-system.cjs.map +1 -1
  13. package/dist/components/ui/data/comment-system.js +67 -67
  14. package/dist/components/ui/data/comment-system.js.map +1 -1
  15. package/dist/components/ui/data/data-table.cjs +1 -1
  16. package/dist/components/ui/data/data-table.cjs.map +1 -1
  17. package/dist/components/ui/data/data-table.js +112 -101
  18. package/dist/components/ui/data/data-table.js.map +1 -1
  19. package/dist/components/ui/data/editorial-calendar.cjs +1 -1
  20. package/dist/components/ui/data/editorial-calendar.cjs.map +1 -1
  21. package/dist/components/ui/data/editorial-calendar.js +291 -291
  22. package/dist/components/ui/data/editorial-calendar.js.map +1 -1
  23. package/dist/components/ui/data/filter-builder.cjs +1 -1
  24. package/dist/components/ui/data/filter-builder.cjs.map +1 -1
  25. package/dist/components/ui/data/filter-builder.js +1 -1
  26. package/dist/components/ui/data/filter-builder.js.map +1 -1
  27. package/dist/components/ui/feedback/empty-state.cjs +1 -1
  28. package/dist/components/ui/feedback/empty-state.cjs.map +1 -1
  29. package/dist/components/ui/feedback/empty-state.js +14 -14
  30. package/dist/components/ui/feedback/empty-state.js.map +1 -1
  31. package/dist/components/ui/feedback/toast.cjs +1 -1
  32. package/dist/components/ui/feedback/toast.cjs.map +1 -1
  33. package/dist/components/ui/feedback/toast.js +53 -22
  34. package/dist/components/ui/feedback/toast.js.map +1 -1
  35. package/dist/components/ui/forms/calendar.cjs +1 -1
  36. package/dist/components/ui/forms/calendar.cjs.map +1 -1
  37. package/dist/components/ui/forms/calendar.js +16 -16
  38. package/dist/components/ui/forms/calendar.js.map +1 -1
  39. package/dist/components/ui/forms/combobox.cjs +1 -1
  40. package/dist/components/ui/forms/combobox.cjs.map +1 -1
  41. package/dist/components/ui/forms/combobox.js +78 -76
  42. package/dist/components/ui/forms/combobox.js.map +1 -1
  43. package/dist/components/ui/forms/file-attachment-list.cjs +1 -1
  44. package/dist/components/ui/forms/file-attachment-list.cjs.map +1 -1
  45. package/dist/components/ui/forms/file-attachment-list.js +4 -4
  46. package/dist/components/ui/forms/file-attachment-list.js.map +1 -1
  47. package/dist/components/ui/forms/file-upload-zone.cjs +2 -2
  48. package/dist/components/ui/forms/file-upload-zone.cjs.map +1 -1
  49. package/dist/components/ui/forms/file-upload-zone.js +55 -49
  50. package/dist/components/ui/forms/file-upload-zone.js.map +1 -1
  51. package/dist/components/ui/forms/form.cjs +1 -1
  52. package/dist/components/ui/forms/form.cjs.map +1 -1
  53. package/dist/components/ui/forms/form.js +11 -11
  54. package/dist/components/ui/forms/form.js.map +1 -1
  55. package/dist/components/ui/forms/input-validation.cjs +1 -1
  56. package/dist/components/ui/forms/input-validation.cjs.map +1 -1
  57. package/dist/components/ui/forms/input-validation.js +20 -20
  58. package/dist/components/ui/forms/input-validation.js.map +1 -1
  59. package/dist/components/ui/forms/multi-select.cjs +1 -1
  60. package/dist/components/ui/forms/multi-select.cjs.map +1 -1
  61. package/dist/components/ui/forms/multi-select.js +69 -67
  62. package/dist/components/ui/forms/multi-select.js.map +1 -1
  63. package/dist/components/ui/forms/radio-group.cjs +1 -1
  64. package/dist/components/ui/forms/radio-group.cjs.map +1 -1
  65. package/dist/components/ui/forms/radio-group.js +19 -19
  66. package/dist/components/ui/forms/radio-group.js.map +1 -1
  67. package/dist/components/ui/forms/social-button.cjs +1 -1
  68. package/dist/components/ui/forms/social-button.cjs.map +1 -1
  69. package/dist/components/ui/forms/social-button.js +6 -6
  70. package/dist/components/ui/forms/social-button.js.map +1 -1
  71. package/dist/components/ui/forms/toggle.cjs +1 -1
  72. package/dist/components/ui/forms/toggle.cjs.map +1 -1
  73. package/dist/components/ui/forms/toggle.js +7 -7
  74. package/dist/components/ui/forms/toggle.js.map +1 -1
  75. package/dist/components/ui/navigation/navigation-menu.cjs +1 -1
  76. package/dist/components/ui/navigation/navigation-menu.cjs.map +1 -1
  77. package/dist/components/ui/navigation/navigation-menu.js +14 -14
  78. package/dist/components/ui/navigation/navigation-menu.js.map +1 -1
  79. package/dist/components/ui/navigation/sidebar.cjs +1 -1
  80. package/dist/components/ui/navigation/sidebar.cjs.map +1 -1
  81. package/dist/components/ui/navigation/sidebar.js +44 -44
  82. package/dist/components/ui/navigation/sidebar.js.map +1 -1
  83. package/dist/components/ui/overlays/context-menu.cjs +1 -1
  84. package/dist/components/ui/overlays/context-menu.cjs.map +1 -1
  85. package/dist/components/ui/overlays/context-menu.js +1 -1
  86. package/dist/components/ui/overlays/context-menu.js.map +1 -1
  87. package/dist/components/ui/overlays/dropdown-menu.cjs +1 -1
  88. package/dist/components/ui/overlays/dropdown-menu.cjs.map +1 -1
  89. package/dist/components/ui/overlays/dropdown-menu.js +5 -5
  90. package/dist/components/ui/overlays/dropdown-menu.js.map +1 -1
  91. package/dist/components/ui/overlays/menubar.cjs +1 -1
  92. package/dist/components/ui/overlays/menubar.cjs.map +1 -1
  93. package/dist/components/ui/overlays/menubar.js +29 -29
  94. package/dist/components/ui/overlays/menubar.js.map +1 -1
  95. package/dist/components/ui/overlays/tooltip.cjs +1 -1
  96. package/dist/components/ui/overlays/tooltip.cjs.map +1 -1
  97. package/dist/components/ui/overlays/tooltip.js +12 -12
  98. package/dist/components/ui/overlays/tooltip.js.map +1 -1
  99. package/dist/components/ui/primitives/avatar.cjs +1 -1
  100. package/dist/components/ui/primitives/avatar.cjs.map +1 -1
  101. package/dist/components/ui/primitives/avatar.js +20 -27
  102. package/dist/components/ui/primitives/avatar.js.map +1 -1
  103. package/dist/components/ui/primitives/badge.cjs +1 -1
  104. package/dist/components/ui/primitives/badge.cjs.map +1 -1
  105. package/dist/components/ui/primitives/badge.js +13 -13
  106. package/dist/components/ui/primitives/badge.js.map +1 -1
  107. package/dist/components/ui/primitives/button.cjs +1 -1
  108. package/dist/components/ui/primitives/button.cjs.map +1 -1
  109. package/dist/components/ui/primitives/button.js +11 -8
  110. package/dist/components/ui/primitives/button.js.map +1 -1
  111. package/dist/index.cjs +1 -1
  112. package/dist/index.css +1 -1
  113. package/dist/index.d.cts +3 -3
  114. package/dist/index.d.ts +3 -3
  115. package/dist/index.js +34 -35
  116. package/dist/index.js.map +1 -1
  117. package/dist/preset.css +1 -2
  118. package/dist/toast-theme.css +100 -64
  119. package/dist/tokens.css +59 -159
  120. package/package.json +5 -3
  121. package/scripts/check-colors.mjs +208 -0
package/dist/tokens.css CHANGED
@@ -11,8 +11,9 @@
11
11
  :root {
12
12
  --font-size: 16px;
13
13
 
14
- /* Custom Color Palette - Base Colors */
15
- /* Grays */
14
+ /* Paleta iClips — só o que algum token semântico usa. Primitivos não viram
15
+ classe (o @theme só mapeia semânticos): escreva bg-primary, nunca bg-purple-500 (check-colors-ignore). */
16
+ /* Neutros — estrutura */
16
17
  --gray-50: #F2F6FA;
17
18
  --gray-100: #E9EEF5;
18
19
  --gray-200: #DAE1EB;
@@ -24,79 +25,23 @@
24
25
  --gray-800: #1A1F26;
25
26
  --gray-900: #000000;
26
27
  --white: #FFFFFF;
27
-
28
- /* Purple (Primary Brand Color) */
28
+
29
+ /* Roxo — ação (exclusivo) */
29
30
  --purple-50: #F6E9FF;
30
31
  --purple-100: #DAA6FF;
31
32
  --purple-200: #BB6DF2;
33
+ --purple-300: #A855E0;
34
+ --purple-400: #913DD0;
32
35
  --purple-500: #7F26BF;
33
- --purple-600: #5B158C;
34
36
  --purple-700: #420F66;
35
-
36
- /* Blue */
37
- --blue-50: #E2E9FF;
38
- --blue-100: #AABCF2;
39
- --blue-200: #667FCC;
40
- --blue-500: #1C2F8C;
41
- --blue-600: #0F1E66;
42
- --blue-700: #061040;
43
-
44
- /* Cyan */
45
- --cyan-50: #D8EFFF;
46
- --cyan-100: #8CCFFF;
47
- --cyan-200: #3DA7F2;
48
- --cyan-500: #0086E6;
49
- --cyan-600: #0053A6;
50
- --cyan-700: #003066;
51
-
52
- /* Teal */
53
- --teal-50: #D4F5F5;
54
- --teal-100: #9DF2F2;
55
- --teal-200: #5CE6E6;
56
- --teal-500: #14CCCC;
57
- --teal-600: #00A7A7;
58
- --teal-700: #045659;
59
-
60
- /* Green */
61
- --green-50: #D3FBDD;
62
- --green-100: #91F2AA;
63
- --green-200: #57D977;
64
- --green-500: #24B347;
65
- --green-600: #007F2A;
66
- --green-700: #004D19;
67
-
68
- /* Yellow */
69
- --yellow-50: #FFF7CC;
70
- --yellow-100: #FFDD99;
71
- --yellow-200: #FFC859;
72
- --yellow-500: #FAB325;
73
- --yellow-600: #E6960B;
74
- --yellow-700: #CC7C04;
75
-
76
- /* Orange */
77
- --orange-50: #FFECE1;
78
- --orange-100: #FFB68C;
79
- --orange-200: #FF8E4C;
80
- --orange-500: #F26924;
81
- --orange-600: #E64D0B;
82
- --orange-700: #BF3000;
83
-
84
- /* Red */
85
- --red-50: #FFE9EC;
86
- --red-100: #FFB2B9;
87
- --red-200: #FF5967;
88
- --red-500: #F22435;
89
- --red-600: #CC0A1A;
90
- --red-700: #99000D;
91
-
92
- /* Pink */
93
- --pink-50: #FFECF9;
94
- --pink-100: #FFB2E8;
95
- --pink-200: #FF66CC;
96
- --pink-500: #E62EA8;
97
- --pink-600: #BF0A89;
98
- --pink-700: #99086D;
99
-
37
+
38
+ /* Estado — feedback, nunca decoração */
39
+ --red-600: #AB4A43;
40
+ --green-600: #427748;
41
+ --yellow-500: #E4BB78;
42
+ --yellow-600: #CFA066;
43
+ --cyan-600: #315687;
44
+
100
45
  /* Semantic Colors - Light Mode */
101
46
  --background: var(--white);
102
47
  --foreground: var(--gray-800);
@@ -112,8 +57,8 @@
112
57
  /* gray-600 (não gray-500): passa AA também sobre `bg-muted`/`bg-accent`
113
58
  (gray-500 dava ~4:1 sobre gray-100). No dark segue gray-300. */
114
59
  --muted-foreground: var(--gray-600);
115
- --accent: var(--purple-50);
116
- --accent-foreground: var(--purple-600);
60
+ --accent: var(--gray-100);
61
+ --accent-foreground: var(--foreground);
117
62
  --destructive: var(--red-600);
118
63
  --destructive-foreground: var(--white);
119
64
  --success: var(--green-600);
@@ -156,13 +101,13 @@
156
101
 
157
102
  /* Charts - Acessibilidade melhorada com Balanceamento Óptico */
158
103
  --chart-1: var(--purple-500); /* Brand Color (Vibrante) */
159
- --chart-2: var(--cyan-600); /* Darkened for contrast */
160
- --chart-3: var(--green-600); /* Darkened for contrast */
161
- --chart-4: var(--orange-600); /* Darkened for contrast */
162
- --chart-5: var(--pink-600); /* Darkened for contrast */
163
- --chart-6: var(--blue-600); /* Darkened for contrast */
164
- --chart-7: var(--teal-600); /* Darkened for contrast */
165
- --chart-8: var(--yellow-600); /* Darkened for contrast */
104
+ --chart-2: var(--gray-400); /* 500: separa de chart-1 em luminância */
105
+ --chart-3: var(--purple-700); /* Darkened for contrast */
106
+ --chart-4: var(--purple-200); /* Darkened for contrast */
107
+ --chart-5: var(--gray-600); /* Darkened for contrast */
108
+ --chart-6: var(--purple-100); /* 200: separa de chart-2 (eram dois azul-marinho) */
109
+ --chart-7: var(--gray-700); /* Darkened for contrast */
110
+ --chart-8: var(--purple-300); /* Darkened for contrast */
166
111
 
167
112
  /* Border Radius */
168
113
  --radius: 0.625rem;
@@ -172,8 +117,10 @@
172
117
  --sidebar-foreground: var(--gray-800);
173
118
  --sidebar-primary: var(--purple-500);
174
119
  --sidebar-primary-foreground: var(--white);
175
- --sidebar-accent: var(--purple-50);
176
- --sidebar-accent-foreground: var(--purple-700);
120
+ --sidebar-accent: var(--gray-100);
121
+ --sidebar-accent-foreground: var(--sidebar-foreground);
122
+ --sidebar-selected: var(--purple-50);
123
+ --sidebar-selected-foreground: var(--purple-700);
177
124
  --sidebar-border: var(--gray-200);
178
125
  --sidebar-ring: var(--purple-500);
179
126
 
@@ -229,14 +176,14 @@
229
176
  --card-foreground: var(--gray-50);
230
177
  --popover: var(--gray-700);
231
178
  --popover-foreground: var(--gray-50);
232
- --primary: var(--purple-200);
179
+ --primary: var(--purple-100);
233
180
  --primary-foreground: var(--gray-900);
234
- --secondary: var(--gray-700);
235
- --secondary-foreground: var(--gray-50);
181
+ --secondary: var(--purple-700);
182
+ --secondary-foreground: var(--purple-100);
236
183
  --muted: var(--gray-600);
237
184
  --muted-foreground: var(--gray-300);
238
- --accent: var(--purple-700);
239
- --accent-foreground: var(--purple-100);
185
+ --accent: var(--gray-600);
186
+ --accent-foreground: var(--gray-50);
240
187
  --destructive: var(--red-600);
241
188
  --destructive-foreground: var(--white);
242
189
  --success: var(--green-600);
@@ -245,6 +192,15 @@
245
192
  --warning-foreground: var(--gray-800);
246
193
  --info: var(--cyan-600);
247
194
  --info-foreground: var(--white);
195
+ /* Redeclarados aqui porque var() dentro de custom property resolve onde é
196
+ declarada: sem isso, um .dark que não esteja no <html> herda a mistura do
197
+ :root, feita com o --foreground claro. 80% também não passa AA sobre
198
+ popover/card escuros; 45% passa até sobre o tint de foco (bg-<token>/20).
199
+ warning mantém 32%, que já passa. */
200
+ --success-emphasis: color-mix(in oklab, var(--success) 45%, var(--foreground));
201
+ --info-emphasis: color-mix(in oklab, var(--info) 45%, var(--foreground));
202
+ --destructive-emphasis: color-mix(in oklab, var(--destructive) 45%, var(--foreground));
203
+ --warning-emphasis: color-mix(in oklab, var(--warning) 32%, var(--foreground));
248
204
  --border: var(--gray-600);
249
205
  --input: var(--gray-600);
250
206
  --input-background: var(--gray-700);
@@ -253,13 +209,13 @@
253
209
 
254
210
  /* Charts - Dark Mode */
255
211
  --chart-1: var(--purple-200);
256
- --chart-2: var(--cyan-200);
257
- --chart-3: var(--green-200);
258
- --chart-4: var(--orange-200);
259
- --chart-5: var(--pink-200);
260
- --chart-6: var(--blue-200);
261
- --chart-7: var(--teal-200);
262
- --chart-8: var(--yellow-200);
212
+ --chart-2: var(--gray-50);
213
+ --chart-3: var(--purple-300);
214
+ --chart-4: var(--purple-50);
215
+ --chart-5: var(--purple-400);
216
+ --chart-6: var(--gray-100);
217
+ --chart-7: var(--gray-500);
218
+ --chart-8: var(--gray-200);
263
219
 
264
220
  /* Sidebar - Dark Mode */
265
221
  --sidebar: var(--gray-800);
@@ -267,7 +223,9 @@
267
223
  --sidebar-primary: var(--purple-200);
268
224
  --sidebar-primary-foreground: var(--gray-900);
269
225
  --sidebar-accent: var(--gray-700);
270
- --sidebar-accent-foreground: var(--purple-100);
226
+ --sidebar-accent-foreground: var(--gray-50);
227
+ --sidebar-selected: var(--purple-700);
228
+ --sidebar-selected-foreground: var(--purple-100);
271
229
  --sidebar-border: var(--gray-600);
272
230
  --sidebar-ring: var(--purple-200);
273
231
  }
@@ -299,6 +257,11 @@
299
257
  }
300
258
 
301
259
  @theme inline {
260
+ --color-*: initial;
261
+ /* ↑ só tokens semânticos viram classe: zera a paleta padrão do Tailwind. */
262
+ --color-white: #FFFFFF;
263
+ --color-black: #000000;
264
+
302
265
  /* Typography */
303
266
  --font-sans: var(--font-sans);
304
267
 
@@ -358,6 +321,8 @@
358
321
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
359
322
  --color-sidebar-accent: var(--sidebar-accent);
360
323
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
324
+ --color-sidebar-selected: var(--sidebar-selected);
325
+ --color-sidebar-selected-foreground: var(--sidebar-selected-foreground);
361
326
  --color-sidebar-border: var(--sidebar-border);
362
327
  --color-sidebar-ring: var(--sidebar-ring);
363
328
 
@@ -403,69 +368,4 @@
403
368
  --spacing-inline-sm: var(--space-inline-sm);
404
369
  --spacing-inline-md: var(--space-inline-md);
405
370
  --spacing-inline-lg: var(--space-inline-lg);
406
-
407
- /* Raw Color Palette */
408
- --color-gray-50: var(--gray-50);
409
- --color-gray-100: var(--gray-100);
410
- --color-gray-200: var(--gray-200);
411
- --color-gray-300: var(--gray-300);
412
- --color-gray-400: var(--gray-400);
413
- --color-gray-500: var(--gray-500);
414
- --color-gray-600: var(--gray-600);
415
- --color-gray-700: var(--gray-700);
416
- --color-gray-800: var(--gray-800);
417
- --color-purple-50: var(--purple-50);
418
- --color-purple-100: var(--purple-100);
419
- --color-purple-200: var(--purple-200);
420
- --color-purple-500: var(--purple-500);
421
- --color-purple-600: var(--purple-600);
422
- --color-purple-700: var(--purple-700);
423
- --color-blue-50: var(--blue-50);
424
- --color-blue-100: var(--blue-100);
425
- --color-blue-200: var(--blue-200);
426
- --color-blue-500: var(--blue-500);
427
- --color-blue-600: var(--blue-600);
428
- --color-blue-700: var(--blue-700);
429
- --color-cyan-50: var(--cyan-50);
430
- --color-cyan-100: var(--cyan-100);
431
- --color-cyan-200: var(--cyan-200);
432
- --color-cyan-500: var(--cyan-500);
433
- --color-cyan-600: var(--cyan-600);
434
- --color-cyan-700: var(--cyan-700);
435
- --color-teal-50: var(--teal-50);
436
- --color-teal-100: var(--teal-100);
437
- --color-teal-200: var(--teal-200);
438
- --color-teal-500: var(--teal-500);
439
- --color-teal-600: var(--teal-600);
440
- --color-teal-700: var(--teal-700);
441
- --color-green-50: var(--green-50);
442
- --color-green-100: var(--green-100);
443
- --color-green-200: var(--green-200);
444
- --color-green-500: var(--green-500);
445
- --color-green-600: var(--green-600);
446
- --color-green-700: var(--green-700);
447
- --color-yellow-50: var(--yellow-50);
448
- --color-yellow-100: var(--yellow-100);
449
- --color-yellow-200: var(--yellow-200);
450
- --color-yellow-500: var(--yellow-500);
451
- --color-yellow-600: var(--yellow-600);
452
- --color-yellow-700: var(--yellow-700);
453
- --color-orange-50: var(--orange-50);
454
- --color-orange-100: var(--orange-100);
455
- --color-orange-200: var(--orange-200);
456
- --color-orange-500: var(--orange-500);
457
- --color-orange-600: var(--orange-600);
458
- --color-orange-700: var(--orange-700);
459
- --color-red-50: var(--red-50);
460
- --color-red-100: var(--red-100);
461
- --color-red-200: var(--red-200);
462
- --color-red-500: var(--red-500);
463
- --color-red-600: var(--red-600);
464
- --color-red-700: var(--red-700);
465
- --color-pink-50: var(--pink-50);
466
- --color-pink-100: var(--pink-100);
467
- --color-pink-200: var(--pink-200);
468
- --color-pink-500: var(--pink-500);
469
- --color-pink-600: var(--pink-600);
470
- --color-pink-700: var(--pink-700);
471
371
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iclips/ui",
3
- "version": "2.0.3",
3
+ "version": "3.0.0",
4
4
  "description": "Design system da iClips — componentes React sobre Radix UI + Tailwind v4, paleta #7F26BF",
5
5
  "author": "iclipsbr",
6
6
  "license": "MIT",
@@ -47,7 +47,9 @@
47
47
  "scripts/codemod-v2-toast.mjs",
48
48
  "README.md",
49
49
  "LICENSE",
50
- "MIGRATION-v2.md"
50
+ "MIGRATION-v2.md",
51
+ "MIGRATION-v3.md",
52
+ "scripts/check-colors.mjs"
51
53
  ],
52
54
  "peerDependencies": {
53
55
  "react": "^18.0.0 || ^19.0.0",
@@ -169,7 +171,7 @@
169
171
  "test:watch": "vitest",
170
172
  "test:coverage": "vitest run --coverage",
171
173
  "typecheck": "tsc --noEmit -p src/tsconfig.json",
172
- "lint": "eslint . --suppressions-location=eslint-suppressions.json && node scripts/check-forwardref.mjs",
174
+ "lint": "eslint . --suppressions-location=eslint-suppressions.json && node scripts/check-forwardref.mjs && node scripts/check-colors.mjs",
173
175
  "lint:fix": "eslint . --fix --suppressions-location=eslint-suppressions.json",
174
176
  "lint:prune-suppressions": "eslint . --prune-suppressions --suppressions-location=eslint-suppressions.json",
175
177
  "format": "prettier --write .",
@@ -0,0 +1,208 @@
1
+ // Aponta cor crua — `bg-gray-100`, `text-purple-600`, `bg-zinc-900`, `var(--teal-500)` —
2
+ // e sugere o token semântico da paleta iClips. Não altera nenhum arquivo.
3
+ //
4
+ // Na lib roda no `npm run lint` (sem argumentos varre `src`). Num app que usa a
5
+ // @iclips/ui, depois de subir para a 3.0.0:
6
+ //
7
+ // node node_modules/@iclips/ui/scripts/check-colors.mjs src
8
+ //
9
+ // Exit: 0 nada encontrado · 1 encontrou cor crua · 2 algum caminho não pôde ser
10
+ // lido (e nada foi encontrado). Uma linha com `check-colors-ignore` é pulada —
11
+ // para documentação que precisa mostrar o que não fazer.
12
+ //
13
+ // node scripts/check-colors.mjs --self-test checa as regras e o CLI
14
+
15
+ import { readdirSync, readFileSync, statSync } from "node:fs";
16
+ import { extname, join, relative } from "node:path";
17
+
18
+ // Paleta padrão do Tailwind v4 — nenhuma gera classe com o preset da 3.0.0.
19
+ const FAMILIAS = "slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose";
20
+ const PREFIXOS = "bg|text|border(?:-[xytrblse])?|ring(?:-offset)?|outline|fill|stroke|from|via|to|divide|placeholder|caret|accent|decoration|shadow";
21
+ const CLASSE = new RegExp(`(?<![\\w-])((?:[\\w\\[\\]&=.-]+:)*(${PREFIXOS})-(${FAMILIAS})-(\\d{2,3})(?:\\/\\d+)?)(?![\\w-])`, "g");
22
+ // Custom properties que deixaram de existir na 3.0.0.
23
+ const VAR_REMOVIDA = /var\(--((?:pink|blue|teal|orange)-\d+|purple-600|(?:red|green|cyan)-(?:50|100|200|500|700)|yellow-(?:50|100|200|700)|cta(?:-foreground)?)\)/g;
24
+
25
+ const EXTENSOES = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".mdx", ".html", ".vue", ".svelte", ".css"]);
26
+ const PULAR_DIR = new Set(["node_modules", "dist", ".git", "storybook-static", "coverage"]);
27
+
28
+ const ESTADO = { red: "destructive", green: "success", emerald: "success", yellow: "warning", amber: "warning", cyan: "info", sky: "info", blue: "info" };
29
+ const NEUTRO = new Set(["slate", "gray", "zinc", "neutral", "stone"]);
30
+
31
+ export function sugerir(classe) {
32
+ const m = new RegExp(`^(?:.*:)?(${PREFIXOS})-(${FAMILIAS})-(\\d{2,3})`).exec(classe);
33
+ if (!m) return null;
34
+ const [, prefixo, familia, degrauTxt] = m;
35
+ const degrau = Number(degrauTxt);
36
+ const base = prefixo.replace(/-[xytrblse]$/, "");
37
+ if (NEUTRO.has(familia)) {
38
+ if (base === "bg") return degrau <= 50 ? "bg-background ou bg-muted/40" : degrau <= 200 ? "bg-muted" : degrau >= 700 ? "bg-card (superfície escura)" : "bg-muted";
39
+ if (base === "text") return degrau >= 700 ? "text-foreground" : degrau >= 400 ? "text-muted-foreground" : "text-muted-foreground (400 ou mais claro nunca em texto)";
40
+ if (base === "border" || base === "divide" || base === "outline") return degrau >= 300 ? `${base}-input` : `${base}-border`;
41
+ if (base === "ring") return "ring-ring";
42
+ }
43
+ if (familia === "purple") {
44
+ if (base === "bg") return degrau <= 100 ? "bg-secondary (seleção)" : "bg-primary";
45
+ if (base === "text") return degrau >= 700 ? "text-secondary-foreground" : "text-primary";
46
+ if (base === "ring") return "ring-ring";
47
+ if (base === "border" || base === "outline") return `${base}-primary`;
48
+ }
49
+ const estado = ESTADO[familia];
50
+ if (estado) {
51
+ if (base === "text") return `text-${estado}-emphasis`;
52
+ if (base === "bg") return degrau <= 100 ? `bg-${estado}/10` : `bg-${estado}`;
53
+ if (base === "border") return `border-${estado}/20`;
54
+ return `${base}-${estado}`;
55
+ }
56
+ return "sem equivalente — a paleta iClips não tem esta cor; use um token semântico ou var(--chart-N) em dados";
57
+ }
58
+
59
+ export function varrerTexto(texto) {
60
+ const achados = [];
61
+ texto.split(/\r?\n/).forEach((linha, i) => {
62
+ if (linha.includes("check-colors-ignore")) return;
63
+ for (const m of linha.matchAll(CLASSE)) achados.push({ linha: i + 1, classe: m[1], sugestao: sugerir(m[1]) });
64
+ for (const m of linha.matchAll(VAR_REMOVIDA)) achados.push({ linha: i + 1, classe: m[0], sugestao: "removida na 3.0.0 — use o token semântico" });
65
+ });
66
+ return achados;
67
+ }
68
+
69
+ function arquivos(caminho, erros) {
70
+ let st;
71
+ try {
72
+ st = statSync(caminho);
73
+ } catch {
74
+ erros.push(caminho);
75
+ return [];
76
+ }
77
+ if (st.isFile()) return [caminho];
78
+ const out = [];
79
+ for (const e of readdirSync(caminho, { withFileTypes: true })) {
80
+ if (e.isDirectory()) {
81
+ if (!PULAR_DIR.has(e.name)) out.push(...arquivos(join(caminho, e.name), erros));
82
+ } else if (EXTENSOES.has(extname(e.name)) && !/\.test\.[jt]sx?$/.test(e.name)) {
83
+ out.push(join(caminho, e.name));
84
+ }
85
+ }
86
+ return out;
87
+ }
88
+
89
+ export function executar(caminhos, { out = console.log, err = console.error } = {}) {
90
+ const erros = [];
91
+ let total = 0;
92
+ for (const alvo of caminhos.length ? caminhos : ["src"]) {
93
+ for (const f of arquivos(alvo, erros)) {
94
+ let texto;
95
+ try {
96
+ texto = readFileSync(f, "utf8");
97
+ } catch {
98
+ erros.push(f);
99
+ continue;
100
+ }
101
+ for (const a of varrerTexto(texto)) {
102
+ total++;
103
+ out(`${relative(process.cwd(), f).replace(/\\/g, "/")}:${a.linha} ${a.classe} → ${a.sugestao}`);
104
+ }
105
+ }
106
+ }
107
+ for (const e of erros) err(`check-colors: não foi possível ler ${e}`);
108
+ if (total) err(`check-colors: ${total} cor(es) crua(s) — a paleta iClips só aceita tokens semânticos.`);
109
+ return total ? 1 : erros.length ? 2 : 0;
110
+ }
111
+
112
+ async function selfTest() {
113
+ const assert = (await import("node:assert/strict")).default;
114
+ const { mkdtempSync, writeFileSync, statSync: st, rmSync } = await import("node:fs");
115
+ const { tmpdir } = await import("node:os");
116
+ const { spawnSync } = await import("node:child_process");
117
+ const { fileURLToPath } = await import("node:url");
118
+
119
+ // detecção: toda família × todo prefixo base, com variante e opacidade
120
+ // lista literal (não derivada da regex): renomear ou esquecer uma família no FAMILIAS falha aqui
121
+ const familias = ["slate", "gray", "zinc", "neutral", "stone", "red", "orange", "amber", "yellow", "lime", "green", "emerald", "teal", "cyan", "sky", "blue", "indigo", "violet", "purple", "fuchsia", "pink", "rose"];
122
+ assert.deepEqual(FAMILIAS.split("|"), familias, "FAMILIAS cobre exatamente a paleta padrão do Tailwind v4");
123
+ const prefixos = ["bg", "text", "border", ...["x", "y", "t", "r", "b", "l", "s", "e"].map((l) => `border-${l}`), "ring", "ring-offset", "outline", "fill", "stroke", "from", "via", "to", "divide", "placeholder", "caret", "accent", "decoration", "shadow"];
124
+ assert.equal(familias.length, 22, "22 famílias do Tailwind");
125
+ for (const f of familias)
126
+ for (const p of prefixos) {
127
+ const c = `${p}-${f}-500`;
128
+ const achados = varrerTexto(`<div className="hover:${c}/40" />`);
129
+ assert.equal(achados.length, 1, `detecta ${c}`);
130
+ assert.equal(achados[0].classe, `hover:${c}/40`, `cita a classe inteira: ${c}`);
131
+ }
132
+ for (const ok of ["bg-primary", "text-muted-foreground", "bg-black/50", "text-white", "bg-white", "border-border", "text-success-emphasis", "bg-chart-2/15", "bg-[#24292F]", "grayscale", "text-gray", "blue-500", "bg-gray-5000"])
133
+ assert.equal(varrerTexto(`<div className="${ok}" />`).length, 0, `não acusa ${ok}`);
134
+ assert.equal(varrerTexto('style={{ color: "var(--teal-500)" }}').length, 1, "var removida");
135
+ assert.equal(varrerTexto('style={{ color: "var(--cta)" }}').length, 1, "var --cta removida");
136
+ assert.equal(varrerTexto('style={{ color: "var(--gray-100)" }}').length, 0, "var mantida");
137
+ assert.equal(varrerTexto('<p className="bg-teal-500" /> {/* check-colors-ignore */}').length, 0, "ignore");
138
+
139
+ // sugestões: um caso por ramo de sugerir(); as linhas marcadas (guia) espelham a tabela do MIGRATION-v3.md
140
+ const tabela = {
141
+ // neutros
142
+ "bg-gray-50": "bg-background ou bg-muted/40", /* guia */ "bg-gray-100": "bg-muted", /* guia */ "bg-gray-200": "bg-muted",
143
+ "bg-gray-400": "bg-muted", "bg-zinc-900": "bg-card (superfície escura)", /* guia */ "bg-gray-700": "bg-card (superfície escura)",
144
+ "text-gray-800": "text-foreground", /* guia */ "text-gray-900": "text-foreground", "text-gray-500": "text-muted-foreground", /* guia */
145
+ "text-gray-400": "text-muted-foreground", "text-gray-300": "text-muted-foreground (400 ou mais claro nunca em texto)",
146
+ "border-gray-200": "border-border", /* guia */ "border-gray-100": "border-border", "border-gray-300": "border-input", /* guia */
147
+ "divide-gray-200": "divide-border", "outline-slate-300": "outline-input", "ring-gray-300": "ring-ring",
148
+ // roxo = ação
149
+ "bg-purple-500": "bg-primary", /* guia */ "bg-purple-600": "bg-primary", "bg-purple-50": "bg-secondary (seleção)", /* guia */ "bg-purple-100": "bg-secondary (seleção)",
150
+ "text-purple-600": "text-primary", /* guia */ "text-purple-500": "text-primary", "text-purple-700": "text-secondary-foreground", /* guia */
151
+ "border-purple-500": "border-primary", /* guia */ "ring-purple-500": "ring-ring", /* guia */ "outline-purple-400": "outline-primary",
152
+ // estado
153
+ "text-red-600": "text-destructive-emphasis", /* guia */ "text-green-600": "text-success-emphasis", "text-yellow-700": "text-warning-emphasis", "text-blue-600": "text-info-emphasis",
154
+ "text-amber-700": "text-warning-emphasis", "text-emerald-600": "text-success-emphasis", "text-sky-600": "text-info-emphasis", "text-cyan-600": "text-info-emphasis",
155
+ "bg-red-600": "bg-destructive", /* guia */ "bg-red-50": "bg-destructive/10", "bg-green-100": "bg-success/10", "bg-blue-500": "bg-info",
156
+ "border-red-200": "border-destructive/20", /* guia */ "fill-green-600": "fill-success", "hover:bg-gray-100": "bg-muted", "dark:bg-zinc-900": "bg-card (superfície escura)",
157
+ };
158
+ for (const [c, esperado] of Object.entries(tabela)) assert.equal(sugerir(c), esperado, c);
159
+ for (const semEquivalente of ["bg-teal-500", "text-orange-600", "bg-pink-500", "text-indigo-500", "bg-violet-500", "text-violet-600", "border-rose-300", "fill-lime-400", "bg-fuchsia-500"])
160
+ assert.match(sugerir(semEquivalente), /^sem equivalente/, `${semEquivalente} sem equivalente`);
161
+
162
+ // CLI de verdade
163
+ const script = fileURLToPath(import.meta.url);
164
+ const run = (args, cwd) => spawnSync(process.execPath, [script, ...args], { cwd, encoding: "utf8" });
165
+ const dir = mkdtempSync(join(tmpdir(), "check-colors-"));
166
+ try {
167
+ const limpo = join(dir, "limpo");
168
+ const sujo = join(dir, "sujo");
169
+ for (const d of [limpo, sujo]) spawnSync(process.execPath, ["-e", `require("fs").mkdirSync(${JSON.stringify(d)})`]);
170
+ writeFileSync(join(limpo, "a.tsx"), '<div className="bg-primary text-white" />\n');
171
+ const alvo = join(sujo, "b.tsx");
172
+ writeFileSync(alvo, 'const x = 1;\n<div className="bg-teal-500" />\n<p className="text-gray-600" />\n');
173
+ writeFileSync(join(sujo, "b.test.tsx"), '<div className="bg-teal-500" />\n');
174
+ const antes = { txt: readFileSync(alvo, "utf8"), mtime: st(alvo).mtimeMs };
175
+
176
+ let r = run([limpo], dir);
177
+ assert.equal(r.status, 0, "dir limpo → exit 0");
178
+
179
+ r = run([sujo], dir);
180
+ assert.equal(r.status, 1, "dir sujo → exit 1");
181
+ const linhas = r.stdout.trim().split("\n");
182
+ assert.equal(linhas.length, 2, "lista todas as ocorrências (e ignora *.test.*)");
183
+ assert.match(linhas[0], /sujo\/b\.tsx:2 {2}bg-teal-500 {2}→ sem equivalente/, "cita arquivo:linha e a classe");
184
+ assert.match(linhas[1], /sujo\/b\.tsx:3 {2}text-gray-600 {2}→ text-muted-foreground/, "sugere o token");
185
+
186
+ assert.equal(readFileSync(alvo, "utf8"), antes.txt, "não altera o conteúdo");
187
+ assert.equal(st(alvo).mtimeMs, antes.mtime, "não toca o arquivo");
188
+
189
+ r = run([join(dir, "nao-existe")], dir);
190
+ assert.equal(r.status, 2, "caminho ilegível sem achado → exit 2");
191
+ assert.match(r.stderr, /não foi possível ler/, "avisa no stderr");
192
+ r = run([join(dir, "nao-existe"), sujo], dir);
193
+ assert.equal(r.status, 1, "ilegível + achado → continua e sai 1");
194
+ } finally {
195
+ rmSync(dir, { recursive: true, force: true });
196
+ }
197
+
198
+ // o lint da lib encadeia o script
199
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
200
+ assert.match(pkg.scripts.lint, /node scripts\/check-colors\.mjs/, "npm run lint roda check-colors");
201
+
202
+ console.log("check-colors: self-test ok");
203
+ }
204
+
205
+ if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/").split("/").pop())) {
206
+ if (process.argv.includes("--self-test")) await selfTest();
207
+ else process.exitCode = executar(process.argv.slice(2));
208
+ }