@1doc/1ds-react 2.13.0 → 2.15.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 (66) hide show
  1. package/CHANGELOG.md +62 -59
  2. package/dist/components/AILabel/AILabel.css +1 -1
  3. package/dist/components/Alert/Alert.css +1 -1
  4. package/dist/components/AppShell/AppShell.css +4 -0
  5. package/dist/components/AttachmentCard/AttachmentCard.css +2 -2
  6. package/dist/components/Avatar/Avatar.d.ts +8 -3
  7. package/dist/components/Avatar/Avatar.js +28 -4
  8. package/dist/components/BottomNavigation/BottomNavigation.css +2 -2
  9. package/dist/components/BottomSheet/BottomSheet.css +2 -2
  10. package/dist/components/Box/Box.css +3 -3
  11. package/dist/components/Button/Button.css +6 -1
  12. package/dist/components/Button/Button.d.ts +11 -2
  13. package/dist/components/Button/Button.js +15 -3
  14. package/dist/components/Card/Card.css +5 -5
  15. package/dist/components/Chart/Chart.css +1 -1
  16. package/dist/components/Coachmark/Coachmark.css +1 -1
  17. package/dist/components/Combobox/Combobox.css +1 -1
  18. package/dist/components/ConfirmDialog/ConfirmDialog.css +1 -1
  19. package/dist/components/Counter/Counter.css +43 -0
  20. package/dist/components/Counter/Counter.d.ts +57 -0
  21. package/dist/components/Counter/Counter.js +99 -0
  22. package/dist/components/DatePicker/DatePicker.css +1 -1
  23. package/dist/components/Drawer/Drawer.css +2 -2
  24. package/dist/components/Dropdown/Dropdown.css +3 -1
  25. package/dist/components/Dropdown/Dropdown.d.ts +17 -0
  26. package/dist/components/Dropdown/Dropdown.js +22 -2
  27. package/dist/components/EmptyState/EmptyState.css +12 -0
  28. package/dist/components/EmptyState/EmptyState.d.ts +3 -0
  29. package/dist/components/EmptyState/EmptyState.js +1 -0
  30. package/dist/components/Kanban/Kanban.css +2 -2
  31. package/dist/components/Kanban/KanbanCard.css +2 -2
  32. package/dist/components/List/List.css +83 -0
  33. package/dist/components/List/List.d.ts +3 -0
  34. package/dist/components/List/List.js +6 -2
  35. package/dist/components/List/ListItem.d.ts +19 -0
  36. package/dist/components/List/ListItem.js +5 -1
  37. package/dist/components/Modal/Modal.css +3 -4
  38. package/dist/components/MultiSelect/MultiSelect.css +1 -1
  39. package/dist/components/OrgChart/OrgChart.css +2 -2
  40. package/dist/components/PhoneInput/PhoneInput.css +2 -2
  41. package/dist/components/Popover/Popover.css +1 -1
  42. package/dist/components/SegmentedControl/SegmentedControl.css +1 -1
  43. package/dist/components/Select/Select.css +1 -1
  44. package/dist/components/StatCard/StatCard.css +99 -5
  45. package/dist/components/StatCard/StatCard.d.ts +12 -6
  46. package/dist/components/StatCard/StatCard.js +6 -2
  47. package/dist/components/Switch/Switch.css +2 -2
  48. package/dist/components/Text/Text.d.ts +6 -0
  49. package/dist/components/TimePicker/TimePicker.css +1 -1
  50. package/dist/components/Toast/Toast.css +2 -2
  51. package/dist/components/Tooltip/Tooltip.css +23 -3
  52. package/dist/components/Tooltip/Tooltip.js +33 -2
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +1 -0
  55. package/dist/teste/PedidosMatheus.test.d.ts +1 -0
  56. package/dist/teste/PedidosMatheus.test.js +91 -0
  57. package/dist/teste/PedidosMatheus2.test.d.ts +1 -0
  58. package/dist/teste/PedidosMatheus2.test.js +82 -0
  59. package/dist/teste/SegmentedControlSuperficie.test.js +1 -1
  60. package/dist/teste/StatCardTamanhoSm.test.js +8 -0
  61. package/package.json +3 -3
  62. package/src/index.ts +2 -0
  63. package/src/teste/PedidosMatheus.test.tsx +129 -0
  64. package/src/teste/PedidosMatheus2.test.tsx +102 -0
  65. package/src/teste/SegmentedControlSuperficie.test.tsx +1 -1
  66. package/src/teste/StatCardTamanhoSm.test.tsx +9 -0
@@ -104,7 +104,7 @@
104
104
  box-sizing: border-box;
105
105
  padding-bottom: var(--umds-primitive-spacing-quarck); /* 4px */
106
106
  border-top: none;
107
- box-shadow: var(--umds-semantic-shadow-md);
107
+ box-shadow: var(--umds-semantic-shadow-top-sm);
108
108
  /* o disco ultrapassa o topo da barra: sem isto ele seria recortado */
109
109
  overflow: visible;
110
110
  }
@@ -131,7 +131,7 @@
131
131
  espaço, para o rótulo não descer junto e a linha de rótulos ficar reta. */
132
132
  margin-top: -12px;
133
133
  margin-bottom: -4px;
134
- box-shadow: var(--umds-semantic-shadow-sm);
134
+ box-shadow: var(--umds-semantic-shadow-bottom-sm);
135
135
  flex-shrink: 0;
136
136
  }
137
137
 
@@ -45,7 +45,7 @@
45
45
  background-color: var(--umds-semantic-color-background-elevated);
