ads-nuxt 0.1.0 → 0.1.2

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 (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +567 -1
  3. package/app/app.config.ts +25 -25
  4. package/app/assets/ads/checkbox-check.svg +5 -5
  5. package/app/assets/ads/checkbox-minus.svg +5 -5
  6. package/app/assets/brand/farmarcas.svg +6 -6
  7. package/app/assets/brand/radar.svg +9 -9
  8. package/app/assets/css/component-tokens.css +403 -403
  9. package/app/assets/css/main.css +617 -617
  10. package/app/components/AdsBadge.vue +48 -48
  11. package/app/components/AdsBroadcast.vue +45 -45
  12. package/app/components/AdsButton.vue +569 -569
  13. package/app/components/AdsButtonGroup.vue +39 -39
  14. package/app/components/AdsButtonMenu.vue +48 -48
  15. package/app/components/AdsCard.vue +117 -117
  16. package/app/components/AdsCharttip.vue +78 -78
  17. package/app/components/AdsCheckbox.vue +77 -77
  18. package/app/components/AdsDatePicker.vue +473 -473
  19. package/app/components/AdsDropdownMenu.vue +19 -19
  20. package/app/components/AdsEmptyState.vue +58 -58
  21. package/app/components/AdsFarmarcasPill.vue +74 -74
  22. package/app/components/AdsFeatureCard.vue +217 -217
  23. package/app/components/AdsFileCard.vue +101 -101
  24. package/app/components/AdsFilterDropdown.vue +174 -174
  25. package/app/components/AdsFilterOption.vue +72 -72
  26. package/app/components/AdsFilterSelect.vue +202 -202
  27. package/app/components/AdsFooter.vue +25 -25
  28. package/app/components/AdsInput.vue +143 -143
  29. package/app/components/AdsListDropdown.vue +44 -44
  30. package/app/components/AdsLoading.vue +109 -109
  31. package/app/components/AdsMenuBar.vue +103 -103
  32. package/app/components/AdsMetricCard.vue +119 -119
  33. package/app/components/AdsMiniPill.vue +40 -40
  34. package/app/components/AdsModal.vue +157 -157
  35. package/app/components/AdsMonthPicker.vue +80 -80
  36. package/app/components/AdsNavbarApp.vue +67 -67
  37. package/app/components/AdsNavbarDesk.vue +62 -62
  38. package/app/components/AdsNotabilityPill.vue +27 -27
  39. package/app/components/AdsNumberInput.vue +166 -166
  40. package/app/components/AdsPagination.vue +91 -91
  41. package/app/components/AdsProductTag.vue +61 -61
  42. package/app/components/AdsRadio.vue +70 -70
  43. package/app/components/AdsSearchInput.vue +113 -113
  44. package/app/components/AdsSegmentedTabs.vue +28 -28
  45. package/app/components/AdsSideMenu.vue +101 -101
  46. package/app/components/AdsSmalltip.vue +44 -44
  47. package/app/components/AdsSteptip.vue +101 -101
  48. package/app/components/AdsSwitch.vue +59 -59
  49. package/app/components/AdsTable.vue +100 -100
  50. package/app/components/AdsTableToolbar.vue +44 -44
  51. package/app/components/AdsTabs.vue +51 -51
  52. package/app/components/AdsTextarea.vue +109 -109
  53. package/app/components/AdsToast.vue +181 -181
  54. package/app/components/AdsTooltip.vue +66 -66
  55. package/app/components/InvertedTooltip.vue +40 -40
  56. package/app/components/OverlayLoading.vue +173 -173
  57. package/app/components/ProgressBarLoading.vue +256 -256
  58. package/app/composables/useOverlayLoading.ts +66 -66
  59. package/app/locale/pt-br.ts +6 -6
  60. package/app/plugins/ads-toast.client.ts +24 -24
  61. package/app/theme/ads-theme.ts +370 -370
  62. package/app/theme/component-tokens.json +402 -402
  63. package/app/theme/component-tokens.ts +815 -815
  64. package/app/types/overlay-loading.types.ts +26 -26
  65. package/app/utils/ads-filter-select.ts +172 -172
  66. package/app/utils/ads-table.ts +204 -204
  67. package/app/utils/calendar-date.ts +128 -128
  68. package/app/utils/category-role.ts +45 -45
  69. package/app/utils/money.ts +77 -77
  70. package/app/utils/notability.ts +8 -8
  71. package/app/utils/parser.ts +134 -134
  72. package/app/utils/percentage.ts +54 -54
  73. package/nuxt.config.ts +31 -32
  74. package/package.json +48 -46
@@ -1,370 +1,370 @@
1
- import {
2
- adsAttention,
3
- adsNeutralDark,
4
- adsNeutralLight,
5
- adsNeutralMid,
6
- adsOpportunity,
7
- adsRadarPricing,
8
- adsSuccess,
9
- adsWarningRed,
10
- } from "./ads-tokens";
11
-
12
- /**
13
- * Escalas semânticas do Nuxt UI preenchidas só com hex da página de tokens.
14
- * Degrau ausente repete o extremo oficial da escala (julgamento Jev).
15
- */
16
- export const adsPalette = {
17
- /** Radar Pricing: RP-25 … RP-800. 700+ repetem RP-800. */
18
- primary: {
19
- 50: adsRadarPricing[25],
20
- 100: adsRadarPricing[50],
21
- 200: adsRadarPricing[100],
22
- 300: adsRadarPricing[200],
23
- 400: adsRadarPricing[400],
24
- 500: adsRadarPricing[500],
25
- 600: adsRadarPricing[600],
26
- 700: adsRadarPricing[800],
27
- 800: adsRadarPricing[800],
28
- 900: adsRadarPricing[800],
29
- 950: adsRadarPricing[800],
30
- },
31
- /** LI / MI / DA. 300 permanece LI-50, a borda dos controles. */
32
- neutral: {
33
- 50: adsNeutralLight[10],
34
- 100: adsNeutralLight[20],
35
- 200: adsNeutralLight[30],
36
- 300: adsNeutralLight[50],
37
- 400: adsNeutralMid[70],
38
- 500: adsNeutralMid[90],
39
- 600: adsNeutralMid[200],
40
- 700: adsNeutralMid[400],
41
- 800: adsNeutralDark[600],
42
- 900: adsNeutralDark[800],
43
- 950: adsNeutralDark[900],
44
- },
45
- /** WA. O 500 fica em WA-300, a borda de erro já medida nos inputs. */
46
- error: {
47
- 50: adsWarningRed[100],
48
- 100: adsWarningRed[100],
49
- 200: adsWarningRed[200],
50
- 300: adsWarningRed[300],
51
- 400: adsWarningRed[300],
52
- 500: adsWarningRed[300],
53
- 600: adsWarningRed[400],
54
- 700: adsWarningRed[500],
55
- 800: adsWarningRed[600],
56
- 900: adsWarningRed[700],
57
- 950: adsWarningRed[800],
58
- },
59
- success: {
60
- 50: adsSuccess[100],
61
- 100: adsSuccess[100],
62
- 200: adsSuccess[200],
63
- 300: adsSuccess[300],
64
- 400: adsSuccess[400],
65
- 500: adsSuccess[500],
66
- 600: adsSuccess[600],
67
- 700: adsSuccess[700],
68
- 800: adsSuccess[800],
69
- 900: adsSuccess[800],
70
- 950: adsSuccess[800],
71
- },
72
- /** AT, não o grupo WA (vermelho). */
73
- warning: {
74
- 50: adsAttention[100],
75
- 100: adsAttention[100],
76
- 200: adsAttention[200],
77
- 300: adsAttention[300],
78
- 400: adsAttention[400],
79
- 500: adsAttention[500],
80
- 600: adsAttention[600],
81
- 700: adsAttention[700],
82
- 800: adsAttention[800],
83
- 900: adsAttention[800],
84
- 950: adsAttention[800],
85
- },
86
- /** OP. */
87
- info: {
88
- 50: adsOpportunity[100],
89
- 100: adsOpportunity[100],
90
- 200: adsOpportunity[200],
91
- 300: adsOpportunity[300],
92
- 400: adsOpportunity[400],
93
- 500: adsOpportunity[500],
94
- 600: adsOpportunity[600],
95
- 700: adsOpportunity[700],
96
- 800: adsOpportunity[800],
97
- 900: adsOpportunity[800],
98
- 950: adsOpportunity[800],
99
- },
100
- } as const;
101
-
102
- /** Controle usa $border-radius-SM (8px). Checkbox usa $border-radius-xSM (4px). */
103
- export const adsRadius = {
104
- control: "0.5rem",
105
- checkbox: "0.25rem",
106
- } as const;
107
-
108
- export const adsButtonTheme = {
109
- slots: {
110
- base: "rounded-md font-bold inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-100 aria-disabled:opacity-100 transition-colors",
111
- },
112
- variants: {
113
- size: {
114
- sm: {
115
- base: "h-8 px-2.5 text-xs gap-1.5",
116
- },
117
- md: {
118
- base: "h-10.5 px-3 text-sm gap-1.5",
119
- },
120
- lg: {
121
- base: "h-10.5 px-3 text-sm gap-2",
122
- },
123
- },
124
- },
125
- };
126
-
127
- export const adsInputTheme = {
128
- variants: {
129
- size: {
130
- sm: {
131
- base: "h-8 px-2.5 text-xs gap-1.5",
132
- },
133
- md: {
134
- base: "h-10 px-3 text-sm gap-1.5",
135
- },
136
- },
137
- },
138
- };
139
-
140
- export const adsInputDateTheme = {
141
- slots: {
142
- segment:
143
- "data-placeholder:![color:var(--ads-date-placeholder)] data-[segment=literal]:![color:var(--ads-date-separator)]",
144
- },
145
- variants: {
146
- size: {
147
- md: {
148
- base: "h-[var(--ads-date-height)] rounded-[var(--ads-date-radius)] px-[var(--ads-date-padding-x)] text-[length:var(--ads-date-font-size)] [font-weight:var(--ads-date-weight)] !leading-[var(--ads-date-line-height)] gap-1.5 w-full ![color:var(--ads-date-text)] !bg-[var(--ads-date-background)] !ring-[var(--ads-date-border)] hover:not-data-disabled:not-data-readonly:not-has-focus-visible:!ring-[var(--ads-date-hover-border)] has-focus-visible:!ring-[var(--ads-date-focus-border)]",
149
- },
150
- },
151
- },
152
- };
153
-
154
- /**
155
- * Tabs link ADS.
156
- * Lista 39px, itens no tamanho do texto, faixa inferior 1px #C1C7D0.
157
- * Inativo: Inter 14 medium, #505F79. Ativo: bold, #061938, risco 4px #006FF7.
158
- */
159
- export const adsLinkTabsUi = {
160
- root: "items-start",
161
- list: "h-[var(--ads-tabs-height)] w-full items-start self-stretch border-b border-[var(--ads-tabs-border)] py-0 px-0",
162
- trigger:
163
- "h-[var(--ads-tabs-height)] grow-0 px-[var(--ads-tabs-padding-x)] py-0 text-[length:var(--ads-tabs-font-size)] [font-weight:var(--ads-tabs-weight)] leading-[1.3] text-[var(--ads-tabs-inactive)] data-[state=inactive]:text-[var(--ads-tabs-inactive)] data-[state=active]:font-bold data-[state=active]:text-[var(--ads-tabs-active)]",
164
- indicator: "-bottom-px h-[var(--ads-tabs-indicator)] rounded-none bg-[var(--ads-tabs-indicator-color)]",
165
- } as const;
166
-
167
- /**
168
- * Tabs segmentadas ADS (Figma Switch Tabs).
169
- * Trilha 37px em neutral-100 (Neutral Light 20, #F0F3F6), raio 4px, respiro 4px.
170
- * Item ativo com 30px.
171
- * Inativo: Inter 12 regular, #061938. Ativo: bold, preto, pill branca com sombra nível 1.
172
- */
173
- export const adsSegmentedTabsUi = {
174
- root: "!w-auto shrink-0",
175
- list: "!h-[var(--ads-segmented-height)] !w-auto !self-start !items-center !gap-1 !rounded-[var(--ads-segmented-radius)] !border-0 !bg-[var(--ads-segmented-track)] !p-1",
176
- trigger:
177
- "!h-[var(--ads-segmented-item-height)] !grow-0 !gap-3 !rounded-[var(--ads-segmented-radius)] !px-[var(--ads-segmented-padding-x)] !py-0 !text-[length:var(--ads-segmented-font-size)] !font-normal !leading-[var(--ads-line-height-sm)] !text-[var(--ads-segmented-text)] data-[state=inactive]:!text-[var(--ads-segmented-text)] data-[state=active]:!font-bold data-[state=active]:!text-[var(--ads-segmented-active-text)]",
178
- indicator:
179
- "!rounded-[var(--ads-segmented-radius)] !bg-[var(--ads-segmented-active-bg)] !shadow-[var(--ads-shadow-level-1)]",
180
- } as const;
181
-
182
- /**
183
- * Dropdown ADS: controle 40px / 14px / medium.
184
- * Opção: 16px vertical, 24px à esquerda, 14px regular #505F79.
185
- * Selecionada em bold. Linha Neutral/Light/40 (#d3d8dd) entre as opções.
186
- */
187
- const adsSelectSizeMd = {
188
- base: "h-10 px-3 text-sm gap-1.5 font-medium",
189
- item: "ps-6 pe-3 py-4 text-sm gap-2 font-normal text-muted border-b border-[#d3d8dd] last:border-b-0 data-[state=checked]:font-bold data-[state=checked]:text-neutral-700 data-highlighted:before:bg-transparent",
190
- };
191
-
192
- const adsSelectSlots = {
193
- placeholder: "truncate text-dimmed font-normal",
194
- group: "!p-0",
195
- };
196
-
197
- export const adsSelectTheme = {
198
- slots: adsSelectSlots,
199
- variants: {
200
- size: {
201
- md: adsSelectSizeMd,
202
- },
203
- },
204
- };
205
-
206
- export const adsSelectMenuTheme = {
207
- slots: adsSelectSlots,
208
- variants: {
209
- size: {
210
- md: adsSelectSizeMd,
211
- },
212
- },
213
- };
214
-
215
- /**
216
- * Menu de ações ADS. Mesma opção do select: 16px vertical, 24px à esquerda,
217
- * 14px regular e linha #d3d8dd entre os itens.
218
- */
219
- export const adsDropdownMenuUi = {
220
- content:
221
- "!min-w-56 !bg-[var(--ads-surface-card)] !shadow-[var(--ads-shadow-level-1)] !ring-0 !rounded-[var(--ads-radius-sm)] border border-[var(--ads-border-subtle)]",
222
- viewport: "!divide-y-0",
223
- group: "!p-0",
224
- item: "!items-center !gap-2 !rounded-none !p-0 !ps-6 !pe-3 !py-4 !text-sm !font-normal !text-[var(--ads-text-secondary)] border-b border-[#d3d8dd] last:border-b-0 before:!hidden data-highlighted:!bg-neutral-100",
225
- itemLeadingIcon: "!size-4 !text-[var(--ads-text-secondary)]",
226
- itemLabel: "!truncate !text-sm !font-normal !text-[var(--ads-text-secondary)]",
227
- } as const;
228
-
229
- /** Rótulo do dropdown ADS: $font-size-xxxs (12px), peso medium. */
230
- export const adsSelectFieldUi = {
231
- label: "text-xs font-medium text-neutral-500",
232
- } as const;
233
-
234
- /**
235
- * Intervalo do date picker ADS.
236
- * Cap (início/fim): primary 500. Meio: primary 100 — mais próximo do cluster
237
- * #c0e0f8 da referência visual (Figma API 429; JEV escolheu primary_100).
238
- */
239
- export const adsCalendarRangeTokens = {
240
- capBackground: adsPalette.primary[500],
241
- capText: "#ffffff",
242
- middleBackground: adsPalette.primary[100],
243
- middleText: adsPalette.primary[500],
244
- } as const;
245
-
246
- export const adsCalendarTheme = {
247
- slots: {
248
- cellTrigger: "rounded-md",
249
- },
250
- };
251
-
252
- /**
253
- * Tooltip ADS (Figma node 5720:10263).
254
- * Fundo DA-900, título branco em negrito, corpo LI-50, raio MD (12px).
255
- */
256
- export const adsTooltipTitleClass = "font-bold text-[var(--ads-tooltip-title)]";
257
-
258
- export const adsTooltipTheme = {
259
- slots: {
260
- content:
261
- "flex items-start gap-1 h-auto max-w-xs rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] px-[var(--ads-tooltip-padding-x)] py-[var(--ads-tooltip-padding-y)] text-[length:var(--ads-tooltip-font-size)] leading-[1.4] text-[var(--ads-tooltip-text)] shadow-none ring-0 select-none data-[state=delayed-open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-tooltip-content-transform-origin) pointer-events-auto",
262
- arrow: "fill-[var(--ads-tooltip-bg)]",
263
- text: "whitespace-normal",
264
- },
265
- };
266
-
267
- /** Mesmo visual para tooltips feitas com UPopover em hover. */
268
- export const adsTooltipPopoverUi = {
269
- content:
270
- "h-auto max-w-xs rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] px-[var(--ads-tooltip-padding-x)] py-[var(--ads-tooltip-padding-y)] text-[length:var(--ads-tooltip-font-size)] leading-[1.4] text-[var(--ads-tooltip-text)] shadow-none ring-0 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-popover-content-transform-origin)",
271
- arrow: "fill-[var(--ads-tooltip-bg)]",
272
- };
273
-
274
- /**
275
- * Status Toast do Figma ADS (frame 371×340).
276
- * Fundo DA-900, raio sm (8px), título 16 bold, apoio 14 a 75%.
277
- * Ícones: SU-400, WA, AT-400 e OP-400. Sem barra de progresso.
278
- */
279
- export const adsToastWidth = "371px";
280
-
281
- export const adsToastIcons = {
282
- success: "i-lucide-circle-check",
283
- error: "i-lucide-circle-x",
284
- warning: "i-lucide-triangle-alert",
285
- info: "i-lucide-info",
286
- primary: "i-lucide-info",
287
- secondary: "i-lucide-info",
288
- neutral: "i-lucide-info",
289
- } as const;
290
-
291
- const adsLegacyToastIcons = new Set([
292
- "i-lucide-check-circle",
293
- "i-lucide-x-circle",
294
- "i-lucide-alert-circle",
295
- "i-lucide-alert-triangle",
296
- ]);
297
-
298
- export const resolveAdsToastIcon = (
299
- color: string | undefined,
300
- icon: unknown,
301
- ): string => {
302
- const fallback =
303
- adsToastIcons[color as keyof typeof adsToastIcons] ?? adsToastIcons.primary;
304
-
305
- if (typeof icon !== "string" || icon.length === 0) {
306
- return fallback;
307
- }
308
-
309
- if (adsLegacyToastIcons.has(icon)) {
310
- return fallback;
311
- }
312
-
313
- return icon;
314
- };
315
-
316
- const adsToastFocus =
317
- "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-white/40";
318
-
319
- export const adsToastTheme = {
320
- slots: {
321
- root: `relative group overflow-hidden bg-neutral-950 !shadow-[var(--ads-shadow-level-2)] !ring-0 rounded-[var(--ads-radius-sm)] p-4 flex !gap-3 items-center focus:outline-none ${adsToastFocus}`,
322
- wrapper: "w-0 flex-1 flex flex-col",
323
- title: "text-base font-bold leading-[1.2] text-white min-w-0 break-words",
324
- description: "text-sm font-normal leading-[1.2] text-white/75 min-w-0 break-words",
325
- icon: "shrink-0 !size-6",
326
- close:
327
- "!size-6 !min-h-0 !p-0 shrink-0 !text-white hover:!bg-transparent hover:!text-white focus-visible:!ring-white/40",
328
- progress: "!hidden",
329
- },
330
- variants: {
331
- color: {
332
- success: { root: adsToastFocus, icon: "text-success-400" },
333
- error: { root: adsToastFocus, icon: "text-error" },
334
- warning: { root: adsToastFocus, icon: "text-warning-400" },
335
- info: { root: adsToastFocus, icon: "text-info-400" },
336
- primary: { root: adsToastFocus, icon: "text-primary-400" },
337
- secondary: { root: adsToastFocus, icon: "text-secondary-400" },
338
- neutral: { root: adsToastFocus, icon: "text-white" },
339
- },
340
- orientation: {
341
- horizontal: { root: "items-center" },
342
- vertical: { root: "items-center" },
343
- },
344
- },
345
- };
346
-
347
- export const adsToasterTheme = {
348
- slots: {
349
- viewport:
350
- "fixed flex flex-col w-[calc(100%-2rem)] sm:w-[371px] z-[100] data-[expanded=true]:h-(--height) focus:outline-none",
351
- },
352
- };
353
-
354
- export const adsToaster = {
355
- position: "top-right" as const,
356
- progress: false,
357
- };
358
-
359
- const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;
360
-
361
- export const adsThemeColorVariables = () => {
362
- const lines: string[] = [];
363
- for (const [name, scale] of Object.entries(adsPalette)) {
364
- lines.push(`--color-${name}: ${scale[500]};`);
365
- for (const shade of shades) {
366
- lines.push(`--color-${name}-${shade}: ${scale[shade]};`);
367
- }
368
- }
369
- return lines;
370
- };
1
+ import {
2
+ adsAttention,
3
+ adsNeutralDark,
4
+ adsNeutralLight,
5
+ adsNeutralMid,
6
+ adsOpportunity,
7
+ adsRadarPricing,
8
+ adsSuccess,
9
+ adsWarningRed,
10
+ } from "./ads-tokens";
11
+
12
+ /**
13
+ * Escalas semânticas do Nuxt UI preenchidas só com hex da página de tokens.
14
+ * Degrau ausente repete o extremo oficial da escala (julgamento Jev).
15
+ */
16
+ export const adsPalette = {
17
+ /** Radar Pricing: RP-25 … RP-800. 700+ repetem RP-800. */
18
+ primary: {
19
+ 50: adsRadarPricing[25],
20
+ 100: adsRadarPricing[50],
21
+ 200: adsRadarPricing[100],
22
+ 300: adsRadarPricing[200],
23
+ 400: adsRadarPricing[400],
24
+ 500: adsRadarPricing[500],
25
+ 600: adsRadarPricing[600],
26
+ 700: adsRadarPricing[800],
27
+ 800: adsRadarPricing[800],
28
+ 900: adsRadarPricing[800],
29
+ 950: adsRadarPricing[800],
30
+ },
31
+ /** LI / MI / DA. 300 permanece LI-50, a borda dos controles. */
32
+ neutral: {
33
+ 50: adsNeutralLight[10],
34
+ 100: adsNeutralLight[20],
35
+ 200: adsNeutralLight[30],
36
+ 300: adsNeutralLight[50],
37
+ 400: adsNeutralMid[70],
38
+ 500: adsNeutralMid[90],
39
+ 600: adsNeutralMid[200],
40
+ 700: adsNeutralMid[400],
41
+ 800: adsNeutralDark[600],
42
+ 900: adsNeutralDark[800],
43
+ 950: adsNeutralDark[900],
44
+ },
45
+ /** WA. O 500 fica em WA-300, a borda de erro já medida nos inputs. */
46
+ error: {
47
+ 50: adsWarningRed[100],
48
+ 100: adsWarningRed[100],
49
+ 200: adsWarningRed[200],
50
+ 300: adsWarningRed[300],
51
+ 400: adsWarningRed[300],
52
+ 500: adsWarningRed[300],
53
+ 600: adsWarningRed[400],
54
+ 700: adsWarningRed[500],
55
+ 800: adsWarningRed[600],
56
+ 900: adsWarningRed[700],
57
+ 950: adsWarningRed[800],
58
+ },
59
+ success: {
60
+ 50: adsSuccess[100],
61
+ 100: adsSuccess[100],
62
+ 200: adsSuccess[200],
63
+ 300: adsSuccess[300],
64
+ 400: adsSuccess[400],
65
+ 500: adsSuccess[500],
66
+ 600: adsSuccess[600],
67
+ 700: adsSuccess[700],
68
+ 800: adsSuccess[800],
69
+ 900: adsSuccess[800],
70
+ 950: adsSuccess[800],
71
+ },
72
+ /** AT, não o grupo WA (vermelho). */
73
+ warning: {
74
+ 50: adsAttention[100],
75
+ 100: adsAttention[100],
76
+ 200: adsAttention[200],
77
+ 300: adsAttention[300],
78
+ 400: adsAttention[400],
79
+ 500: adsAttention[500],
80
+ 600: adsAttention[600],
81
+ 700: adsAttention[700],
82
+ 800: adsAttention[800],
83
+ 900: adsAttention[800],
84
+ 950: adsAttention[800],
85
+ },
86
+ /** OP. */
87
+ info: {
88
+ 50: adsOpportunity[100],
89
+ 100: adsOpportunity[100],
90
+ 200: adsOpportunity[200],
91
+ 300: adsOpportunity[300],
92
+ 400: adsOpportunity[400],
93
+ 500: adsOpportunity[500],
94
+ 600: adsOpportunity[600],
95
+ 700: adsOpportunity[700],
96
+ 800: adsOpportunity[800],
97
+ 900: adsOpportunity[800],
98
+ 950: adsOpportunity[800],
99
+ },
100
+ } as const;
101
+
102
+ /** Controle usa $border-radius-SM (8px). Checkbox usa $border-radius-xSM (4px). */
103
+ export const adsRadius = {
104
+ control: "0.5rem",
105
+ checkbox: "0.25rem",
106
+ } as const;
107
+
108
+ export const adsButtonTheme = {
109
+ slots: {
110
+ base: "rounded-md font-bold inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-100 aria-disabled:opacity-100 transition-colors",
111
+ },
112
+ variants: {
113
+ size: {
114
+ sm: {
115
+ base: "h-8 px-2.5 text-xs gap-1.5",
116
+ },
117
+ md: {
118
+ base: "h-10.5 px-3 text-sm gap-1.5",
119
+ },
120
+ lg: {
121
+ base: "h-10.5 px-3 text-sm gap-2",
122
+ },
123
+ },
124
+ },
125
+ };
126
+
127
+ export const adsInputTheme = {
128
+ variants: {
129
+ size: {
130
+ sm: {
131
+ base: "h-8 px-2.5 text-xs gap-1.5",
132
+ },
133
+ md: {
134
+ base: "h-10 px-3 text-sm gap-1.5",
135
+ },
136
+ },
137
+ },
138
+ };
139
+
140
+ export const adsInputDateTheme = {
141
+ slots: {
142
+ segment:
143
+ "data-placeholder:![color:var(--ads-date-placeholder)] data-[segment=literal]:![color:var(--ads-date-separator)]",
144
+ },
145
+ variants: {
146
+ size: {
147
+ md: {
148
+ base: "h-[var(--ads-date-height)] rounded-[var(--ads-date-radius)] px-[var(--ads-date-padding-x)] text-[length:var(--ads-date-font-size)] [font-weight:var(--ads-date-weight)] !leading-[var(--ads-date-line-height)] gap-1.5 w-full ![color:var(--ads-date-text)] !bg-[var(--ads-date-background)] !ring-[var(--ads-date-border)] hover:not-data-disabled:not-data-readonly:not-has-focus-visible:!ring-[var(--ads-date-hover-border)] has-focus-visible:!ring-[var(--ads-date-focus-border)]",
149
+ },
150
+ },
151
+ },
152
+ };
153
+
154
+ /**
155
+ * Tabs link ADS.
156
+ * Lista 39px, itens no tamanho do texto, faixa inferior 1px #C1C7D0.
157
+ * Inativo: Inter 14 medium, #505F79. Ativo: bold, #061938, risco 4px #006FF7.
158
+ */
159
+ export const adsLinkTabsUi = {
160
+ root: "items-start",
161
+ list: "h-[var(--ads-tabs-height)] w-full items-start self-stretch border-b border-[var(--ads-tabs-border)] py-0 px-0",
162
+ trigger:
163
+ "h-[var(--ads-tabs-height)] grow-0 px-[var(--ads-tabs-padding-x)] py-0 text-[length:var(--ads-tabs-font-size)] [font-weight:var(--ads-tabs-weight)] leading-[1.3] text-[var(--ads-tabs-inactive)] data-[state=inactive]:text-[var(--ads-tabs-inactive)] data-[state=active]:font-bold data-[state=active]:text-[var(--ads-tabs-active)]",
164
+ indicator: "-bottom-px h-[var(--ads-tabs-indicator)] rounded-none bg-[var(--ads-tabs-indicator-color)]",
165
+ } as const;
166
+
167
+ /**
168
+ * Tabs segmentadas ADS (Figma Switch Tabs).
169
+ * Trilha 37px em neutral-100 (Neutral Light 20, #F0F3F6), raio 4px, respiro 4px.
170
+ * Item ativo com 30px.
171
+ * Inativo: Inter 12 regular, #061938. Ativo: bold, preto, pill branca com sombra nível 1.
172
+ */
173
+ export const adsSegmentedTabsUi = {
174
+ root: "!w-auto shrink-0",
175
+ list: "!h-[var(--ads-segmented-height)] !w-auto !self-start !items-center !gap-1 !rounded-[var(--ads-segmented-radius)] !border-0 !bg-[var(--ads-segmented-track)] !p-1",
176
+ trigger:
177
+ "!h-[var(--ads-segmented-item-height)] !grow-0 !gap-3 !rounded-[var(--ads-segmented-radius)] !px-[var(--ads-segmented-padding-x)] !py-0 !text-[length:var(--ads-segmented-font-size)] !font-normal !leading-[var(--ads-line-height-sm)] !text-[var(--ads-segmented-text)] data-[state=inactive]:!text-[var(--ads-segmented-text)] data-[state=active]:!font-bold data-[state=active]:!text-[var(--ads-segmented-active-text)]",
178
+ indicator:
179
+ "!rounded-[var(--ads-segmented-radius)] !bg-[var(--ads-segmented-active-bg)] !shadow-[var(--ads-shadow-level-1)]",
180
+ } as const;
181
+
182
+ /**
183
+ * Dropdown ADS: controle 40px / 14px / medium.
184
+ * Opção: 16px vertical, 24px à esquerda, 14px regular #505F79.
185
+ * Selecionada em bold. Linha Neutral/Light/40 (#d3d8dd) entre as opções.
186
+ */
187
+ const adsSelectSizeMd = {
188
+ base: "h-10 px-3 text-sm gap-1.5 font-medium",
189
+ item: "ps-6 pe-3 py-4 text-sm gap-2 font-normal text-muted border-b border-[#d3d8dd] last:border-b-0 data-[state=checked]:font-bold data-[state=checked]:text-neutral-700 data-highlighted:before:bg-transparent",
190
+ };
191
+
192
+ const adsSelectSlots = {
193
+ placeholder: "truncate text-dimmed font-normal",
194
+ group: "!p-0",
195
+ };
196
+
197
+ export const adsSelectTheme = {
198
+ slots: adsSelectSlots,
199
+ variants: {
200
+ size: {
201
+ md: adsSelectSizeMd,
202
+ },
203
+ },
204
+ };
205
+
206
+ export const adsSelectMenuTheme = {
207
+ slots: adsSelectSlots,
208
+ variants: {
209
+ size: {
210
+ md: adsSelectSizeMd,
211
+ },
212
+ },
213
+ };
214
+
215
+ /**
216
+ * Menu de ações ADS. Mesma opção do select: 16px vertical, 24px à esquerda,
217
+ * 14px regular e linha #d3d8dd entre os itens.
218
+ */
219
+ export const adsDropdownMenuUi = {
220
+ content:
221
+ "!min-w-56 !bg-[var(--ads-surface-card)] !shadow-[var(--ads-shadow-level-1)] !ring-0 !rounded-[var(--ads-radius-sm)] border border-[var(--ads-border-subtle)]",
222
+ viewport: "!divide-y-0",
223
+ group: "!p-0",
224
+ item: "!items-center !gap-2 !rounded-none !p-0 !ps-6 !pe-3 !py-4 !text-sm !font-normal !text-[var(--ads-text-secondary)] border-b border-[#d3d8dd] last:border-b-0 before:!hidden data-highlighted:!bg-neutral-100",
225
+ itemLeadingIcon: "!size-4 !text-[var(--ads-text-secondary)]",
226
+ itemLabel: "!truncate !text-sm !font-normal !text-[var(--ads-text-secondary)]",
227
+ } as const;
228
+
229
+ /** Rótulo do dropdown ADS: $font-size-xxxs (12px), peso medium. */
230
+ export const adsSelectFieldUi = {
231
+ label: "text-xs font-medium text-neutral-500",
232
+ } as const;
233
+
234
+ /**
235
+ * Intervalo do date picker ADS.
236
+ * Cap (início/fim): primary 500. Meio: primary 100 — mais próximo do cluster
237
+ * #c0e0f8 da referência visual (Figma API 429; JEV escolheu primary_100).
238
+ */
239
+ export const adsCalendarRangeTokens = {
240
+ capBackground: adsPalette.primary[500],
241
+ capText: "#ffffff",
242
+ middleBackground: adsPalette.primary[100],
243
+ middleText: adsPalette.primary[500],
244
+ } as const;
245
+
246
+ export const adsCalendarTheme = {
247
+ slots: {
248
+ cellTrigger: "rounded-md",
249
+ },
250
+ };
251
+
252
+ /**
253
+ * Tooltip ADS (Figma node 5720:10263).
254
+ * Fundo DA-900, título branco em negrito, corpo LI-50, raio MD (12px).
255
+ */
256
+ export const adsTooltipTitleClass = "font-bold text-[var(--ads-tooltip-title)]";
257
+
258
+ export const adsTooltipTheme = {
259
+ slots: {
260
+ content:
261
+ "flex items-start gap-1 h-auto max-w-xs rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] px-[var(--ads-tooltip-padding-x)] py-[var(--ads-tooltip-padding-y)] text-[length:var(--ads-tooltip-font-size)] leading-[1.4] text-[var(--ads-tooltip-text)] shadow-none ring-0 select-none data-[state=delayed-open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-tooltip-content-transform-origin) pointer-events-auto",
262
+ arrow: "fill-[var(--ads-tooltip-bg)]",
263
+ text: "whitespace-normal",
264
+ },
265
+ };
266
+
267
+ /** Mesmo visual para tooltips feitas com UPopover em hover. */
268
+ export const adsTooltipPopoverUi = {
269
+ content:
270
+ "h-auto max-w-xs rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] px-[var(--ads-tooltip-padding-x)] py-[var(--ads-tooltip-padding-y)] text-[length:var(--ads-tooltip-font-size)] leading-[1.4] text-[var(--ads-tooltip-text)] shadow-none ring-0 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-popover-content-transform-origin)",
271
+ arrow: "fill-[var(--ads-tooltip-bg)]",
272
+ };
273
+
274
+ /**
275
+ * Status Toast do Figma ADS (frame 371×340).
276
+ * Fundo DA-900, raio sm (8px), título 16 bold, apoio 14 a 75%.
277
+ * Ícones: SU-400, WA, AT-400 e OP-400. Sem barra de progresso.
278
+ */
279
+ export const adsToastWidth = "371px";
280
+
281
+ export const adsToastIcons = {
282
+ success: "i-lucide-circle-check",
283
+ error: "i-lucide-circle-x",
284
+ warning: "i-lucide-triangle-alert",
285
+ info: "i-lucide-info",
286
+ primary: "i-lucide-info",
287
+ secondary: "i-lucide-info",
288
+ neutral: "i-lucide-info",
289
+ } as const;
290
+
291
+ const adsLegacyToastIcons = new Set([
292
+ "i-lucide-check-circle",
293
+ "i-lucide-x-circle",
294
+ "i-lucide-alert-circle",
295
+ "i-lucide-alert-triangle",
296
+ ]);
297
+
298
+ export const resolveAdsToastIcon = (
299
+ color: string | undefined,
300
+ icon: unknown,
301
+ ): string => {
302
+ const fallback =
303
+ adsToastIcons[color as keyof typeof adsToastIcons] ?? adsToastIcons.primary;
304
+
305
+ if (typeof icon !== "string" || icon.length === 0) {
306
+ return fallback;
307
+ }
308
+
309
+ if (adsLegacyToastIcons.has(icon)) {
310
+ return fallback;
311
+ }
312
+
313
+ return icon;
314
+ };
315
+
316
+ const adsToastFocus =
317
+ "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-white/40";
318
+
319
+ export const adsToastTheme = {
320
+ slots: {
321
+ root: `relative group overflow-hidden bg-neutral-950 !shadow-[var(--ads-shadow-level-2)] !ring-0 rounded-[var(--ads-radius-sm)] p-4 flex !gap-3 items-center focus:outline-none ${adsToastFocus}`,
322
+ wrapper: "w-0 flex-1 flex flex-col",
323
+ title: "text-base font-bold leading-[1.2] text-white min-w-0 break-words",
324
+ description: "text-sm font-normal leading-[1.2] text-white/75 min-w-0 break-words",
325
+ icon: "shrink-0 !size-6",
326
+ close:
327
+ "!size-6 !min-h-0 !p-0 shrink-0 !text-white hover:!bg-transparent hover:!text-white focus-visible:!ring-white/40",
328
+ progress: "!hidden",
329
+ },
330
+ variants: {
331
+ color: {
332
+ success: { root: adsToastFocus, icon: "text-success-400" },
333
+ error: { root: adsToastFocus, icon: "text-error" },
334
+ warning: { root: adsToastFocus, icon: "text-warning-400" },
335
+ info: { root: adsToastFocus, icon: "text-info-400" },
336
+ primary: { root: adsToastFocus, icon: "text-primary-400" },
337
+ secondary: { root: adsToastFocus, icon: "text-secondary-400" },
338
+ neutral: { root: adsToastFocus, icon: "text-white" },
339
+ },
340
+ orientation: {
341
+ horizontal: { root: "items-center" },
342
+ vertical: { root: "items-center" },
343
+ },
344
+ },
345
+ };
346
+
347
+ export const adsToasterTheme = {
348
+ slots: {
349
+ viewport:
350
+ "fixed flex flex-col w-[calc(100%-2rem)] sm:w-[371px] z-[100] data-[expanded=true]:h-(--height) focus:outline-none",
351
+ },
352
+ };
353
+
354
+ export const adsToaster = {
355
+ position: "top-right" as const,
356
+ progress: false,
357
+ };
358
+
359
+ const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;
360
+
361
+ export const adsThemeColorVariables = () => {
362
+ const lines: string[] = [];
363
+ for (const [name, scale] of Object.entries(adsPalette)) {
364
+ lines.push(`--color-${name}: ${scale[500]};`);
365
+ for (const shade of shades) {
366
+ lines.push(`--color-${name}-${shade}: ${scale[shade]};`);
367
+ }
368
+ }
369
+ return lines;
370
+ };