@1doc/1ds-react 2.9.0 → 2.10.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 (115) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/dist/components/Accordion/Accordion.css +13 -0
  3. package/dist/components/Accordion/Accordion.d.ts +3 -0
  4. package/dist/components/Accordion/Accordion.js +1 -0
  5. package/dist/components/AppSubheader/AppSubheader.css +10 -1
  6. package/dist/components/AttachmentCard/AttachmentCard.css +10 -8
  7. package/dist/components/AttachmentCard/AttachmentCard.d.ts +3 -0
  8. package/dist/components/AttachmentCard/AttachmentCard.js +8 -1
  9. package/dist/components/Badge/Badge.css +14 -1
  10. package/dist/components/Badge/Badge.d.ts +11 -2
  11. package/dist/components/Badge/Badge.js +3 -0
  12. package/dist/components/Calendar/Calendar.d.ts +7 -6
  13. package/dist/components/Coachmark/Coachmark.css +10 -0
  14. package/dist/components/Coachmark/Coachmark.d.ts +12 -0
  15. package/dist/components/Coachmark/Coachmark.js +46 -5
  16. package/dist/components/ColorPicker/ColorPicker.css +8 -1
  17. package/dist/components/ColorPicker/ColorPicker.d.ts +2 -2
  18. package/dist/components/DataTable/DataTable.css +174 -0
  19. package/dist/components/DataTable/DataTable.d.ts +63 -2
  20. package/dist/components/DataTable/DataTable.js +278 -6
  21. package/dist/components/DescriptionList/DescriptionList.css +14 -0
  22. package/dist/components/DescriptionList/DescriptionList.d.ts +3 -0
  23. package/dist/components/DescriptionList/DescriptionList.js +1 -0
  24. package/dist/components/EmptyState/EmptyState.d.ts +4 -2
  25. package/dist/components/EmptyState/EmptyState.js +2 -2
  26. package/dist/components/Input/Input.css +21 -0
  27. package/dist/components/Input/Input.d.ts +7 -0
  28. package/dist/components/Input/Input.js +2 -1
  29. package/dist/components/List/ListItem.d.ts +14 -2
  30. package/dist/components/List/ListItem.js +3 -2
  31. package/dist/components/RichTextEditor/RichTextEditor.css +47 -0
  32. package/dist/components/RichTextEditor/RichTextEditor.d.ts +22 -8
  33. package/dist/components/RichTextEditor/RichTextEditor.js +460 -3
  34. package/dist/components/SegmentedControl/SegmentedControl.css +24 -0
  35. package/dist/components/SegmentedControl/SegmentedControl.d.ts +2 -0
  36. package/dist/components/SegmentedControl/SegmentedControl.js +4 -1
  37. package/dist/components/Select/Select.css +94 -0
  38. package/dist/components/Select/Select.d.ts +26 -4
  39. package/dist/components/Select/Select.js +210 -35
  40. package/dist/components/StatCard/StatCard.css +65 -0
  41. package/dist/components/StatCard/StatCard.d.ts +9 -0
  42. package/dist/components/StatCard/StatCard.js +10 -1
  43. package/dist/components/Toast/Toast.css +32 -0
  44. package/dist/components/Toast/Toast.d.ts +18 -0
  45. package/dist/components/Toast/Toast.js +14 -1
  46. package/dist/components/Toast/ToastStack.js +8 -1
  47. package/dist/teste/AccordionTituloNeutro.test.d.ts +1 -0
  48. package/dist/teste/AccordionTituloNeutro.test.js +23 -0
  49. package/dist/teste/AppSubheaderRegiao.test.d.ts +1 -0
  50. package/dist/teste/AppSubheaderRegiao.test.js +13 -0
  51. package/dist/teste/AttachmentCardLista.test.d.ts +1 -0
  52. package/dist/teste/AttachmentCardLista.test.js +49 -0
  53. package/dist/teste/AttachmentCardSm.test.d.ts +1 -0
  54. package/dist/teste/AttachmentCardSm.test.js +26 -0
  55. package/dist/teste/BadgeNeutroColado.test.d.ts +1 -0
  56. package/dist/teste/BadgeNeutroColado.test.js +11 -0
  57. package/dist/teste/BadgeTag.test.js +13 -0
  58. package/dist/teste/CalendarCabecalho.test.d.ts +1 -0
  59. package/dist/teste/CalendarCabecalho.test.js +11 -0
  60. package/dist/teste/CoachmarkSemVeu.test.d.ts +1 -0
  61. package/dist/teste/CoachmarkSemVeu.test.js +85 -0
  62. package/dist/teste/ColorPickerXs.test.d.ts +1 -0
  63. package/dist/teste/ColorPickerXs.test.js +21 -0
  64. package/dist/teste/DataTableArvoreEVariantes.test.d.ts +1 -0
  65. package/dist/teste/DataTableArvoreEVariantes.test.js +190 -0
  66. package/dist/teste/DescriptionListCompacta.test.d.ts +1 -0
  67. package/dist/teste/DescriptionListCompacta.test.js +22 -0
  68. package/dist/teste/EmptyStateTituloRico.test.d.ts +1 -0
  69. package/dist/teste/EmptyStateTituloRico.test.js +23 -0
  70. package/dist/teste/InputAgrupado.test.d.ts +1 -0
  71. package/dist/teste/InputAgrupado.test.js +24 -0
  72. package/dist/teste/ListItemTooltipStandalone.test.d.ts +1 -0
  73. package/dist/teste/ListItemTooltipStandalone.test.js +39 -0
  74. package/dist/teste/RichTextEditorAlinhaImagemTabela.test.d.ts +1 -0
  75. package/dist/teste/RichTextEditorAlinhaImagemTabela.test.js +212 -0
  76. package/dist/teste/RichTextEditorEstiloDeTexto.test.d.ts +1 -0
  77. package/dist/teste/RichTextEditorEstiloDeTexto.test.js +120 -0
  78. package/dist/teste/SegmentedControlContador.test.d.ts +1 -0
  79. package/dist/teste/SegmentedControlContador.test.js +39 -0
  80. package/dist/teste/SelectAbertoControladoEFlutuante.test.d.ts +1 -0
  81. package/dist/teste/SelectAbertoControladoEFlutuante.test.js +145 -0
  82. package/dist/teste/SelectFiltroCorETamanho.test.d.ts +1 -0
  83. package/dist/teste/SelectFiltroCorETamanho.test.js +77 -0
  84. package/dist/teste/StatCardSecundariaETom.test.d.ts +1 -0
  85. package/dist/teste/StatCardSecundariaETom.test.js +45 -0
  86. package/dist/teste/ToastAcaoEDetalhes.test.d.ts +1 -0
  87. package/dist/teste/ToastAcaoEDetalhes.test.js +94 -0
  88. package/dist/utils/html-seguro.d.ts +19 -1
  89. package/dist/utils/html-seguro.js +113 -2
  90. package/dist/utils/toast.d.ts +10 -1
  91. package/dist/utils/toast.js +2 -1
  92. package/package.json +2 -2
  93. package/src/teste/AccordionTituloNeutro.test.tsx +25 -0
  94. package/src/teste/AppSubheaderRegiao.test.tsx +17 -0
  95. package/src/teste/AttachmentCardLista.test.tsx +53 -0
  96. package/src/teste/AttachmentCardSm.test.tsx +29 -0
  97. package/src/teste/BadgeNeutroColado.test.tsx +15 -0
  98. package/src/teste/BadgeTag.test.tsx +21 -0
  99. package/src/teste/CalendarCabecalho.test.tsx +11 -0
  100. package/src/teste/CoachmarkSemVeu.test.tsx +101 -0
  101. package/src/teste/ColorPickerXs.test.tsx +22 -0
  102. package/src/teste/DataTableArvoreEVariantes.test.tsx +240 -0
  103. package/src/teste/DescriptionListCompacta.test.tsx +24 -0
  104. package/src/teste/EmptyStateTituloRico.test.tsx +34 -0
  105. package/src/teste/InputAgrupado.test.tsx +26 -0
  106. package/src/teste/ListItemTooltipStandalone.test.tsx +64 -0
  107. package/src/teste/RichTextEditorAlinhaImagemTabela.test.tsx +238 -0
  108. package/src/teste/RichTextEditorEstiloDeTexto.test.tsx +137 -0
  109. package/src/teste/SegmentedControlContador.test.tsx +43 -0
  110. package/src/teste/SelectAbertoControladoEFlutuante.test.tsx +180 -0
  111. package/src/teste/SelectFiltroCorETamanho.test.tsx +90 -0
  112. package/src/teste/StatCardSecundariaETom.test.tsx +51 -0
  113. package/src/teste/ToastAcaoEDetalhes.test.tsx +109 -0
  114. package/src/utils/html-seguro.ts +105 -2
  115. package/src/utils/toast.ts +12 -2