46
46
  border-top-left-radius: var(--umds-primitive-border-radius-sm);
47
47
  border-top-right-radius: var(--umds-primitive-border-radius-sm);
48
- box-shadow: var(--umds-semantic-shadow-lg);
48
+ box-shadow: var(--umds-semantic-shadow-top-sm);
49
49
  outline: none;
50
50
  animation: umds-bottomsheet-entrada var(--umds-semantic-motion-duration-base)
51
51
  var(--umds-semantic-motion-easing-standard);
@@ -153,7 +153,7 @@
153
153
  margin: 0 calc(-1 * var(--umds-primitive-spacing-xxxs))
154
154
  calc(-1 * var(--umds-primitive-spacing-xxxs));
155
155
  background-color: var(--umds-semantic-color-background-elevated);
156
- box-shadow: var(--umds-semantic-shadow-md);
156
+ box-shadow: var(--umds-semantic-shadow-top-sm);
157
157
  flex-shrink: 0;
158
158
  }
159
159
 
@@ -96,9 +96,9 @@
96
96
  .umds-box--radius-circular { border-radius: var(--umds-primitive-border-radius-circular); }
97
97
 
98
98
  /* ── Elevação ── */
99
- .umds-box--shadow-level1 { box-shadow: var(--umds-primitive-shadow-level-1); }
100
- .umds-box--shadow-level2 { box-shadow: var(--umds-primitive-shadow-level-2); }
101
- .umds-box--shadow-level3 { box-shadow: var(--umds-primitive-shadow-level-3); }
99
+ .umds-box--shadow-level1 { box-shadow: var(--umds-semantic-shadow-bottom-md); }
100
+ .umds-box--shadow-level2 { box-shadow: var(--umds-semantic-shadow-bottom-lg); }
101
+ .umds-box--shadow-level3 { box-shadow: var(--umds-semantic-shadow-bottom-xl); }
102
102
 
103
103
  /* ── Layout flex (direction, gap, align, justify, wrap) ──
104
104
  Liga quando qualquer uma das props é passada; mesmos nomes e valores do Stack. */
@@ -22,6 +22,7 @@
22
22
  line-height: 1;
23
23
  white-space: nowrap;
24
24
  text-decoration: none;
25
+ box-sizing: border-box; /* com href o Button é <a>, que nasce content-box */
25
26
 
26
27
  transition:
27
28
  background-color var(--umds-semantic-motion-duration-fast) var(--umds-semantic-motion-easing-standard),
@@ -210,8 +211,12 @@
210
211
  .umds-button--icon.umds-button--md { width: var(--umds-primitive-spacing-sm); } /* 40px */
211
212
  .umds-button--icon.umds-button--lg { width: var(--umds-primitive-spacing-md); } /* 48px */
212
213
 
214
+ /* Camada de estado translúcida, da cor do próprio ícone: funciona sobre
215
+ qualquer fundo (o ⋮ num item azul selecionado não vira um quadrado branco).
216
+ 12% de neutral-dark-down (#565656) sobre o branco dá #EBEBEB, o mesmo
217
+ neutral-light-medium de antes: no fundo claro o visual não muda. */
213
218
  .umds-button--icon:hover:not(:disabled) {
214
- background-color: var(--umds-semantic-color-neutral-light-medium);
219
+ background-color: color-mix(in srgb, currentColor 12%, transparent);
215
220
  }
216
221
 
217
222
  .umds-button--icon:disabled {
@@ -22,8 +22,11 @@ import './Button.css';
22
22
  * @property {boolean} autoFocus - Foca o botão automaticamente ao montar
23
23
  * @property {number} tabIndex - Ordem de tabulação nativa
24
24
  * @property {object} style - Estilos inline aplicados ao botão (objeto no React, string/objeto no Vue)
25
- * @quandoUsar AÇÃO que muda estado: salvar, enviar, abrir modal, confirmar. Use `variant="icon"` para ação só com ícone (sempre com `ariaLabel`).
26
- * @quandoNaoUsar Para NAVEGAR para outra página ou recurso — use `Link`, que gera `<a>` e permite abrir em nova aba. A regra: se o usuário pode querer "abrir em nova aba", é `Link`.
25
+ * @property {string} href - Botão que NAVEGA: com `href` o Button vira `<a>` com a mesma aparência (Ctrl+clique e "abrir em nova aba" funcionam). Desabilitado, volta a ser `<button disabled>`.
26
+ * @property {string} target - Destino do link (`_blank` ganha `rel="noopener noreferrer"`)
27
+ * @property {string} rel - Relação do link; sobrescreve o padrão do `target`
28
+ * @quandoUsar AÇÃO que muda estado: salvar, enviar, abrir modal, confirmar. Use `variant="icon"` para ação só com ícone (sempre com `ariaLabel`). Para navegação com cara de botão (o "Ver todos" de um rodapé de card), passe `href`: sai um `<a>` de verdade.
29
+ * @quandoNaoUsar Para navegar com cara de texto, no meio de uma frase, use `Link`. Nunca navegue por `onClick`: sem `href` não há Ctrl+clique nem "abrir em nova aba".
27
30
  */
28
31
  export interface ButtonProps {
29
32
  variant?: "primary" | "secondary" | "neutral" | "neutral-text" | "text" | "negative" | "negative-outlined" | "icon";
@@ -48,6 +51,12 @@ export interface ButtonProps {
48
51
  form?: string;
49
52
  autoFocus?: boolean;
50
53
  tabIndex?: number;
54
+ /** Botão que navega: vira `<a>` com a mesma aparência. Desabilitado, volta a ser `<button>`. */
55
+ href?: string;
56
+ /** Destino do link. `_blank` ganha `rel="noopener noreferrer"`. */
57
+ target?: string;
58
+ /** Relação do link. Sobrescreve o padrão do `target`. */
59
+ rel?: string;
51
60
  }
52
61
  import { IconProp } from "../Icon/Icon";
53
62
  declare function Button(props: ButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -63,15 +63,27 @@ function Button(props) {
63
63
  function endIconIsIcon() {
64
64
  return isIconValue(props.endIcon);
65
65
  }
66
+ function isLink() {
67
+ return !!props.href && !props.disabled;
68
+ }
69
+ function relValue() {
70
+ if (props.rel)
71
+ return props.rel;
72
+ return props.target === "_blank" ? "noopener noreferrer" : undefined;
73
+ }
66
74
  function handleClick(event) {
67
75
  // Clique bloqueado durante o carregamento — evita dupla submissão
68
- // sem mudar o visual (o botão não fica disabled).
69
- if (props.loading)
76
+ // sem mudar o visual (o botão não fica disabled). No `<a>`, segura
77
+ // também a navegação.
78
+ if (props.loading) {
79
+ if (event && event.preventDefault)
80
+ event.preventDefault();
70
81
  return;
82
+ }
71
83
  if (props.onClick) {
72
84
  props.onClick(event);
73
85
  }
74
86
  }
75
- return (_jsxs("button", { type: props.type || "button", disabled: props.disabled, id: props.id, name: props.name, value: props.value, form: props.form, autoFocus: props.autoFocus || undefined, tabIndex: props.tabIndex, "aria-label": props.ariaLabel, "aria-busy": props.loading, onClick: (event) => handleClick(event), className: classes(), children: [showLoadingAtStart() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showStartIcon() ? (_jsx(_Fragment, { children: startIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.startIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.startIcon }) })) })) : null, showLoadingAtCenter() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showLoadingAtCenter() ? (_jsx("span", { className: "umds-button__visually-hidden", children: props.children })) : null, showChildren() ? _jsx(_Fragment, { children: props.children }) : null, showEndIcon() ? (_jsx(_Fragment, { children: endIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.endIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.endIcon }) })) })) : null, showLoadingAtEnd() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null] }));
87
+ return (_jsx(_Fragment, { children: isLink() ? (_jsx(_Fragment, { children: _jsxs("a", { href: props.href, target: props.target, rel: relValue(), id: props.id, tabIndex: props.tabIndex, "aria-label": props.ariaLabel, "aria-busy": props.loading, onClick: (event) => handleClick(event), className: classes(), children: [showLoadingAtStart() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showStartIcon() ? (_jsx(_Fragment, { children: startIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.startIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.startIcon }) })) })) : null, showLoadingAtCenter() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showLoadingAtCenter() ? (_jsx("span", { className: "umds-button__visually-hidden", children: props.children })) : null, showChildren() ? _jsx(_Fragment, { children: props.children }) : null, showEndIcon() ? (_jsx(_Fragment, { children: endIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.endIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.endIcon }) })) })) : null, showLoadingAtEnd() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null] }) })) : (_jsx(_Fragment, { children: _jsxs("button", { type: props.type || "button", disabled: props.disabled, id: props.id, name: props.name, value: props.value, form: props.form, autoFocus: props.autoFocus || undefined, tabIndex: props.tabIndex, "aria-label": props.ariaLabel, "aria-busy": props.loading, onClick: (event) => handleClick(event), className: classes(), children: [showLoadingAtStart() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showStartIcon() ? (_jsx(_Fragment, { children: startIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.startIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.startIcon }) })) })) : null, showLoadingAtCenter() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showLoadingAtCenter() ? (_jsx("span", { className: "umds-button__visually-hidden", children: props.children })) : null, showChildren() ? _jsx(_Fragment, { children: props.children }) : null, showEndIcon() ? (_jsx(_Fragment, { children: endIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.endIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.endIcon }) })) })) : null, showLoadingAtEnd() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null] }) })) }));
76
88
  }
77
89
  export default Button;
@@ -15,10 +15,10 @@
15
15
 
16
16
  /* ── Sombras (tokens semânticos oficiais — TOKEN-MAP §3b) ── */
17
17
  .umds-card--shadow-none { box-shadow: none; }
18
- .umds-card--shadow-xs { box-shadow: var(--umds-semantic-shadow-xs); }
19
- .umds-card--shadow-sm { box-shadow: var(--umds-semantic-shadow-sm); }
20
- .umds-card--shadow-md { box-shadow: var(--umds-semantic-shadow-md); }
21
- .umds-card--shadow-lg { box-shadow: var(--umds-semantic-shadow-lg); }
18
+ .umds-card--shadow-xs { box-shadow: var(--umds-semantic-shadow-bottom-xs); }
19
+ .umds-card--shadow-sm { box-shadow: var(--umds-semantic-shadow-bottom-sm); }
20
+ .umds-card--shadow-md { box-shadow: var(--umds-semantic-shadow-bottom-md); }
21
+ .umds-card--shadow-lg { box-shadow: var(--umds-semantic-shadow-bottom-lg); }
22
22
 
23
23
  /* ── Padding ── */
24
24
  .umds-card--padding-none { padding: 0; }
@@ -37,7 +37,7 @@
37
37
 