@@ -191,10 +191,16 @@
191
191
  width: 100%;
192
192
  }
193
193
 
194
+ /* Fundo branco: o azul-claro do gatilho só vem depois de escolhido um valor (`--has-value`,
195
+ abaixo), que é quando o filtro está recortando a lista. O tamanho padrão é o sm (32px). */
194
196
  .umds-select--filter .umds-select__trigger {
195
197
  width: auto;
196
198
  gap: var(--umds-primitive-spacing-nano);
197
199
  border-color: var(--umds-semantic-color-primary-pure);
200
+ background-color: var(--umds-semantic-color-background-white);
201
+ }
202
+
203
+ .umds-select--filter.umds-select--has-value .umds-select__trigger {
198
204
  background-color: var(--umds-semantic-color-primary-light);
199
205
  }
200
206
 
@@ -212,6 +218,11 @@
212
218
  font-weight: var(--umds-primitive-font-weight-semibold);
213
219
  }
214
220
 
221
+ /* Fonte de 12px no filtro pequeno (o tamanho do CrisDoc); no md segue 14px. */
222
+ .umds-select--filter.umds-select--sm .umds-select__trigger .umds-text {
223
+ font-size: var(--umds-primitive-font-size-xxxs); /* 12px */
224
+ }
225
+
215
226
  .umds-select--filter .umds-select__dropdown {
216
227
  /* 240px: três vezes o degrau xxl (80px). Sem token de largura de menu. */
217
228
  width: max-content;
@@ -219,6 +230,89 @@
219
230
  max-width: calc(100vw - var(--umds-primitive-spacing-xxxs) * 2);
220
231
  }
221
232
 
233
+ /* ── Variante search: o filtro dentro de uma barra de busca ──
234
+ Altura de campo e contorno neutro, para ficar ao lado do campo de busca. */
235
+ .umds-select--filter-search .umds-select__trigger {
236
+ border-color: var(--umds-semantic-field-border-default);
237
+ background-color: var(--umds-semantic-color-background-white);
238
+ }
239
+
240
+ .umds-select--filter-search.umds-select--has-value .umds-select__trigger {
241
+ background-color: var(--umds-semantic-color-background-white);
242
+ }
243
+
244
+ .umds-select--filter-search .umds-select__trigger:hover:not(.umds-select--disabled .umds-select__trigger) {
245
+ border-color: var(--umds-semantic-field-border-hover);
246
+ }
247
+
248
+ .umds-select--filter-search .umds-select__trigger .umds-text,
249
+ .umds-select--filter-search .umds-select__actions {
250
+ color: var(--umds-semantic-color-neutral-dark-pure);
251
+ font-weight: var(--umds-primitive-font-weight-regular);
252
+ }
253
+
254
+ .umds-select--filter-search .umds-select__actions {
255
+ color: var(--umds-semantic-color-neutral-dark-medium);
256
+ }
257
+
258
+ /* ── Variante combo: o filtro colado a outro controle ──
259
+ Sem contorno nem raio próprios: quem desenha a borda é o contêiner do combo. */
260
+ .umds-select--filter-combo .umds-select__trigger {
261
+ border-color: transparent;
262
+ border-radius: var(--umds-primitive-border-radius-none);
263
+ background-color: transparent;
264
+ }
265
+
266
+ .umds-select--filter-combo.umds-select--has-value .umds-select__trigger {
267
+ background-color: transparent;
268
+ }
269
+
270
+ .umds-select--filter-combo .umds-select__trigger:hover:not(.umds-select--disabled .umds-select__trigger) {
271
+ border-color: transparent;
272
+ background-color: var(--umds-semantic-color-background-light);
273
+ }
274
+
275
+ .umds-select--filter-combo .umds-select__trigger .umds-text,
276
+ .umds-select--filter-combo .umds-select__actions {
277
+ color: var(--umds-semantic-color-neutral-dark-down);
278
+ font-weight: var(--umds-primitive-font-weight-regular);
279
+ }
280
+
281
+ /* O foco do combo é o anel do próprio gatilho, por dentro (o contêiner pode cortar o de fora). */
282
+ .umds-select--filter-combo .umds-select__trigger:focus-visible,
283
+ .umds-select--filter-combo.umds-select--open .umds-select__trigger {
284
+ border-color: transparent;
285
+ box-shadow: inset 0 0 0 var(--umds-primitive-border-width-thin) var(--umds-semantic-color-primary-pure);
286
+ }
287
+
288
+ /* ── Ícone e amostra de cor no gatilho (triggerIcon, triggerSwatch) ── */
289
+ .umds-select__trigger-icon {
290
+ display: inline-flex;
291
+ align-items: center;
292
+ flex-shrink: 0;
293
+ color: var(--umds-semantic-color-neutral-dark-medium);
294
+ }
295
+
296
+ .umds-select--filter .umds-select__trigger-icon {
297
+ color: inherit;
298
+ }
299
+
300
+ .umds-select__swatch {
301
+ flex-shrink: 0;
302
+ box-sizing: border-box;
303
+ width: var(--umds-primitive-spacing-mini);
304
+ height: var(--umds-primitive-spacing-mini);
305
+ border-radius: var(--umds-primitive-border-radius-xs);
306
+ /* Anel de 1px: amostra clara não some no fundo branco, nem escura no escuro. */
307
+ border: var(--umds-primitive-border-width-hairline) solid var(--umds-semantic-color-neutral-light-down);
308
+ }
309
+
310
+ /* ── Lista flutuante (dentro de diálogo) ──
311
+ O estilo inline (fixed, translate, largura e altura medidas) vem do componente. */
312
+ .umds-select--floating .umds-select__dropdown {
313
+ box-sizing: border-box;
314
+ }
315
+
222
316
  /* ── Busca no dropdown ── */