38
38
  .umds-card--hoverable:hover {
39
39
  border-color: var(--umds-semantic-color-primary-medium); /* #6393DB */
40
- box-shadow: var(--umds-semantic-shadow-md); /* protótipo --shadow-md */
40
+ box-shadow: var(--umds-semantic-shadow-bottom-md); /* protótipo --shadow-md */
41
41
  transform: translateY(-1px);
42
42
  }
43
43
 
@@ -215,7 +215,7 @@
215
215
  border: var(--umds-primitive-border-width-hairline) solid var(--umds-semantic-color-neutral-light-medium);
216
216
  border-radius: var(--umds-primitive-border-radius-sm);
217
217
  background-color: var(--umds-semantic-color-neutral-light-pure);
218
- box-shadow: var(--umds-primitive-shadow-level-2);
218
+ box-shadow: var(--umds-semantic-shadow-bottom-lg);
219
219
  font-size: var(--umds-primitive-font-size-xxxs); /* 12px */
220
220
  line-height: var(--umds-primitive-font-line-height-lg);
221
221
  white-space: nowrap;
@@ -64,7 +64,7 @@
64
64
  background: var(--umds-semantic-color-background-white);
65
65
  color: var(--umds-semantic-color-neutral-dark-pure);
66
66
  border-radius: var(--umds-primitive-border-radius-md); /* 16px */
67
- box-shadow: var(--umds-primitive-shadow-level-3);
67
+ box-shadow: var(--umds-semantic-shadow-bottom-xl);
68
68
  outline: none;
69
69
  }
70
70
 
@@ -231,7 +231,7 @@
231
231
  background-color: var(--umds-semantic-color-background-elevated);
232
232
  border: 1px solid var(--umds-semantic-color-neutral-light-down);
233
233
  border-radius: var(--umds-primitive-border-radius-sm);
234
- box-shadow: var(--umds-semantic-shadow-md);
234
+ box-shadow: var(--umds-semantic-shadow-bottom-md);
235
235
  z-index: var(--umds-semantic-layer-popover);
236
236
  max-height: 300px; /* literal, igual ao Select — candidato a novo token */
237
237
  display: flex;
@@ -39,7 +39,7 @@
39
39
  .umds-confirm-dialog {
40
40
  background: var(--umds-semantic-color-background-elevated); /* superfície elevada: igual ao white no light; mais clara no dark (decisão 25/07) */
41
41
  border-radius: var(--umds-primitive-border-radius-sm); /* 8px universal (rodada ago/2026) — era md */
42
- box-shadow: var(--umds-semantic-shadow-lg); /* protótipo --shadow-lg */
42
+ box-shadow: var(--umds-semantic-shadow-center-xl); /* modal: mesma sombra do Modal */
43
43
  width: 100%;
44
44
  max-width: 440px;
45
45
  overflow: hidden;
@@ -0,0 +1,43 @@
1
+ /* ─────────────────────────────────────────────
2
+ COUNTER
3
+ Número que conta até o valor. Herda fonte, tamanho e cor de onde está.
4
+ A reserva (cópia invisível do valor final) e o número que corre ocupam a
5
+ mesma célula da grade: a largura é a do valor final desde o primeiro quadro.
6
+ ───────────────────────────────────────────── */
7
+
8
+ .umds-counter {
9
+ /* Contém a cópia escondida (__leitura, absolute): sem isso ela escapa do
10
+ overflow de uma área que rola (o mesmo bug corrigido no Tooltip). */
11
+ position: relative;
12
+ display: inline-grid;
13
+ font-variant-numeric: tabular-nums;
14
+ white-space: nowrap;
15
+ }
16
+
17
+ .umds-counter__reserva,
18
+ .umds-counter__numero {
19
+ grid-area: 1 / 1;
20
+ }
21
+
22
+ .umds-counter__reserva {
23
+ visibility: hidden;
24
+ }
25
+
26
+ .umds-counter__numero {
27
+ justify-self: end;
28
+ }
29
+
30
+ /* Valor final para o leitor de tela. Receita visually-hidden; os 1px não são
31
+ medida de design. */
32
+ .umds-counter__leitura {
33
+ position: absolute;
34
+ width: 1px;
35
+ height: 1px;
36
+ margin: -1px;
37
+ padding: 0;
38
+ border: 0;
39
+ overflow: hidden;
40
+ clip: rect(0, 0, 0, 0);
41
+ clip-path: inset(50%);
42
+ white-space: nowrap;
43
+ }
@@ -0,0 +1,57 @@
1
+ import './Counter.css';
2
+ /**
3
+ * Counter — número que conta até o valor.
4
+ *
5
+ * Ao aparecer, o número sobe de `from` (0) até `value`; quando `value` muda,
6
+ * conta do valor atual até o novo. É o número do painel ("1.284 processos") e o
7
+ * total de páginas da caixa de entrada. Herda fonte, tamanho e cor de onde
8
+ * está, então serve dentro de um título, de um `StatCard` ou no meio do texto.
9
+ *
10
+ * Três decisões que valem saber:
11
+ *
12
+ * - **O leitor de tela lê só o valor final.** O número que corre é
13
+ * `aria-hidden`; uma cópia escondida com o valor final é o que se anuncia.
14
+ * Ninguém ouve "1, 37, 210, 1.284".
15
+ * - **A largura não pula.** Uma cópia invisível do valor final reserva o
16
+ * espaço, e os algarismos são tabulares: o texto em volta não dança enquanto
17
+ * o número conta.
18
+ * - **Movimento reduzido é respeitado.** Com `prefers-reduced-motion` (ou
19
+ * `disableAnimation`) o número aparece direto no valor final.
20
+ *
21
+ * O formato é o do `Intl.NumberFormat` em `locale` (pt-BR: "1.284", "22,31").
22
+ * As casas decimais seguem as do `value`, a menos que `decimals` diga outra
23
+ * coisa.
24
+ * @property {number} value - Valor final (número ou texto numérico)
25
+ * @property {number} from - De onde a contagem começa ao montar (padrão 0)
26
+ * @property {number} duration - Duração da contagem em ms (padrão 1000)
27
+ * @property {number} decimals - Casas decimais (padrão: as do `value`)
28
+ * @property {string} locale - Idioma do formato (padrão "pt-BR")
29
+ * @property {string} prefix - Texto antes do número ("R$ ")
30
+ * @property {string} suffix - Texto depois do número ("%", " páginas")
31
+ * @property {boolean} disableAnimation - Mostra o valor final direto, sem contar
32
+ * @property {string} className - Classes CSS adicionais
33
+ * @quandoUsar Número de destaque que merece atenção ao aparecer: KPI de painel, total de páginas ou de itens de uma caixa de entrada, contadores de resultado.
34
+ * @quandoNaoUsar Para contagem pequena presa a outro elemento (não lidos de uma aba), use `Badge`. Para número que muda o tempo todo (cronômetro, campo editável), mostre o texto direto: a animação a cada mudança cansa. Em tabela (`DataTable`), número não anima.
35
+ */
36
+ export interface CounterProps {
37
+ /** Valor final. Aceita número ou texto numérico ("1284", "22.31"). */
38
+ value: number | string;
39
+ /** De onde a contagem começa ao montar. @default 0 */
40
+ from?: number;
41
+ /** Duração da contagem, em milissegundos. @default 1000 */
42
+ duration?: number;
43
+ /** Casas decimais. Sem a prop, as do `value`. */
44
+ decimals?: number;
45
+ /** Idioma do formato do número. @default 'pt-BR' */
46
+ locale?: string;
47
+ /** Texto antes do número ("R$ "). */
48
+ prefix?: string;
49
+ /** Texto depois do número ("%", " páginas"). */
50
+ suffix?: string;
51
+ /** Mostra o valor final direto, sem contar. @default false */
52
+ disableAnimation?: boolean;
53
+ /** Classes CSS adicionais. */
54
+ className?: string;
55
+ }
56
+ declare function Counter(props: CounterProps): import("react/jsx-runtime").JSX.Element;
57
+ export default Counter;
@@ -0,0 +1,99 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import './Counter.css';
4
+ import { useState, useRef, useEffect } from "react";
5
+ function Counter(props) {
6
+ const quadroRef = useRef(null);
7
+ const ultimoRef = useRef(null);
8
+ const alvoRef = useRef(null);
9
+ const [exibido, setExibido] = useState(() => null);
10
+ function alvo() {
11
+ const numero = Number(props.value);
12
+ return isFinite(numero) ? numero : 0;
13
+ }
14
+ function casas() {
15
+ if (typeof props.decimals === "number")
16
+ return props.decimals;
17
+ const texto = String(props.value);
18
+ const ponto = texto.indexOf(".");
19
+ return ponto < 0 ? 0 : Math.min(texto.length - ponto - 1, 4);
20
+ }
21
+ function formatar(numero) {
22
+ const digitos = casas();
23
+ const formatado = new Intl.NumberFormat(props.locale || "pt-BR", {
24
+ minimumFractionDigits: digitos,
25
+ maximumFractionDigits: digitos,
26
+ }).format(numero);
27
+ return (props.prefix || "") + formatado + (props.suffix || "");
28
+ }
29
+ function semAnimacao() {
30
+ if (props.disableAnimation)
31
+ return true;
32
+ if (typeof window === "undefined" || !window.matchMedia)
33
+ return false;
34
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
35
+ }
36
+ function parar() {
37
+ if (quadroRef.current !== null && typeof window !== "undefined") {
38
+ window.cancelAnimationFrame(quadroRef.current);
39
+ }
40
+ quadroRef.current = null;
41
+ }
42
+ function animar(de, para) {
43
+ parar();
44
+ const duracao = typeof props.duration === "number" ? props.duration : 1000;
45
+ if (semAnimacao() ||
46
+ duracao <= 0 ||
47
+ de === para ||
48
+ typeof window === "undefined") {
49
+ ultimoRef.current = para;
50
+ setExibido(para);
51
+ return;
52
+ }
53
+ const inicio = performance.now();
54
+ const passo = (agora) => {
55
+ const progresso = Math.min(1, (agora - inicio) / duracao);
56
+ // ease-out cúbico: rápido no começo, assenta devagar no valor.
57
+ const suave = 1 - Math.pow(1 - progresso, 3);
58
+ const atual = progresso >= 1 ? para : de + (para - de) * suave;
59
+ ultimoRef.current = atual;
60
+ setExibido(atual);
61
+ quadroRef.current =
62
+ progresso < 1 ? window.requestAnimationFrame(passo) : null;
63
+ };
64
+ quadroRef.current = window.requestAnimationFrame(passo);
65
+ }
66
+ function textoNaTela() {
67
+ if (exibido !== null)
68
+ return formatar(exibido);
69
+ if (props.disableAnimation)
70
+ return formatar(alvo());
71
+ return formatar(typeof props.from === "number" ? props.from : 0);
72
+ }
73
+ function textoFinal() {
74
+ return formatar(alvo());
75
+ }
76
+ function classes() {
77
+ return ["umds-counter", props.className].filter(Boolean).join(" ");
78
+ }
79
+ useEffect(() => {
80
+ const para = alvo();
81
+ alvoRef.current = para;
82
+ animar(typeof props.from === "number" ? props.from : 0, para);
83
+ }, []);
84
+ useEffect(() => {
85
+ const para = alvo();
86
+ // O React roda este efeito também na montagem: o onMount já cuidou dela.
87
+ if (ultimoRef.current === null || alvoRef.current === para)
88
+ return;
89
+ alvoRef.current = para;
90
+ animar(ultimoRef.current, para);
91
+ }, [props.value]);
92
+ useEffect(() => {
93
+ return () => {
94
+ parar();
95
+ };
96
+ }, []);
97
+ return (_jsxs("span", { className: classes(), children: [_jsx("span", { className: "umds-counter__reserva", "aria-hidden": "true", children: textoFinal() }), _jsx("span", { className: "umds-counter__numero", "aria-hidden": "true", children: textoNaTela() }), _jsx("span", { className: "umds-counter__leitura", children: textoFinal() })] }));
98
+ }
99
+ export default Counter;
@@ -178,7 +178,7 @@
178
178
  background: var(--umds-semantic-color-background-elevated); /* superfície elevada: igual ao white no light; mais clara no dark (decisão 25/07) */
179
179
  border: 1px solid var(--umds-semantic-color-neutral-light-down); /* #D5D5D5 */
180
180
  border-radius: var(--umds-primitive-border-radius-sm); /* 8px */
181
- box-shadow: var(--umds-semantic-shadow-md);
181
+ box-shadow: var(--umds-semantic-shadow-bottom-md);
182
182
  padding: var(--umds-primitive-spacing-xxxs); /* 16px */
183
183
  gap: var(--umds-primitive-spacing-xxxs); /* 16px */
184
184
  box-sizing: border-box;
@@ -71,7 +71,7 @@
71
71
  box-sizing: border-box;
72
72
  padding: var(--umds-primitive-spacing-xxxs);
73
73
  background-color: var(--umds-semantic-color-background-elevated);
74
- box-shadow: var(--umds-semantic-shadow-lg);
74
+ box-shadow: var(--umds-semantic-shadow-bottom-lg);
75
75
  outline: none;
76
76
  }