223
317
  .umds-select__search-container {
224
318
  display: flex;
@@ -3,10 +3,13 @@ import './Select.css';
3
3
  * Opção exibida na lista do Select.
4
4
  * @property {string} label - Texto visível da opção.
5
5
  * @property {any} value - Valor associado à opção.
6
+ * @property {string} color - Cor da opção, um token (`var(--umds-semantic-color-...)` ou `var(--umds-primitive-...)`): uma amostra de 12px antes do texto na lista e, com a opção escolhida (seleção única), no gatilho. Decorativa: o texto diz a opção. Sem a prop, nada muda.
6
7
  */
7
8
  export interface SelectOption {
8
9
  label: string;
9
10
  value: string | number;
11
+ /** Cor da opção (token): amostra antes do texto, e no gatilho quando escolhida. */
12
+ color?: string;
10
13
  [key: string]: any;
11
14
  }
12
15
  /**
@@ -40,8 +43,14 @@ export interface SelectOption {
40
43
  * @property {string} ariaLabel - Nome acessível quando não há label visível.
41
44
  * @property {string} id - ID base dos elementos internos. Gerado automaticamente se omitido.
42
45
  * @property {function} onChange - Callback com o(s) valor(es) selecionado(s).
43
- * @property {string} placement - Onde a lista abre: 'bottom' (padrão) | 'top' | 'auto'. O `auto` abre para cima quando falta espaço embaixo da tela e há mais em cima (lista dentro do rodapé de um modal).
44
- * @property {string} variant - 'default' | 'filter'. O `filter` é o botão de filtro de barra de ferramentas: contornado em azul, largura do conteúdo, lista mais larga que o botão, busca focada ao abrir. Combine com `searchable`, `emptyOptionLabel` e o slot `footer`.
46
+ * @property {string} placement - Onde a lista abre: 'bottom' (padrão) | 'top' | 'auto'. O `auto` mede o espaço da janela E do ancestral que rola ou corta (um corpo de modal, um painel com overflow): abre para cima quando falta espaço embaixo e há mais em cima, e limita a altura da lista ao espaço que sobra.
47
+ *
48
+ * Dentro de um diálogo (`role="dialog"` ou `aria-modal`), a lista abre por cima dele: ela passa a `position: fixed`, ancorada no gatilho pelo `@floating-ui/dom`, e não é cortada pelo `overflow` do corpo do modal.
49
+ * @property {string} variant - 'default' | 'filter' | 'search' | 'combo'. O `filter` é o botão de filtro de barra de ferramentas: 32px (`size="sm"` é o padrão das variantes de filtro) e fonte de 12px, fundo branco com contorno azul (azul-claro só depois de escolhido um valor), largura do conteúdo, lista mais larga que o botão, busca focada ao abrir. `search` é o mesmo filtro dentro de uma barra de busca (altura de campo, contorno neutro). `combo` é o filtro colado a outro controle (sem contorno nem raio próprios: quem desenha a borda é o contêiner). Combine com `searchable`, `emptyOptionLabel` e o slot `footer`.
50
+ * @property {boolean} open - Abre/fecha a lista de fora (modo controlado). Sem ela, o Select controla o próprio estado. Use com `onOpenChange`: o filtro com ação no rodapé fecha a lista antes de abrir um modal.
51
+ * @property {function} onOpenChange - Chamado quando a lista pede para abrir ou fechar (clique, teclado, clique fora, Esc, escolha de opção). No modo controlado, é quem decide o novo `open`.
52
+ * @property {IconProp} triggerIcon - Ícone à esquerda do texto do gatilho (objeto Font Awesome importado). Decorativo.
53
+ * @property {string} triggerSwatch - Amostra de cor à esquerda do texto do gatilho: um token (`var(--umds-semantic-color-...)`) ou `var(--umds-primitive-...)`. Decorativa; o texto diz a opção.
45
54
  * @property {string} emptyOptionLabel - Opção "sem filtro" no topo da lista (seleção única): marcada quando nada está escolhido; ao escolher, `onChange` recebe `""` e o campo volta a mostrar o `placeholder` (o nome do filtro).
46
55
  * @property {any} footer - Rodapé fixo da lista, com ações ("Gerenciar marcadores"). Passe o elemento correspondente (React node ou Vue slot `footer`).
47
56
  * @quandoUsar Escolher UM valor de uma lista fechada. O valor selecionado fica visível no campo.
@@ -83,8 +92,20 @@ export interface SelectProps {
83
92
  size?: "sm" | "md" | "lg";
84
93
  /** Onde a lista abre. @default 'bottom' */
85
94
  placement?: "bottom" | "top" | "auto";
86
- /** Variante visual. @default 'default' */
87
- variant?: "default" | "filter";
95
+ /** Variante visual. `filter`, `search` e `combo` são o filtro de barra. @default 'default' */
96
+ variant?: "default" | "filter" | "search" | "combo";
97
+ /**
98
+ * Abre/fecha a lista de fora (modo controlado). `any` de propósito: no Vue uma
99
+ * prop `boolean` sem valor chega como `false`, e `props.open !== undefined`
100
+ * ficaria sempre verdadeiro (o Select se acharia controlado e nunca abriria).
101
+ */
102
+ open?: any;
103
+ /** Pede para abrir ou fechar a lista. No modo controlado, o pai decide o novo `open`. */
104
+ onOpenChange?: (open: boolean) => void;
105
+ /** Ícone à esquerda do texto do gatilho. */
106
+ triggerIcon?: IconProp;
107
+ /** Amostra de cor à esquerda do texto do gatilho (token de cor). */
108
+ triggerSwatch?: string;
88
109
  /** Opção "sem filtro" no topo da lista (seleção única). Escolhê-la chama `onChange("")`. */
89
110
  emptyOptionLabel?: string;
90
111
  /** Rodapé fixo da lista (ações). React node ou slot `footer` no Vue. */
@@ -105,5 +126,6 @@ export interface SelectProps {
105
126
  ariaLabel?: string;
106
127
  className?: string;
107
128
  }
129
+ import { IconProp } from "../Icon/Icon";
108
130
  declare function Select(props: SelectProps): import("react/jsx-runtime").JSX.Element;
109
131
  export default Select;
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import './Select.css';
4
4
  import { useState, useRef, useEffect } from "react";
5
5
  import { faChevronUp, faChevronDown, faMagnifyingGlass, faXmark, faCheck, faTrash, } from "@fortawesome/free-solid-svg-icons";
6
+ import { computePosition, autoUpdate, offset, flip, size, } from "@floating-ui/dom";
6
7
  import Text from "../Text/Text";
7
8
  import Icon from "../Icon/Icon";
8
9
  import Spinner from "../Spinner/Spinner";
@@ -15,6 +16,10 @@ function Select(props) {
15
16
  const searchInputRef = useRef(null);
16
17
  const outsideWatcherRef = useRef(null);
17
18
  const searchTimeoutRef = useRef(null);
19
+ const dropdownRef = useRef(null);
20
+ const floatCleanupRef = useRef(null);
21
+ const latestOpenRef = useRef(false);
22
+ const latestOpenChangeRef = useRef(null);
18
23
  function textoDeApoio() {
19
24
  return props.helperText || props.description || "";
20
25
  }
@@ -25,6 +30,14 @@ function Select(props) {
25
30
  return props.readOnly ? "true" : undefined;
26
31
  }
27
32
  const [isOpen, setIsOpen] = useState(() => false);
33
+ const [prepared, setPrepared] = useState(() => false);
34
+ const [floatMode, setFloatMode] = useState(() => false);
35
+ const [floatReady, setFloatReady] = useState(() => false);
36
+ const [floatX, setFloatX] = useState(() => 0);
37
+ const [floatY, setFloatY] = useState(() => 0);
38
+ const [floatW, setFloatW] = useState(() => 0);
39
+ const [floatMaxH, setFloatMaxH] = useState(() => 0);
40
+ const [listMaxH, setListMaxH] = useState(() => 0);
28
41
  const [searchTerm, setSearchTerm] = useState(() => "");
29
42
  const [internalSelected, setInternalSelected] = useState(() => Array.isArray(props.defaultValue)
30
43
  ? props.defaultValue
@@ -36,6 +49,24 @@ function Select(props) {
36
49
  const [shouldFocusTrigger, setShouldFocusTrigger] = useState(() => false);
37
50
  const [activeIndex, setActiveIndex] = useState(() => -1);
38
51
  const [uid, setUid] = useState(() => "");
52
+ function opened() {
53
+ if (props.open !== undefined)
54
+ return props.open === "" ? true : !!props.open;
55
+ return isOpen;
56
+ }
57
+ function isFilter() {
58
+ return (props.variant === "filter" ||
59
+ props.variant === "search" ||
60
+ props.variant === "combo");
61
+ }
62
+ function setOpen(next) {
63
+ if (next === opened())
64
+ return;
65
+ if (props.open === undefined)
66
+ setIsOpen(next);
67
+ if (props.onOpenChange)
68
+ props.onOpenChange(next);
69
+ }
39
70
  function baseId() {
40
71
  return props.id || uid;
41
72
  }
@@ -48,6 +79,9 @@ function Select(props) {
48
79
  function isInvalid() {
49
80
  return !!props.invalid || !!props.errorMessage;
50
81
  }
82
+ function ariaRequiredText() {
83
+ return props.required ? "true" : undefined;
84
+ }
51
85
  function ariaInvalidText() {
52
86
  return isInvalid() ? "true" : undefined;
53
87
  }
@@ -116,6 +150,14 @@ function Select(props) {
116
150
  }
117
151
  return String(currentVal[0]);
118
152
  }
153
+ function triggerSwatchColor() {
154
+ if (props.triggerSwatch)
155
+ return props.triggerSwatch;
156
+ if (props.multiple || isPlaceholder())
157
+ return undefined;
158
+ const escolhida = selectedOptionsList()[0];
159
+ return escolhida ? escolhida.color : undefined;
160
+ }
119
161
  function isPlaceholder() {
120
162
  const currentVal = props.value !== undefined
121
163
  ? Array.isArray(props.value)
@@ -162,14 +204,19 @@ function Select(props) {
162
204
  function wrapperClassesList() {
163
205
  return [
164
206
  "umds-select",
165
- `umds-select--${props.size || "md"}`,
207
+ `umds-select--${props.size || (isFilter() ? "sm" : "md")}`,
208
+ isFilter() && !isPlaceholder() ? "umds-select--has-value" : "",
166
209
  props.fullWidth ? "umds-select--full-width" : "",
167
210
  props.disabled ? "umds-select--disabled" : "",
168
211
  props.readOnly ? "umds-select--readonly" : "",
169
212
  props.invalid ? "umds-select--invalid" : "",
170
- isOpen ? "umds-select--open" : "",
213
+ opened() ? "umds-select--open" : "",
171
214
  props.searchVariant === "inline" ? "umds-select--search-inline" : "",
172
- props.variant === "filter" ? "umds-select--filter" : "",
215
+ isFilter() ? "umds-select--filter" : "",
216
+ props.variant === "search" ? "umds-select--filter-search" : "",
217
+ props.variant === "combo" ? "umds-select--filter-combo" : "",
218
+ opened() && floatMode ? "umds-select--floating" : "",
219
+ props.triggerIcon ? "umds-select--has-trigger-icon" : "",
173
220
  opensUp && props.searchVariant !== "inline"
174
221
  ? "umds-select--placement-top"
175
222
  : "",
@@ -198,7 +245,7 @@ function Select(props) {
198
245
  return props.label && !baseId() ? props.label : undefined;
199
246
  }
200
247
  function activeDescendantText() {
201
- if (!isOpen || activeIndex < 0 || !baseId())
248
+ if (!opened() || activeIndex < 0 || !baseId())
202
249
  return undefined;
203
250
  return baseId() + "-option-" + activeIndex;
204
251
  }
@@ -208,8 +255,18 @@ function Select(props) {
208
255
  function removeItemAriaLabelText(label) {
209
256
  return (props.removeAriaLabel || "Remover") + " " + label;
210
257
  }
211
- function shouldOpenUp() {
258
+ function insideDialog() {
259
+ if (typeof document === "undefined" ||
260
+ !triggerRef.current ||
261
+ props.searchVariant === "inline")
262
+ return false;
263
+ if (typeof triggerRef.current.closest !== "function")
264
+ return false;
265
+ return !!triggerRef.current.closest('[role="dialog"], [aria-modal="true"]');
266
+ }
267
+ function shouldOpenUp(flutuante) {
212
268
  const pedido = props.placement || "bottom";
269
+ setListMaxH(0);
213
270
  if (pedido === "top")
214
271
  return true;
215
272
  if (pedido !== "auto")
@@ -217,32 +274,114 @@ function Select(props) {
217
274
  if (typeof window === "undefined" || !triggerRef.current)
218
275
  return false;
219
276
  const caixa = triggerRef.current.getBoundingClientRect();
220
- const abaixo = window.innerHeight - caixa.bottom;
221
- const acima = caixa.top;
277
+ let topo = 0;
278
+ let base = window.innerHeight;
279
+ if (!flutuante) {
280
+ let pai = triggerRef.current.parentElement;
281
+ while (pai && pai !== document.body) {
282
+ const corte = window.getComputedStyle(pai).overflowY;
283
+ if (corte === "auto" ||
284
+ corte === "scroll" ||
285
+ corte === "hidden" ||
286
+ corte === "clip") {
287
+ const area = pai.getBoundingClientRect();
288
+ topo = Math.max(topo, area.top);
289
+ base = Math.min(base, area.bottom);
290
+ }
291
+ pai = pai.parentElement;
292
+ }
293
+ }
294
+ const abaixo = base - caixa.bottom;
295
+ const acima = caixa.top - topo;
222
296
  const opcoes = filteredOptionsList().length;
223
297
  const estimada = Math.min(300, opcoes * 36 + 8 + (props.searchable ? 44 : 0) + (props.footer ? 44 : 0));
224
- return abaixo < estimada && acima > abaixo;
298
+ const sobe = abaixo < estimada && acima > abaixo;
299
+ const espaco = (sobe ? acima : abaixo) - 8;
300
+ if (espaco < 300)
301
+ setListMaxH(Math.max(96, Math.floor(espaco)));
302
+ return sobe;
303
+ }
304
+ function prepareOpen() {
305
+ setPrepared(true);
306
+ // Local: no React gerado o setState é assíncrono e o estado leria STALE.
307
+ const flutuante = insideDialog();
308
+ setFloatMode(flutuante);
309
+ setFloatReady(false);
310
+ setOpensUp(shouldOpenUp(flutuante));
311
+ // Abre com a opção já selecionada ativa (padrão combobox).
312
+ setActiveIndex(filteredOptionsList().findIndex((opt) => isOptionSelected(opt.value)));
313
+ if (props.searchable &&
314
+ (props.autoFocusSearch || props.searchVariant === "inline" || isFilter())) {
315
+ setShouldFocusSearch(true);
316
+ }
317
+ }
318
+ function updateFloat() {
319
+ if (!triggerRef.current || !dropdownRef.current)
320
+ return;
321
+ computePosition(triggerRef.current, dropdownRef.current, {
322
+ strategy: "fixed",
323
+ placement: opensUp ? "top-start" : "bottom-start",
324
+ middleware: [
325
+ offset(4),
326
+ flip({
327
+ fallbackStrategy: "bestFit",
328
+ }),
329
+ size({
330
+ apply({ availableHeight, rects }) {
331
+ setFloatW(rects.reference.width);
332
+ setFloatMaxH(Math.max(96, Math.min(300, Math.floor(availableHeight - 8))));
333
+ },
334
+ }),
335
+ ],
336
+ }).then((posicao) => {
337
+ setFloatX(Math.round(posicao.x));
338
+ setFloatY(Math.round(posicao.y));
339
+ setFloatReady(true);
340
+ });
341
+ }
342
+ function dropdownStyle() {
343
+ if (props.searchVariant === "inline")
344
+ return {
345
+ top: "0",
346
+ };
347
+ if (opened() && floatMode) {
348
+ const estilo = {
349
+ position: "fixed",
350
+ top: "0",
351
+ left: "0",
352
+ bottom: "auto",
353
+ transform: "translate(" + floatX + "px, " + floatY + "px)",
354
+ visibility: floatReady ? "visible" : "hidden",
355
+ };
356
+ if (isFilter()) {
357
+ estilo.minWidth =
358
+ "max(" + floatW + "px, calc(var(--umds-primitive-spacing-xxl) * 3))";
359
+ }
360
+ else {
361
+ estilo.width = floatW + "px";
362
+ }
363
+ if (floatMaxH)
364
+ estilo.maxHeight = floatMaxH + "px";
365
+ return estilo;
366
+ }
367
+ return listMaxH
368
+ ? {
369
+ maxHeight: listMaxH + "px",
370
+ }
371
+ : {};
225
372
  }
226
373
  function toggle() {
227
374
  if (props.disabled || props.readOnly)
228
375
  return;
229
- // NUNCA ler isOpen logo após atribuí-lo: no React gerado o
230
- // setState é assíncrono e a leitura vem STALE. Decidir os ramos pela
231
- // variável local (mesmo padrão do toggle do Switch).
232
- const next = !isOpen;
233
- setIsOpen(next);
376
+ // Decide pela variável local: no React gerado o setState é assíncrono e
377
+ // ler o estado logo após atribuí-lo vem STALE.
378
+ const next = !opened();
379
+ setOpen(next);
234
380
  if (next) {
235
- setOpensUp(shouldOpenUp());
236
- // Abre com a opção já selecionada ativa (padrão combobox).
237
- setActiveIndex(filteredOptionsList().findIndex((opt) => isOptionSelected(opt.value)));
238
- if (props.searchable &&
239
- (props.autoFocusSearch ||
240
- props.searchVariant === "inline" ||
241
- props.variant === "filter")) {
242
- setShouldFocusSearch(true);
243
- }
381
+ prepareOpen();
244
382
  }
245
383
  else {
384
+ setPrepared(false);
246
385
  setActiveIndex(-1);
247
386
  }
248
387
  }
@@ -275,7 +414,8 @@ function Select(props) {
275
414
  }
276
415
  }
277
416
  function closeSearch() {
278
- setIsOpen(false);
417
+ setOpen(false);
418
+ setPrepared(false);
279
419
  setSearchTerm("");
280
420
  setActiveIndex(-1);
281
421
  if (props.onSearchChange) {
@@ -298,7 +438,7 @@ function Select(props) {
298
438
  return;
299
439
  const key = event.key;
300
440
  if (key === "Escape") {
301
- if (isOpen) {
441
+ if (opened()) {
302
442
  event.preventDefault();
303
443
  closeSearch();
304
444
  }
@@ -311,7 +451,7 @@ function Select(props) {
311
451
  return;
312
452
  }
313
453
  event.preventDefault();
314
- if (!isOpen) {
454
+ if (!opened()) {
315
455
  toggle();
316
456
  return;
317
457
  }
@@ -377,7 +517,8 @@ function Select(props) {
377
517
  else {
378
518
  newValue = option.value;
379
519
  setInternalSelected([newValue]);
380
- setIsOpen(false);
520
+ setOpen(false);
521
+ setPrepared(false);
381
522
  setActiveIndex(-1);
382
523
  }
383
524
  if (props.onChange) {
@@ -428,12 +569,22 @@ function Select(props) {
428
569
  useEffect(() => {
429
570
  if (!props.id)
430
571
  setUid(gerarId("umds-select"));
572
+ // No Vue o onUpdate só roda depois de uma atualização: a lista que já nasce
573
+ // aberta (open controlado) é preparada aqui.
574
+ latestOpenRef.current = opened();
575
+ latestOpenChangeRef.current = props.onOpenChange;
576
+ if (opened() && !prepared)
577
+ prepareOpen();
431
578
  if (typeof document === "undefined")
432
579
  return;
433
580
  const handleClickOutside = (event) => {
434
581
  if (selectRef.current &&
435
582
  !selectRef.current.contains(event.target)) {
583
+ // Só avisa quando a lista está aberta: o ouvinte vive no document inteiro.
584
+ if (latestOpenRef.current && latestOpenChangeRef.current)
585
+ latestOpenChangeRef.current(false);
436
586
  setIsOpen(false);
587
+ setPrepared(false);
437
588
  setActiveIndex(-1);
438
589
  }
439
590
  };
@@ -441,7 +592,27 @@ function Select(props) {
441
592
  document.addEventListener("mousedown", handleClickOutside);
442
593
  }, []);
443
594
  useEffect(() => {
444
- if (isOpen && shouldFocusSearch) {
595
+ // Guarda o estado visto neste render para o ouvinte de clique fora.
596
+ latestOpenRef.current = opened();
597
+ latestOpenChangeRef.current = props.onOpenChange;
598
+ // Aberta de fora (open controlado): aplica os efeitos de abrir uma vez.
599
+ if (opened() && !prepared)
600
+ prepareOpen();
601
+ if (!opened() && prepared)
602
+ setPrepared(false);
603
+ // Lista flutuante: segue o gatilho (scroll, resize) enquanto aberta.
604
+ if (opened() &&
605
+ floatMode &&
606
+ triggerRef.current &&
607
+ dropdownRef.current &&
608
+ !floatCleanupRef.current) {
609
+ floatCleanupRef.current = autoUpdate(triggerRef.current, dropdownRef.current, () => updateFloat());
610
+ }
611
+ if ((!opened() || !floatMode) && floatCleanupRef.current) {
612
+ floatCleanupRef.current();
613
+ floatCleanupRef.current = null;
614
+ }
615
+ if (opened() && shouldFocusSearch) {
445
616
  // Retry a few times if ref is not yet ready (though onUpdate usually implies rendered)
446
617
  // We use a small timeout to ensure browser paints or simply check ref
447
618
  if (searchInputRef.current) {
@@ -450,7 +621,7 @@ function Select(props) {
450
621
  }
451
622
  }
452
623
  // Devolve o foco ao trigger após fechar por Esc/seleção via teclado.
453
- if (!isOpen && shouldFocusTrigger) {
624
+ if (!opened() && shouldFocusTrigger) {
454
625
  if (triggerRef.current) {
455
626
  triggerRef.current.focus();
456
627
  setShouldFocusTrigger(false);
@@ -459,6 +630,10 @@ function Select(props) {
459
630
  });
460
631
  useEffect(() => {
461
632
  return () => {
633
+ if (floatCleanupRef.current) {
634
+ floatCleanupRef.current();
635
+ floatCleanupRef.current = null;
636
+ }
462
637
  // Cancela o debounce pendente da busca — evita callback após unmount.
463
638
  if (searchTimeoutRef.current) {
464
639
  clearTimeout(searchTimeoutRef.current);
@@ -472,13 +647,11 @@ function Select(props) {
472
647
  }
473
648
  };
474
649
  }, []);
475
- return (_jsxs("div", { ref: selectRef, className: wrapperClassesList(), children: [props.label ? (_jsxs(Text, { as: "label", variant: "label", className: "umds-select__label", id: labelIdText(), children: [props.label, props.required ? (_jsx("span", { className: "umds-select__required", children: " *" })) : null] })) : null, _jsxs("div", { className: "umds-select__container", children: [!(isOpen && props.searchVariant === "inline") ? (_jsxs("div", { role: "combobox", "aria-haspopup": "listbox", className: "umds-select__trigger", ref: triggerRef, onClick: (event) => toggle(), onKeyDown: (event) => handleTriggerKeyDown(event), "aria-readonly": ariaReadonly(), "aria-expanded": isOpen, "aria-controls": listboxIdText(), "aria-activedescendant": activeDescendantText(), "aria-labelledby": comboboxLabelledBy(), "aria-label": comboboxAriaLabel(), "aria-invalid": ariaInvalidText(), "aria-describedby": describedByText(), "aria-disabled": props.disabled ? "true" : undefined, tabIndex: props.disabled ? -1 : 0, children: [_jsxs("div", { className: "umds-select__values", children: [displayLabelText() ? (_jsx(Text, { as: "span", variant: "body2", className: isPlaceholder() ? "umds-select__placeholder" : "", children: displayLabelText() })) : null, props.multiple &&
650
+ return (_jsxs("div", { ref: selectRef, className: wrapperClassesList(), children: [props.label ? (_jsxs(Text, { as: "label", variant: "label", className: "umds-select__label", id: labelIdText(), children: [props.label, props.required ? (_jsx("span", { className: "umds-select__required", children: " *" })) : null] })) : null, _jsxs("div", { className: "umds-select__container", children: [!(opened() && props.searchVariant === "inline") ? (_jsxs("div", { role: "combobox", "aria-haspopup": "listbox", className: "umds-select__trigger", ref: triggerRef, onClick: (event) => toggle(), onKeyDown: (event) => handleTriggerKeyDown(event), "aria-readonly": ariaReadonly(), "aria-expanded": opened(), "aria-controls": listboxIdText(), "aria-activedescendant": activeDescendantText(), "aria-labelledby": comboboxLabelledBy(), "aria-label": comboboxAriaLabel(), "aria-invalid": ariaInvalidText(), "aria-required": ariaRequiredText(), "aria-describedby": describedByText(), "aria-disabled": props.disabled ? "true" : undefined, tabIndex: props.disabled ? -1 : 0, children: [_jsxs("div", { className: "umds-select__values", children: [props.triggerIcon ? (_jsx("span", { "aria-hidden": "true", className: "umds-select__trigger-icon", children: _jsx(Icon, { variant: "inherit", customSize: "12px", icon: props.triggerIcon }) })) : null, triggerSwatchColor() ? (_jsx("span", { "aria-hidden": "true", className: "umds-select__swatch", style: {
651
+ background: triggerSwatchColor(),
652
+ } })) : null, displayLabelText() ? (_jsx(Text, { as: "span", variant: "body2", className: isPlaceholder() ? "umds-select__placeholder" : "", children: displayLabelText() })) : null, props.multiple &&
476
653
  props.selectionDisplay === "tags" &&
477
- selectedOptionsList().length > 0 ? (_jsx("div", { className: "umds-select__tags", children: selectedOptionsList()?.map((opt) => (_jsx(Tag, { size: "sm", label: opt.label, closable: true, disabled: props.disabled, onClose: (e) => removeValue(opt.value, e) }, opt.value))) })) : null] }), _jsxs("div", { className: "umds-select__actions", children: [props.loading ? _jsx(Spinner, { size: "sm", variant: "secondary" }) : null, _jsx("div", { className: "umds-select__arrow", children: _jsx(Icon, { size: "sm", icon: isOpen ? faChevronUp : faChevronDown }) })] })] })) : null, isOpen ? (_jsxs("div", { className: "umds-select__dropdown", style: props.searchVariant === "inline"
478
- ? {
479
- top: "0",
480
- }
481
- : {}, onKeyDown: (event) => handleDropdownKeyDown(event), children: [props.searchable ? (_jsxs("div", { className: "umds-select__search-container", children: [_jsx("div", { className: "umds-select__search-icon", children: _jsx(Icon, { size: "sm", icon: faMagnifyingGlass }) }), _jsx("input", { type: "text", className: "umds-select__search-input", ref: searchInputRef, placeholder: props.searchPlaceholder || "Buscar...", "aria-label": props.searchAriaLabel || "Buscar", value: searchTerm, onInput: (e) => handleSearch(e) }), props.searchVariant === "inline" ? (_jsx("button", { type: "button", className: "umds-select__search-close", "aria-label": props.closeSearchAriaLabel || "Fechar busca", onClick: (event) => closeSearch(), onKeyDown: (event) => handleSearchCloseKeyDown(event), children: _jsx(Icon, { size: "sm", icon: faXmark }) })) : null] })) : null, _jsxs("div", { role: "listbox", className: "umds-select__options", id: listboxIdText(), "aria-multiselectable": props.multiple ? "true" : undefined, onScroll: (e) => onScrollList(e), children: [filteredOptionsList().length === 0 && !props.loading ? (_jsx("div", { className: "umds-select__no-options", children: _jsx(Text, { variant: "body2", color: "secondary", children: props.emptyLabel || "Nenhum item encontrado" }) })) : null, filteredOptionsList()?.map((option, index) => (_jsxs("div", { role: "option", id: optionIdText(index), "aria-selected": isOptionSelected(option.value), onClick: (event) => selectOption(option), className: [
654
+ selectedOptionsList().length > 0 ? (_jsx("div", { className: "umds-select__tags", children: selectedOptionsList()?.map((opt) => (_jsx(Tag, { size: "sm", label: opt.label, closable: true, disabled: props.disabled, onClose: (e) => removeValue(opt.value, e) }, opt.value))) })) : null] }), _jsxs("div", { className: "umds-select__actions", children: [props.loading ? _jsx(Spinner, { size: "sm", variant: "secondary" }) : null, _jsx("div", { className: "umds-select__arrow", children: _jsx(Icon, { size: "sm", icon: opened() ? faChevronUp : faChevronDown }) })] })] })) : null, opened() ? (_jsxs("div", { className: "umds-select__dropdown", ref: dropdownRef, style: dropdownStyle(), onKeyDown: (event) => handleDropdownKeyDown(event), children: [props.searchable ? (_jsxs("div", { className: "umds-select__search-container", children: [_jsx("div", { className: "umds-select__search-icon", children: _jsx(Icon, { size: "sm", icon: faMagnifyingGlass }) }), _jsx("input", { type: "text", className: "umds-select__search-input", ref: searchInputRef, placeholder: props.searchPlaceholder || "Buscar...", "aria-label": props.searchAriaLabel || "Buscar", value: searchTerm, onInput: (e) => handleSearch(e) }), props.searchVariant === "inline" ? (_jsx("button", { type: "button", className: "umds-select__search-close", "aria-label": props.closeSearchAriaLabel || "Fechar busca", onClick: (event) => closeSearch(), onKeyDown: (event) => handleSearchCloseKeyDown(event), children: _jsx(Icon, { size: "sm", icon: faXmark }) })) : null] })) : null, _jsxs("div", { role: "listbox", className: "umds-select__options", id: listboxIdText(), "aria-multiselectable": props.multiple ? "true" : undefined, onScroll: (e) => onScrollList(e), children: [filteredOptionsList().length === 0 && !props.loading ? (_jsx("div", { className: "umds-select__no-options", children: _jsx(Text, { variant: "body2", color: "secondary", children: props.emptyLabel || "Nenhum item encontrado" }) })) : null, filteredOptionsList()?.map((option, index) => (_jsxs("div", { role: "option", id: optionIdText(index), "aria-selected": isOptionSelected(option.value), onClick: (event) => selectOption(option), className: [
482
655
  "umds-select__option",
483
656
  isOptionSelected(option.value)
484
657
  ? "umds-select__option--selected"
@@ -486,7 +659,9 @@ function Select(props) {
486
659
  index === activeIndex ? "umds-select__option--active" : "",
487
660
  ]
488
661
  .filter(Boolean)
489
- .join(" "), children: [props.multiple ? (_jsx("div", { className: "umds-select__option-checkbox", children: _jsx(Checkbox, { checked: isOptionSelected(option.value), decorative: true }) })) : null, _jsx("div", { className: "umds-select__option-content", children: _jsx(_Fragment, { children: props.item || (_jsx(Text, { variant: "body2", children: option.label })) }) }), !props.multiple && isOptionSelected(option.value) ? (_jsx("div", { className: "umds-select__option-check", children: _jsx(Icon, { size: "sm", icon: faCheck }) })) : null] }, option.value))), props.loading && props.pagination ? (_jsxs("div", { className: "umds-select__loading-more", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { variant: "caption", children: props.loadingLabel || "Carregando mais..." })] })) : null] }), props.footer ? (_jsx("div", { className: "umds-select__footer", children: _jsx(_Fragment, { children: props.footer }) })) : null] })) : null] }), props.name ? (_jsx("input", { type: "hidden", name: props.name, value: formValue() || "" })) : null, props.errorMessage ? (_jsx(Text, { variant: "caption", color: "negative", className: "umds-select__error-message", id: errorIdText(), children: props.errorMessage })) : null, mostraApoio() ? (_jsx(Text, { variant: "caption", color: "secondary", className: "umds-select__description", id: helperIdText(), children: textoDeApoio() })) : null, props.multiple &&
662
+ .join(" "), children: [props.multiple ? (_jsx("div", { className: "umds-select__option-checkbox", children: _jsx(Checkbox, { checked: isOptionSelected(option.value), decorative: true }) })) : null, option.color ? (_jsx("span", { "aria-hidden": "true", className: "umds-select__swatch", style: {
663
+ background: option.color,
664
+ } })) : null, _jsx("div", { className: "umds-select__option-content", children: _jsx(_Fragment, { children: props.item || (_jsx(Text, { variant: "body2", children: option.label })) }) }), !props.multiple && isOptionSelected(option.value) ? (_jsx("div", { className: "umds-select__option-check", children: _jsx(Icon, { size: "sm", icon: faCheck }) })) : null] }, option.value))), props.loading && props.pagination ? (_jsxs("div", { className: "umds-select__loading-more", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { variant: "caption", children: props.loadingLabel || "Carregando mais..." })] })) : null] }), props.footer ? (_jsx("div", { className: "umds-select__footer", children: _jsx(_Fragment, { children: props.footer }) })) : null] })) : null] }), props.name ? (_jsx("input", { type: "hidden", name: props.name, value: formValue() || "" })) : null, props.errorMessage ? (_jsx(Text, { variant: "caption", color: "negative", className: "umds-select__error-message", id: errorIdText(), children: props.errorMessage })) : null, mostraApoio() ? (_jsx(Text, { variant: "caption", color: "secondary", className: "umds-select__description", id: helperIdText(), children: textoDeApoio() })) : null, props.multiple &&
490
665
  props.selectionDisplay === "list" &&
491
666
  selectedOptionsList().length > 0 ? (_jsx("div", { className: "umds-select__selected-list", children: selectedOptionsList()?.map((opt) => (_jsxs("div", { className: "umds-select__selected-list-item", children: [_jsx(Text, { variant: "body2", children: opt.label }), _jsx("button", { type: "button", className: "umds-select__list-remove", "aria-label": removeItemAriaLabelText(opt.label), onClick: (e) => removeValue(opt.value, e), children: _jsx(Icon, { size: "sm", icon: faTrash }) })] }, opt.value))) })) : null] }));
492
667
  }
@@ -225,3 +225,68 @@
225
225
  .umds-stat-card--rotulo-topo:not(:has(.umds-stat-card__icone)) .umds-stat-card__valor {
226
226
  grid-column: 1 / -1;
227
227
  }
228
+
229
+ /* ── valueTone: cor do número principal (variantes escuras: contraste AA no branco) ── */
230
+ .umds-stat-card--valor-primary .umds-stat-card__valor {
231
+ color: var(--umds-semantic-color-primary-pure);
232
+ }
233
+
234
+ .umds-stat-card--valor-positive .umds-stat-card__valor {
235
+ color: var(--umds-semantic-color-feedback-positive-dark);
236
+ }
237
+
238
+ .umds-stat-card--valor-negative .umds-stat-card__valor {
239
+ color: var(--umds-semantic-color-feedback-negative-dark);
240
+ }
241
+
242
+ .umds-stat-card--valor-info .umds-stat-card__valor {
243
+ color: var(--umds-semantic-color-feedback-info-dark);
244
+ }
245
+
246
+ /* ── Segunda medida (secondaryValue, secondaryLabel) ──
247
+ Menor, à direita da principal, separada por um filete. Vem depois do corpo no HTML. */
248
+ .umds-stat-card__secundaria {
249
+ display: flex;
250
+ flex-direction: column;
251
+ gap: var(--umds-primitive-spacing-micro);
252
+ margin-left: auto;
253
+ padding-left: var(--umds-primitive-spacing-xxxs);
254
+ border-left: var(--umds-primitive-border-width-hairline) solid var(--umds-semantic-color-neutral-light-medium);
255
+ text-align: right;
256
+ }
257
+
258
+ .umds-stat-card--centro .umds-stat-card__secundaria {
259
+ margin-left: 0;
260
+ padding-left: 0;
261
+ border-left: none;
262
+ align-items: center;
263
+ text-align: center;
264
+ }
265
+
266
+ .umds-stat-card__valor-secundario {
267
+ font-size: var(--umds-primitive-font-size-sm); /* 20px */
268
+ font-weight: var(--umds-primitive-font-weight-bold);
269
+ line-height: 1.15;
270
+ font-variant-numeric: tabular-nums;
271
+ color: var(--umds-semantic-color-neutral-dark-down);
272
+ }
273
+
274
+ .umds-stat-card__rotulo-secundario {
275
+ font-size: var(--umds-primitive-font-size-xxxs);
276
+ color: var(--umds-semantic-color-neutral-dark-down);
277
+ line-height: 1.4;
278
+ }
279
+
280
+ /* Com o rótulo no topo a grade ganha uma terceira coluna para a segunda medida. */
281
+ .umds-stat-card--rotulo-topo.umds-stat-card--com-secundaria {
282
+ grid-template-columns: auto 1fr auto;
283
+ }
284
+
285
+ .umds-stat-card--rotulo-topo .umds-stat-card__secundaria {
286
+ grid-column: 3;
287
+ grid-row: 2;
288
+ }
289
+
290
+ .umds-stat-card--rotulo-topo.umds-stat-card--com-secundaria:not(:has(.umds-stat-card__icone)) .umds-stat-card__valor {
291
+ grid-column: 1 / 3;
292
+ }