77
77
 
@@ -189,7 +189,7 @@
189
189
  margin: 0 calc(-1 * var(--umds-primitive-spacing-xxxs))
190
190
  calc(-1 * var(--umds-primitive-spacing-xxxs));
191
191
  background-color: var(--umds-semantic-color-background-elevated);
192
- box-shadow: var(--umds-semantic-shadow-md);
192
+ box-shadow: var(--umds-semantic-shadow-bottom-md);
193
193
  flex-shrink: 0;
194
194
  }
195
195
 
@@ -35,7 +35,7 @@
35
35
  background: var(--umds-semantic-color-background-elevated); /* superfície elevada: igual ao white no light; mais clara no dark (decisão 25/07) */
36
36
  border: 1px solid var(--umds-semantic-color-neutral-light-down); /* #D5D5D5 */
37
37
  border-radius: var(--umds-primitive-border-radius-sm); /* era 12px (protótipo --radius-lg) — snap 14/07: painel overlay → md */
38
- box-shadow: var(--umds-semantic-shadow-md); /* era 0 4px 16px rgba(0,0,0,0.12) — mapeado para shadow-md 14/07 */
38
+ box-shadow: var(--umds-semantic-shadow-bottom-md); /* era 0 4px 16px rgba(0,0,0,0.12) — mapeado para shadow-md 14/07 */
39
39
  font-family: var(--umds-semantic-font-family-base);
40
40
  min-width: 160px; /* dimensão do protótipo sem token 1DS */
41
41
  transition: opacity var(--umds-semantic-motion-duration-fast) var(--umds-semantic-motion-easing-standard);
@@ -55,10 +55,12 @@
55
55
  align-items: center;
56
56
  gap: var(--umds-primitive-spacing-nano); /* 8px */
57
57
  width: 100%;
58
+ box-sizing: border-box; /* o item com href é <a>, que nasce content-box */
58
59
  padding: var(--umds-primitive-spacing-nano) var(--umds-primitive-spacing-xxxs); /* 8px 16px */
59
60
  background: none;
60
61
  border: none;
61
62
  text-align: left;
63
+ text-decoration: none;
62
64
  font-family: var(--umds-semantic-font-family-base);
63
65
  font-size: var(--umds-primitive-font-size-xxs); /* 14px */
64
66
  font-weight: 400;
@@ -7,14 +7,31 @@ import './Dropdown.css';
7
7
  * @property {boolean} disabled - Desabilita o item (não selecionável).
8
8
  * @property {boolean} danger - Aplica o estilo destrutivo (vermelho).
9
9
  * @property {boolean} dividerAfter - Adiciona um divisor abaixo do item.
10
+ * @property {string} href - Item que navega: vira `<a>` de verdade (Ctrl+clique e "abrir em nova aba" funcionam). O `onSelect` continua sendo chamado.
11
+ * @property {string} target - Destino do link (`_blank` ganha `rel="noopener noreferrer"`).
12
+ * @property {string} rel - Relação do link; sobrescreve o padrão do `target`.
13
+ * @property {function} onSelect - Ação deste item: chamada ao escolhê-lo, antes do `onSelect` do Dropdown. Dispensa o switch por `key` e o cast para um tipo próprio.
14
+ *
15
+ * O item aceita campos próprios (`command`, `id`, `payload`...), que voltam
16
+ * intactos no `onSelect`: não é preciso converter para outro tipo.
10
17
  */
11
18
  export interface DropdownItem {
19
+ /** Ação deste item, chamada ao escolhê-lo (antes do `onSelect` do Dropdown). */
20
+ onSelect?: (item: DropdownItem) => void;
21
+ /** Campos próprios do produto (`command`, `payload`...) voltam intactos no `onSelect`. */
22
+ [campo: string]: any;
12
23
  key: string;
13
24
  label: string;
14
25
  icon?: IconProp;
15
26
  disabled?: boolean;
16
27
  danger?: boolean;
17
28
  dividerAfter?: boolean;
29
+ /** Item que navega: vira `<a>` (Ctrl+clique e nova aba funcionam). Desabilitado, volta a ser botão. */
30
+ href?: string;
31
+ /** Destino do link. `_blank` ganha `rel="noopener noreferrer"`. */
32
+ target?: string;
33
+ /** Relação do link. Sobrescreve o padrão do `target`. */
34
+ rel?: string;
18
35
  }
19
36
  /**
20
37
  * Dropdown component — menu de ações ancorado a um trigger.
@@ -211,6 +211,8 @@ function Dropdown(props) {
211
211
  function handleSelect(item) {
212
212
  if (item.disabled)
213
213
  return;
214
+ if (item.onSelect)
215
+ item.onSelect(item);
214
216
  if (props.onSelect) {
215
217
  props.onSelect(item);
216
218
  }
@@ -302,8 +304,21 @@ function Dropdown(props) {
302
304
  activateIndex(nextEnabledIndex(total, -1));
303
305
  }
304
306
  else if (key === "Enter" || key === " ") {
305
- event.preventDefault();
306
307
  const item = itemList()[focusedIndex()];
308
+ // Item com href é <a>: o Enter fica com o navegador, que dispara o
309
+ // clique do link (e respeita Ctrl/Shift para nova aba/janela). O
310
+ // onClick do <a> chama o handleSelect. Espaço não ativa link no
311
+ // navegador, então vira um clique de verdade.
312
+ if (item && item.href && !item.disabled) {
313
+ if (key === " ") {
314
+ event.preventDefault();
315
+ const alvo = event.target;
316
+ if (alvo && alvo.click)
317
+ alvo.click();
318
+ }
319
+ return;
320
+ }
321
+ event.preventDefault();
307
322
  if (item)
308
323
  handleSelect(item);
309
324
  }
@@ -312,6 +327,11 @@ function Dropdown(props) {
312
327
  close();
313
328
  }
314
329
  }
330
+ function relDoItem(item) {
331
+ if (item.rel)
332
+ return item.rel;
333
+ return item.target === "_blank" ? "noopener noreferrer" : undefined;
334
+ }
315
335
  function itemClasses(item) {
316
336
  return [
317
337
  "umds-dropdown__item",
@@ -370,6 +390,6 @@ function Dropdown(props) {
370
390
  }, []);
371
391
  return (_jsxs("span", { ref: triggerRef, className: triggerClasses(), style: {
372
392
  display: "inline-flex",
373
- }, children: [_jsx("span", { className: "umds-dropdown__trigger", ref: triggerButtonRef, role: triggerIsInteractive ? undefined : "button", tabIndex: triggerIsInteractive ? undefined : 0, "aria-haspopup": triggerIsInteractive ? undefined : "menu", "aria-expanded": triggerIsInteractive ? undefined : ariaExpandedText(), onClick: (event) => toggle(), onKeyDown: (event) => handleTriggerKeydown(event), children: _jsx(_Fragment, { children: props.children }) }), _jsx(Portal, { children: mounted ? (_jsx("span", { className: "umds-dropdown__floating", ref: floatingRef, style: floatingStyle(), children: _jsx("ul", { className: "umds-dropdown__menu", role: "menu", ref: menuRef, "aria-label": props.ariaLabel, tabIndex: -1, style: menuStyle(), onKeyDown: (event) => handleMenuKeydown(event), children: itemList()?.map((item, index) => (_jsx("li", { role: "none", children: _jsxs("button", { type: "button", role: "menuitem", className: itemClasses(item), tabIndex: index === activeIndex ? 0 : -1, "data-active": index === activeIndex, disabled: item.disabled, onClick: (event) => handleSelect(item), children: [item.icon ? (_jsx("span", { className: "umds-dropdown__item-icon", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: item.icon }) })) : null, _jsx("span", { className: "umds-dropdown__item-label", children: item.label })] }) }, item.key))) }) })) : null })] }));
393
+ }, children: [_jsx("span", { className: "umds-dropdown__trigger", ref: triggerButtonRef, role: triggerIsInteractive ? undefined : "button", tabIndex: triggerIsInteractive ? undefined : 0, "aria-haspopup": triggerIsInteractive ? undefined : "menu", "aria-expanded": triggerIsInteractive ? undefined : ariaExpandedText(), onClick: (event) => toggle(), onKeyDown: (event) => handleTriggerKeydown(event), children: _jsx(_Fragment, { children: props.children }) }), _jsx(Portal, { children: mounted ? (_jsx("span", { className: "umds-dropdown__floating", ref: floatingRef, style: floatingStyle(), children: _jsx("ul", { className: "umds-dropdown__menu", role: "menu", ref: menuRef, "aria-label": props.ariaLabel, tabIndex: -1, style: menuStyle(), onKeyDown: (event) => handleMenuKeydown(event), children: itemList()?.map((item, index) => (_jsx("li", { role: "none", children: item.href && !item.disabled ? (_jsxs("a", { role: "menuitem", href: item.href, target: item.target, rel: relDoItem(item), className: itemClasses(item), tabIndex: index === activeIndex ? 0 : -1, "data-active": index === activeIndex, onClick: (event) => handleSelect(item), children: [item.icon ? (_jsx("span", { className: "umds-dropdown__item-icon", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: item.icon }) })) : null, _jsx("span", { className: "umds-dropdown__item-label", children: item.label })] })) : (_jsx(_Fragment, { children: _jsxs("button", { type: "button", role: "menuitem", className: itemClasses(item), tabIndex: index === activeIndex ? 0 : -1, "data-active": index === activeIndex, disabled: item.disabled, onClick: (event) => handleSelect(item), children: [item.icon ? (_jsx("span", { className: "umds-dropdown__item-icon", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: item.icon }) })) : null, _jsx("span", { className: "umds-dropdown__item-label", children: item.label })] }) })) }, item.key))) }) })) : null })] }));
374
394
  }
375
395
  export default Dropdown;
@@ -130,3 +130,15 @@
130
130
  outline: 2px solid var(--umds-semantic-color-primary-pure);
131
131
  outline-offset: 2px;
132
132
  }
133
+
134
+ /* ── fill: ocupa e centraliza no espaço disponível ──
135
+ Para o vazio de uma coluna que rola: cresce no pai flex (flex: 1) e, num pai
136
+ com altura definida, ocupa a altura toda (min-height). O conteúdo continua
137
+ centralizado pela regra base. */
138
+ .umds-empty-state--fill {
139
+ flex: 1 1 auto;
140
+ align-self: stretch;
141
+ box-sizing: border-box;
142
+ width: 100%;
143
+ min-height: 100%;
144
+ }
@@ -11,6 +11,7 @@ import './EmptyState.css';
11
11
  * @property {function} onAction - Callback acionado ao clicar no botão de ação
12
12
  * @property {string} size - Tamanho do bloco: 'sm' | 'md' | 'lg'
13
13
  * @property {string} className - Classes CSS adicionais
14
+ * @property {boolean} fill - Ocupa e centraliza no espaço disponível: cresce no pai flex e, num pai com altura definida, fica no meio dele (o vazio de uma coluna que rola)
14
15
  * @quandoUsar Quando NÃO HÁ dados para mostrar: lista vazia, busca sem resultado, área ainda não configurada. Traz ícone, mensagem e ação opcional.
15
16
  * @quandoNaoUsar Enquanto os dados ainda estão CARREGANDO — use `Skeleton` ou `Spinner`. Estado vazio é resposta definitiva, não espera.
16
17
  */
@@ -22,6 +23,8 @@ export interface EmptyStateProps {
22
23
  actionLabel?: string;
23
24
  onAction?: () => void;
24
25
  size?: "sm" | "md" | "lg";
26
+ /** Ocupa e centraliza no espaço disponível (coluna que rola, painel). @default false */
27
+ fill?: boolean;
25
28
  className?: string;
26
29
  }
27
30
  import { IconProp } from "../Icon/Icon";
@@ -7,6 +7,7 @@ function EmptyState(props) {
7
7
  return [
8
8
  "umds-empty-state",
9
9
  `umds-empty-state--${props.size || "md"}`,
10
+ props.fill ? "umds-empty-state--fill" : "",
10
11
  props.className,
11
12
  ]
12
13
  .filter(Boolean)
@@ -65,7 +65,7 @@
65
65
 
66
66
  .umds-kanban__handle:hover .umds-kanban-card {
67
67
  border-color: var(--umds-semantic-color-primary-medium);
68
- box-shadow: var(--umds-semantic-shadow-md);
68
+ box-shadow: var(--umds-semantic-shadow-bottom-md);
69
69
  }
70
70
 
71
71
  .umds-kanban__handle:focus {
@@ -80,7 +80,7 @@
80
80
  /* Pego pelo teclado: o cartão "levanta". */
81
81
  .umds-kanban__item--pego .umds-kanban-card {
82
82
  border-color: var(--umds-semantic-color-primary-pure);
83
- box-shadow: var(--umds-semantic-shadow-lg);
83
+ box-shadow: var(--umds-semantic-shadow-bottom-lg);
84
84
  }
85
85
 
86
86
  /* Origem do arrasto com o ponteiro: fica o lugar, esmaecido. */
@@ -18,7 +18,7 @@
18
18
  border: 1px solid var(--umds-semantic-color-neutral-light-medium);
19
19
  border-radius: var(--umds-primitive-border-radius-sm);
20
20
  background-color: var(--umds-semantic-color-background-elevated);
21
- box-shadow: var(--umds-semantic-shadow-xs);
21
+ box-shadow: var(--umds-semantic-shadow-bottom-xs);
22
22
  font-family: var(--umds-semantic-font-family-base);
23
23
  color: var(--umds-semantic-color-neutral-dark-pure);
24
24
  text-align: left;
@@ -33,7 +33,7 @@
33
33
 
34
34
  .umds-kanban-card--interativo:hover {
35
35
  border-color: var(--umds-semantic-color-primary-medium);
36
- box-shadow: var(--umds-semantic-shadow-md);
36
+ box-shadow: var(--umds-semantic-shadow-bottom-md);
37
37
  }
38
38
 
39
39
  .umds-kanban-card--interativo:focus-visible {