@1doc/1ds-react 2.10.0 → 2.11.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 (82) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/dist/components/Accordion/Accordion.d.ts +6 -0
  3. package/dist/components/Accordion/Accordion.js +17 -5
  4. package/dist/components/Badge/Badge.css +13 -4
  5. package/dist/components/Badge/Badge.d.ts +9 -2
  6. package/dist/components/Badge/Badge.js +3 -0
  7. package/dist/components/Button/Button.css +5 -0
  8. package/dist/components/Button/Button.d.ts +3 -0
  9. package/dist/components/Button/Button.js +1 -0
  10. package/dist/components/Calendar/Calendar.css +10 -0
  11. package/dist/components/Calendar/Calendar.d.ts +3 -0
  12. package/dist/components/Calendar/Calendar.js +6 -1
  13. package/dist/components/Coachmark/Coachmark.d.ts +2 -1
  14. package/dist/components/Coachmark/Coachmark.js +6 -1
  15. package/dist/components/DataTable/DataTable.css +42 -0
  16. package/dist/components/DataTable/DataTable.d.ts +9 -2
  17. package/dist/components/DataTable/DataTable.js +15 -0
  18. package/dist/components/Input/Input.css +9 -0
  19. package/dist/components/Input/Input.d.ts +3 -0
  20. package/dist/components/Input/Input.js +1 -0
  21. package/dist/components/Modal/Modal.css +19 -0
  22. package/dist/components/Modal/Modal.d.ts +7 -0
  23. package/dist/components/Modal/Modal.js +1 -0
  24. package/dist/components/SegmentedControl/SegmentedControl.css +46 -0
  25. package/dist/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  26. package/dist/components/SegmentedControl/SegmentedControl.js +1 -0
  27. package/dist/components/Select/Select.css +28 -2
  28. package/dist/components/Select/Select.d.ts +7 -0
  29. package/dist/components/Select/Select.js +55 -9
  30. package/dist/components/StatCard/StatCard.css +8 -0
  31. package/dist/components/StatCard/StatCard.d.ts +3 -0
  32. package/dist/components/StatCard/StatCard.js +1 -0
  33. package/dist/components/Tabs/Tabs.css +11 -0
  34. package/dist/components/Tabs/Tabs.d.ts +7 -0
  35. package/dist/components/Tabs/Tabs.js +7 -1
  36. package/dist/components/Tag/Tag.css +3 -1
  37. package/dist/teste/AccordionControlado.test.d.ts +1 -0
  38. package/dist/teste/AccordionControlado.test.js +73 -0
  39. package/dist/teste/BadgeAttachedTomEPadding.test.d.ts +1 -0
  40. package/dist/teste/BadgeAttachedTomEPadding.test.js +43 -0
  41. package/dist/teste/ButtonDensidade.test.d.ts +1 -0
  42. package/dist/teste/ButtonDensidade.test.js +28 -0
  43. package/dist/teste/CalendarCompacto.test.d.ts +1 -0
  44. package/dist/teste/CalendarCompacto.test.js +29 -0
  45. package/dist/teste/CoachmarkUmPasso.test.d.ts +1 -0
  46. package/dist/teste/CoachmarkUmPasso.test.js +34 -0
  47. package/dist/teste/DataTableCompactaETom.test.d.ts +1 -0
  48. package/dist/teste/DataTableCompactaETom.test.js +69 -0
  49. package/dist/teste/InputGhost.test.d.ts +1 -0
  50. package/dist/teste/InputGhost.test.js +32 -0
  51. package/dist/teste/ModalProporcoes1Doc.test.d.ts +1 -0
  52. package/dist/teste/ModalProporcoes1Doc.test.js +32 -0
  53. package/dist/teste/SegmentedControlSuperficie.test.d.ts +1 -0
  54. package/dist/teste/SegmentedControlSuperficie.test.js +39 -0
  55. package/dist/teste/SelectItemComEscopo.test.d.ts +1 -0
  56. package/dist/teste/SelectItemComEscopo.test.js +54 -0
  57. package/dist/teste/SelectListaFiltro.test.d.ts +1 -0
  58. package/dist/teste/SelectListaFiltro.test.js +102 -0
  59. package/dist/teste/StatCardTamanhoSm.test.d.ts +1 -0
  60. package/dist/teste/StatCardTamanhoSm.test.js +32 -0
  61. package/dist/teste/TabsCompacta.test.d.ts +1 -0
  62. package/dist/teste/TabsCompacta.test.js +35 -0
  63. package/dist/teste/TagPositivoTingido.test.d.ts +1 -0
  64. package/dist/teste/TagPositivoTingido.test.js +54 -0
  65. package/dist/teste/lerArquivo.d.ts +1 -0
  66. package/dist/teste/lerArquivo.js +12 -0
  67. package/package.json +3 -3
  68. package/src/teste/AccordionControlado.test.tsx +87 -0
  69. package/src/teste/BadgeAttachedTomEPadding.test.tsx +60 -0
  70. package/src/teste/ButtonDensidade.test.tsx +40 -0
  71. package/src/teste/CalendarCompacto.test.tsx +37 -0
  72. package/src/teste/CoachmarkUmPasso.test.tsx +45 -0
  73. package/src/teste/DataTableCompactaETom.test.tsx +85 -0
  74. package/src/teste/InputGhost.test.tsx +42 -0
  75. package/src/teste/ModalProporcoes1Doc.test.tsx +36 -0
  76. package/src/teste/SegmentedControlSuperficie.test.tsx +52 -0
  77. package/src/teste/SelectItemComEscopo.test.tsx +69 -0
  78. package/src/teste/SelectListaFiltro.test.tsx +120 -0
  79. package/src/teste/StatCardTamanhoSm.test.tsx +42 -0
  80. package/src/teste/TabsCompacta.test.tsx +45 -0
  81. package/src/teste/TagPositivoTingido.test.tsx +60 -0
  82. package/src/teste/lerArquivo.ts +13 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,224 @@
1
1
  # @1doc/1ds-react
2
2
 
3
+ ## 2.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bf2cfdc: feat(accordion): `open` e `onOpenChange`, abertura controlada
8
+
9
+ `open` (lista de ids dos itens abertos) e `onOpenChange` (recebe a nova lista a cada abrir ou fechar). Com `open`, quem manda é o
10
+ app: o clique só pede e o painel muda quando `open` muda, para lembrar a escolha entre visitas (as seções do CrisDoc). Sem `open`, o
11
+ Accordion guarda o próprio estado, como antes (começando pelos `defaultOpen`), e `onOpenChange` também avisa. `open=[]` é controlado
12
+ (tudo fechado), não ausência. Item desabilitado não pede nada.
13
+
14
+ ```vue
15
+ <Accordion
16
+ :items="secoes"
17
+ multiple
18
+ :open="abertos"
19
+ :onOpenChange="(ids) => (abertos = ids)"
20
+ />
21
+ ```
22
+
23
+ - 9c8baa3: feat(badge): `2xs` com 8px de padding lateral; `attached` com 22px e o tom escuro como opção (`attachedTone`)
24
+
25
+ - `size="2xs"`: o padding lateral passa de 4px a 8px, como o selo do CrisDoc. A altura segue em 16px e a fonte em 10px.
26
+ - `attached`: o rótulo colado tem altura mínima de 22px (24 - 2) de qualquer tamanho; o `md` (padrão) já media 22 e
27
+ o `xs` e o `2xs` colados sobem a ele.
28
+ - `attachedTone="dark"` (novo; padrão `light`): o neutro sólido colado em cinza escuro (`neutral-dark-down`) com texto
29
+ branco, o rótulo escuro do CrisDoc. O ardósia do CrisDoc dá 2,9:1 com texto branco, então o DS usa o token que passa
30
+ (7,3:1 e inverte sozinho nos temas escuros). Só vale com `attached`, `variant="neutral"` e `appearance="solid"`.
31
+
32
+ ```vue
33
+ <Badge
34
+ attached
35
+ size="xs"
36
+ variant="neutral"
37
+ appearance="solid"
38
+ attachedTone="dark"
39
+ >Tarefa manual</Badge>
40
+ ```
41
+
42
+ - 7883c6b: feat(button): `density="comfortable"`, o `sm` com 12px de padding lateral
43
+
44
+ O botão pequeno do CrisDoc tem 12px de padding lateral e o `sm` do DS tem 8px. Mudar o `sm` alargaria todo botão que já o
45
+ usa, então a diferença virou uma densidade: `density="comfortable"` leva o `sm` a 12px (a altura de 32px, a fonte e o raio
46
+ não mudam). Sem a prop, nenhum botão muda. Nos outros tamanhos a prop não faz diferença (`xs` já usa 12px, `md` 16px e
47
+ `lg` 24px), e o botão só de ícone, de largura fixa, fica de fora.
48
+
49
+ ```vue
50
+ <Button
51
+ variant="secondary"
52
+ size="sm"
53
+ density="comfortable"
54
+ >Personalizar</Button>
55
+ ```
56
+
57
+ - c101901: feat(calendar): `density="compact"`, evento de 20px
58
+
59
+ `density` (`default` | `compact`). O `compact` leva o evento a 20px de altura (16 + 4, o degrau do DS mais perto dos 20,5 do CrisDoc;
60
+ o padrão dá 25) com 4px de padding lateral (o padrão tem 8) e linha de 16px. Com menos padding o título cabe inteiro onde antes
61
+ cortava; ele só é cortado, com reticências, quando passa da largura da célula. O padrão não muda.
62
+
63
+ ```vue
64
+ <Calendar :events="eventos" density="compact" />
65
+ ```
66
+
67
+ - c9c05cc: feat(coachmark): com um passo só, sem o contador "1 de 1"
68
+
69
+ O contador ("2 de 8") só aparece com mais de um passo, no cartão e na região `aria-live`: com um passo o leitor de
70
+ tela ouve só o título. É o caso da dica pontual (`modal={false}`) e de qualquer tour de um passo. `counterLabel`
71
+ continua valendo para os tours com mais passos. O botão do último passo segue sendo "Concluir" (`finishLabel`).
72
+
73
+ - ad47648: feat(datatable): `density="compact"` com cabeçalho e linha de 44px, e tom por linha (`rowTone`)
74
+
75
+ - `density="compact"`: `th` e `td` ganham `height` igual à altura de campo do DS (44px), no lugar dos 38 e 49 de
76
+ antes. É mínimo: conteúdo mais alto (avatar, botão) estica a linha. A tabela de documento (`variant="document"`)
77
+ continua justa.
78
+ - `rowTone`: o nome do campo da linha com o tom, ou uma função `(row, rowIndex) => tom`. Tons: `negative`,
79
+ `positive`, `info` (fundo claro da cor semântica) e `muted` (texto esmaecido). A linha selecionada ou destacada
80
+ vence o tom e o tom vence a zebra. Tom desconhecido é ignorado. O tom é decoração: o estado também vai em texto.
81
+
82
+ ```vue
83
+ <DataTable
84
+ density="compact"
85
+ :columns="colunas"
86
+ :data="usuarios"
87
+ :rowTone="
88
+ (u) => (u.bloqueado ? 'negative' : u.desligado ? 'muted' : undefined)
89
+ "
90
+ />
91
+ ```
92
+
93
+ - a1de816: feat(input): `variant="ghost"`, a busca dentro de um painel
94
+
95
+ `variant` (`default` | `ghost`). O `ghost` tira a borda escura do repouso: fundo cinza claro (`background-light`) e borda clara
96
+ (`neutral-light-medium`). Só o repouso muda: hover (borda mais escura), foco (borda azul e anel), erro, desabilitado e somente leitura
97
+ são os do campo padrão, e o ícone, o placeholder e o cursor seguem dizendo que é um campo. Sem a prop, nada muda.
98
+
99
+ ```vue
100
+ <Input
101
+ variant="ghost"
102
+ size="sm"
103
+ fullWidth
104
+ ariaLabel="Buscar filtros"
105
+ placeholder="Buscar filtros"
106
+ />
107
+ ```
108
+
109
+ - 9e41dc1: feat(modal): `density="1doc"`, as proporções do modal do CrisDoc
110
+
111
+ `density` (`default` | `1doc`) leva o cabeçalho a 67px (o padrão dá 61), o rodapé a 65px (o padrão dá 73, com o padding de 12px 24px)
112
+ e o raio a 12px (o padrão é 8px). As alturas são mínimas: título com subtítulo ou botão mais alto continua crescendo. Combina com
113
+ `variant="primary"`. A tela cheia e o painel (`responsive`) mantêm os cantos deles. Sem a prop, o modal não muda. Sem token para
114
+ essas medidas: literais comentados no CSS.
115
+
116
+ ```vue
117
+ <Modal
118
+ :open="aberto"
119
+ density="1doc"
120
+ variant="primary"
121
+ title="Resolver tarefa"
122
+ width="600px"
123
+ />
124
+ ```
125
+
126
+ - 7d9e687: feat(segmentedcontrol): `variant="surface"`, o escolhido em superfície branca
127
+
128
+ `variant` (`default` | `surface`). No `surface` o trilho é cinza claro, sem divisores, e o segmento escolhido é uma peça branca com
129
+ sombra, texto escuro e negrito (o seletor do CrisDoc). A seleção não depende só do tom: o peso e a sombra a dizem. O contador do
130
+ escolhido fica cinza sobre o branco e o dos outros, branco sobre o trilho. O padrão (azul cheio) não muda.
131
+
132
+ ```vue
133
+ <SegmentedControl
134
+ label="Modo"
135
+ variant="surface"
136
+ :options="opcoes"
137
+ :value="modo"
138
+ :onChange="trocar"
139
+ />
140
+ ```
141
+
142
+ - bf81dfa: feat(select): a lista do filtro tem a largura do gatilho, com `listMinWidth` e sem passar da janela
143
+
144
+ Regressão do 2.10 no `Select variant="filter"` (a lista do Marcadores da inbox media 556px e passava 17px da borda):
145
+
146
+ - Causa: o wrapper do filtro tinha `width: auto` e é `display: flex` (bloco), então se esticava até a largura do pai;
147
+ o `100%` da lista passava a ser a do pai e não a do gatilho. Agora é `fit-content`.
148
+ - A lista tem a largura do gatilho (nunca menor), cresce até o conteúdo e fica dentro da janela: o campo de busca
149
+ não entra mais na conta da largura (`contain: inline-size`) e acompanha a lista.
150
+ - Perto da borda direita a lista se alinha ao fim do gatilho (medido ao abrir). Na lista flutuante dentro de
151
+ diálogo, o floating-ui ganhou `shift` com 8px de respiro.
152
+ - `listMinWidth` (número em px ou medida CSS) troca o mínimo de 240px: o CrisDoc usa 220.
153
+
154
+ ```vue
155
+ <Select
156
+ variant="filter"
157
+ ariaLabel="Marcadores"
158
+ placeholder="Marcadores"
159
+ searchable
160
+ :listMinWidth="220"
161
+ :options="marcadores"
162
+ />
163
+ ```
164
+
165
+ - cfe3687: feat(select): `item` com o escopo da opção, na lista e no gatilho
166
+
167
+ O conteúdo de cada opção agora recebe a opção nos dois frameworks:
168
+
169
+ - Vue: o slot `#item="{ optionData }"` (o Mitosis gerava `<slot name="item">` sem escopo e o `optionData` chegava `undefined`; o
170
+ `postbuild.js` passa `optionData` e o apelido `option`).
171
+ - React: `item` pode ser uma função `({ optionData }) => nó` (antes, a função virava filho inválido). O nó solto, sem escopo,
172
+ continua valendo na lista.
173
+ - Com a opção escolhida (seleção única), o gatilho mostra o conteúdo do `item` no lugar do texto: o "SeletorComEtiqueta" do
174
+ CrisDoc, com nome e etiqueta, aparece igual na lista e no campo. Sem valor (placeholder), na seleção múltipla e com o nó solto do
175
+ React, o gatilho segue com o texto.
176
+
177
+ ```vue
178
+ <Select label="Prioridade" :options="opcoes" :value="valor" :onChange="mudar">
179
+ <template #item="{ optionData }"><strong>{{ optionData.label }}</strong> {{ optionData.etiqueta }}</template>
180
+ </Select>
181
+ ```
182
+
183
+ - 53dfebe: feat(statcard): `size="sm"`, o KPI com o número a 18px/800
184
+
185
+ Os KPIs da home do CrisDoc têm o número a 18px/800. `size="sm"` leva o número a esse tamanho (o `md`, padrão, segue
186
+ em 24/700). Vale também com `labelPosition="top"`, que tem o número em 32/800: o `sm` vence. O 18px não está na escala
187
+ de fonte do DS (que vai de 16 a 20) e fica como literal comentado, como o 10px do `Badge` 2xs. O número continua antes
188
+ do rótulo no HTML.
189
+
190
+ ```vue
191
+ <StatCard
192
+ size="sm"
193
+ label="Minhas tarefas"
194
+ :value="12"
195
+ :icon="faClock"
196
+ variant="primary"
197
+ />
198
+ ```
199
+
200
+ - 4471b90: feat(tabs): `density="compact"`, abas de 40px
201
+
202
+ O `Tabs` ganha `density` (`default` | `compact`). O `compact` leva a altura mínima das abas de 48px a 40px (o degrau `sm` do DS) com
203
+ padding vertical de 8px, para barras de abas dentro de uma tela (o CrisDoc usa 40). A pílula do contador (`count`) não muda e já
204
+ era o que o CrisDoc pede: cinza (`neutral-light-medium`) na aba inativa e azul só na ativa, nas duas densidades. Sem a prop, nenhuma
205
+ aba muda.
206
+
207
+ ```vue
208
+ <Tabs :value="aba" :onChange="trocar" density="compact" ariaLabel="Processos">
209
+ <Tab label="Fluxos" index="1" :count="56" />
210
+ </Tabs>
211
+ ```
212
+
213
+ ### Patch Changes
214
+
215
+ - 7ccb05c: fix(tag): `variant="positive"` outlined com o texto em `positive-dark`
216
+
217
+ O contorno da tag outlined é transparente, então o texto cai sobre o fundo de quem está atrás, que muitas vezes é
218
+ tingido (o azul-claro de um card). `positive-pure` sobre `primary-light` dá 3,6:1 e reprova o WCAG 1.4.3;
219
+ `positive-dark` dá 7,9:1 e é o mesmo par de texto da tag `soft`. Nos temas escuro e de alto contraste o `dark` também
220
+ passa de 4,5:1. O contorno continua em `positive-pure`. As outras variantes não mudam.
221
+
3
222
  ## 2.10.0
4
223
 
5
224
  ### Minor Changes
@@ -14,6 +14,8 @@ export interface AccordionItemData {
14
14
  /**
15
15
  * Accordion component — lista de painéis expansíveis (sanfona).
16
16
  * @property {AccordionItemData[]} items - Itens do accordion (id, title, content, disabled?, defaultOpen?). Conteúdo livre por item: no React, `content` aceita nó; no Vue, o slot `#content-<id>` (recebe `{ item }`).
17
+ * @property {string[]} open - Ids dos itens abertos (modo controlado). Sem a prop, o Accordion guarda o próprio estado (começando pelos `defaultOpen`). Com ela, quem manda é o app: abrir ou fechar chama `onOpenChange` e o painel só muda quando `open` muda. Serve para lembrar a escolha entre visitas.
18
+ * @property {function} onOpenChange - Chamado ao abrir ou fechar um item, com a nova lista de ids abertos (`(openIds: string[]) => void`). Funciona também sem `open`.
17
19
  * @property {boolean} multiple - Permite manter vários painéis abertos ao mesmo tempo
18
20
  * @property {string} variant - Estilo visual: 'default' | 'bordered' | 'separated'
19
21
  * @property {string} titleVariant - Título dos itens: 'default' | 'neutral'. O `neutral` é o título de texto, 14px na cor do texto, sem cor de ação (como um `Button text` neutro), para o accordion do CrisDoc.
@@ -24,6 +26,10 @@ export interface AccordionItemData {
24
26
  export interface AccordionProps {
25
27
  items: AccordionItemData[];
26
28
  multiple?: boolean;
29
+ /** Ids dos itens abertos (controlado). Sem a prop, o estado é interno. */
30
+ open?: string[];
31
+ /** Abriu ou fechou um item: recebe a nova lista de ids abertos. */
32
+ onOpenChange?: (openIds: string[]) => void;
27
33
  variant?: "default" | "bordered" | "separated";
28
34
  /** Título dos itens. `neutral`: 14px na cor do texto, peso médio. @default 'default' */
29
35
  titleVariant?: "default" | "neutral";
@@ -9,8 +9,13 @@ function Accordion(props) {
9
9
  function itemList() {
10
10
  return props.items || [];
11
11
  }
12
+ function currentOpen() {
13
+ return props.open !== undefined && props.open !== null
14
+ ? props.open
15
+ : openIds;
16
+ }
12
17
  function isOpen(id) {
13
- return openIds.includes(id);
18
+ return currentOpen().includes(id);
14
19
  }
15
20
  function triggerId(id) {
16
21
  return `umds-accordion-trigger-${id}`;
@@ -21,15 +26,22 @@ function Accordion(props) {
21
26
  function toggle(id, disabled) {
22
27
  if (disabled)
23
28
  return;
24
- if (openIds.includes(id)) {
25
- setOpenIds(openIds.filter((x) => x !== id));
29
+ const atuais = currentOpen();
30
+ let proximos;
31
+ if (atuais.includes(id)) {
32
+ proximos = atuais.filter((x) => x !== id);
26
33
  }
27
34
  else if (props.multiple) {
28
- setOpenIds([...openIds, id]);
35
+ proximos = [...atuais, id];
29
36
  }
30
37
  else {
31
- setOpenIds([id]);
38
+ proximos = [id];
32
39
  }
40
+ // Controlado: quem muda é o app (o `open` novo). Sem `open`, o estado interno.
41
+ if (props.open === undefined || props.open === null)
42
+ setOpenIds(proximos);
43
+ if (props.onOpenChange)
44
+ props.onOpenChange(proximos);
33
45
  }
34
46
  function wrapperClasses() {
35
47
  return [
@@ -60,12 +60,12 @@
60
60
 
61
61
  /* ── Tamanhos — padding + gap por tamanho (sem height fixa) ── */
62
62
 
63
- /* 2xs: 16px de altura (10 de fonte + 2 + 2 de padding + 2 de borda). A fonte de
64
- 10px não está na escala do DS (menor degrau: 12px); é o selo compacto do
65
- CrisDoc, sem token equivalente. */
63
+ /* 2xs: 16px de altura (10 de fonte + 2 + 2 de padding + 2 de borda) e 8px de padding
64
+ lateral, como o selo do CrisDoc. A fonte de 10px não está na escala do DS (menor
65
+ degrau: 12px), sem token equivalente. */
66
66
  .umds-badge--2xs {
67
67
  font-size: 0.625rem; /* 10px: fora da escala, selo compacto */
68
- padding: var(--umds-primitive-spacing-micro) var(--umds-primitive-spacing-quarck);
68
+ padding: var(--umds-primitive-spacing-micro) var(--umds-primitive-spacing-nano);
69
69
  gap: var(--umds-primitive-spacing-micro);
70
70
  border-radius: var(--umds-primitive-border-radius-xs); /* 4px */
71
71
  }
@@ -252,6 +252,8 @@
252
252
  position: absolute;
253
253
  top: 0;
254
254
  left: 0;
255
+ /* 22px: o rótulo do CrisDoc (24 - 2). Os tamanhos menores (xs, 2xs) sobem a ele; o md já mede 22. */
256
+ min-height: calc(var(--umds-primitive-spacing-xxs) - var(--umds-primitive-spacing-micro));
255
257
  border-radius: 0 0 var(--umds-primitive-border-radius-sm) var(--umds-primitive-border-radius-sm);
256
258
  }
257
259
 
@@ -267,3 +269,10 @@
267
269
  background-color: var(--umds-semantic-color-neutral-light-medium);
268
270
  color: var(--umds-semantic-color-neutral-dark-pure);
269
271
  }
272
+
273
+ /* attachedTone="dark": o neutro escuro do CrisDoc. O ardósia do CrisDoc dá 2,9:1 com texto branco; o par
274
+ neutral-dark-down + branco dá 7,3:1 e inverte sozinho nos temas escuros. */
275
+ .umds-badge--attached.umds-badge--neutral.umds-badge--solid.umds-badge--attached-dark {
276
+ background-color: var(--umds-semantic-color-neutral-dark-down);
277
+ color: var(--umds-semantic-color-neutral-light-pure);
278
+ }
@@ -13,8 +13,8 @@ export interface BadgeProps {
13
13
  */
14
14
  variant?: "neutral" | "positive" | "negative" | "info" | "primary" | "new";
15
15
  /**
16
- * Tamanho do badge. `2xs` é o compacto (16px, fonte de 10px) para selo de
17
- * setor e de contagem dentro de linha de lista e card.
16
+ * Tamanho do badge. `2xs` é o compacto (16px, fonte de 10px, 8px de padding
17
+ * lateral) para selo de setor e de contagem dentro de linha de lista e card.
18
18
  * @default 'md'
19
19
  */
20
20
  size?: "2xs" | "xs" | "sm" | "md" | "lg" | "pill";
@@ -35,6 +35,13 @@ export interface BadgeProps {
35
35
  * @default 'left'
36
36
  */
37
37
  attachedSide?: "left" | "right";
38
+ /**
39
+ * Tom do rótulo colado neutro e sólido: `light` (padrão, cinza claro com texto escuro) ou `dark` (cinza
40
+ * escuro com texto branco, o rótulo escuro do CrisDoc). Só vale com `attached`, `variant="neutral"` e
41
+ * `appearance="solid"`; as outras variantes ignoram. O rótulo colado mede 22px de altura.
42
+ * @default 'light'
43
+ */
44
+ attachedTone?: "light" | "dark";
38
45
  /**
39
46
  * Estilo visual do badge.
40
47
  * @default 'soft'
@@ -11,6 +11,9 @@ function Badge(props) {
11
11
  props.attached && props.attachedSide === "right"
12
12
  ? "umds-badge--attached-right"
13
13
  : "",
14
+ props.attached && props.attachedTone === "dark"
15
+ ? "umds-badge--attached-dark"
16
+ : "",
14
17
  props.className,
15
18
  ]
16
19
  .filter(Boolean)
@@ -56,6 +56,11 @@
56
56
  font-size: var(--umds-primitive-font-size-xxxs); /* 12px */
57
57
  }
58
58
 
59
+ /* density="comfortable": o sm com o padding de 12px do CrisDoc. O botão só de ícone tem largura fixa e fica de fora. */
60
+ .umds-button--sm.umds-button--comfortable:not(.umds-button--icon) {
61
+ padding: 0 var(--umds-primitive-spacing-mini); /* 12px */
62
+ }
63
+
59
64
  .umds-button--md {
60
65
  height: var(--umds-primitive-spacing-sm); /* 40px */
61
66
  padding: 0 var(--umds-primitive-spacing-xxxs); /* 16px */
@@ -3,6 +3,7 @@ import './Button.css';
3
3
  * Button — botão de ação com variantes, tamanhos, ícones e estado de carregamento.
4
4
  * @property {string} variant - Variante visual: 'primary' | 'secondary' | 'neutral' | 'neutral-text' | 'text' | 'negative' | 'negative-outlined' | 'icon'. `neutral` é o contornado cinza; `neutral-text` é o texto sem borda e sem azul, para barras de ações onde nenhum botão é a ação principal.
5
5
  * @property {string} size - Tamanho do botão: 'xs' (26px, para dentro de card e linha de lista) | 'sm' (32px) | 'md' (40px) | 'lg' (48px)
6
+ * @property {string} density - 'default' (padrão) | 'comfortable'. O `comfortable` leva o padding lateral do `sm` de 8px a 12px (o botão pequeno do CrisDoc); a altura (32px) não muda. Nos outros tamanhos não faz diferença: `xs` já usa 12px, `md` 16px e `lg` 24px. Sem a prop, nenhum botão existente muda.
6
7
  * @property {boolean} disabled - Desabilita o botão
7
8
  * @property {string} type - Tipo nativo do botão: 'button' | 'submit' | 'reset'
8
9
  * @property {Function} onClick - Callback disparado no clique
@@ -27,6 +28,8 @@ import './Button.css';
27
28
  export interface ButtonProps {
28
29
  variant?: "primary" | "secondary" | "neutral" | "neutral-text" | "text" | "negative" | "negative-outlined" | "icon";
29
30
  size?: "xs" | "sm" | "md" | "lg";
31
+ /** `comfortable`: o `sm` com 12px de padding lateral (o padrão é 8px). @default 'default' */
32
+ density?: "default" | "comfortable";
30
33
  disabled?: boolean;
31
34
  type?: "button" | "submit" | "reset";
32
35
  children?: any;
@@ -9,6 +9,7 @@ function Button(props) {
9
9
  "umds-button",
10
10
  `umds-button--${props.variant || "primary"}`,
11
11
  `umds-button--${props.size || "md"}`,
12
+ props.density === "comfortable" ? "umds-button--comfortable" : "",
12
13
  props.fullWidth ? "umds-button--full-width" : "",
13
14
  props.className,
14
15
  ]
@@ -171,6 +171,16 @@
171
171
  cursor: pointer;
172
172
  }
173
173
 
174
+ /* density="compact": evento de 20px (16 + 4: o degrau do DS mais perto dos 20,5 do CrisDoc) e 4px de padding
175
+ lateral. A linha tem 16px de altura e 2px de padding em cima e embaixo. O título continua cortado com
176
+ reticências só quando passa da largura (a regra do título, abaixo). */
177
+ .umds-calendar--compact .umds-calendar__event {
178
+ min-height: calc(var(--umds-primitive-spacing-xxxs) + var(--umds-primitive-spacing-quarck)); /* 20px */
179
+ padding: var(--umds-primitive-spacing-micro) var(--umds-primitive-spacing-quarck); /* 2px 4px */
180
+ line-height: var(--umds-primitive-spacing-xxxs); /* 16px */
181
+ align-items: center;
182
+ }
183
+
174
184
  .umds-calendar__event:hover {
175
185
  filter: brightness(0.96);
176
186
  }
@@ -65,6 +65,7 @@ export interface CalendarDayCell {
65
65
  * @property {string} today - O "hoje" do calendário (`YYYY-MM-DD`; padrão a data do sistema). Útil em testes e em demonstração.
66
66
  * @property {number} weekStartsOn - Primeiro dia da semana: 0 domingo (padrão) | 1 segunda
67
67
  * @property {number} maxVisible - Eventos por dia na visão de mês antes do "+N" (padrão 3)
68
+ * @property {string} density - 'default' | 'compact'. O `compact` aperta o evento: altura de 20px (o degrau do DS mais perto dos 20,5 do CrisDoc; o padrão dá 25) e 4px de padding lateral (o padrão tem 8), para o título caber inteiro onde antes cortava com reticências. O título só é cortado (com reticências) quando de fato passa da largura da célula.
68
69
  * @property {string} locale - Idioma dos nomes de mês e de dia (padrão 'pt-BR')
69
70
  * @property {string} ariaLabel - Nome acessível da grade (padrão: o título, ex.: "Maio 2026")
70
71
  * @property {string} previousLabel - Nome do botão de voltar (padrão "Anterior")
@@ -99,6 +100,8 @@ export interface CalendarProps {
99
100
  today?: string;
100
101
  weekStartsOn?: 0 | 1;
101
102
  maxVisible?: number;
103
+ /** `compact`: evento de 20px com 4px de padding lateral. @default 'default' */
104
+ density?: "default" | "compact";
102
105
  locale?: string;
103
106
  ariaLabel?: string;
104
107
  previousLabel?: string;
@@ -100,7 +100,12 @@ function Calendar(props) {
100
100
  ];
101
101
  }
102
102
  function rootClasses() {
103
- return ["umds-calendar", "umds-calendar--" + viewValue(), props.className]
103
+ return [
104
+ "umds-calendar",
105
+ "umds-calendar--" + viewValue(),
106
+ props.density === "compact" ? "umds-calendar--compact" : "",
107
+ props.className,
108
+ ]
104
109
  .filter(Boolean)
105
110
  .join(" ");
106
111
  }
@@ -23,7 +23,8 @@ export interface CoachmarkStep {
23
23
  * O cartão é um diálogo modal (`role="dialog"`, `aria-modal`): o foco entra nele
24
24
  * ao abrir, o Tab fica preso nele, Esc sai do tour, as setas esquerda e direita
25
25
  * voltam e avançam, e o foco volta ao elemento de antes ao fechar. A cada passo o
26
- * leitor de tela ouve "2 de 8: título" (região `aria-live`).
26
+ * leitor de tela ouve "2 de 8: título" (região `aria-live`). Com um passo só não há contador ("1 de 1" não
27
+ * diz nada): o cartão mostra o título e o leitor de tela ouve só ele.
27
28
  *
28
29
  * O componente é CONTROLADO por `open`: o tour só existe com `open` verdadeiro, e
29
30
  * `onClose(motivo)` avisa quando ele quer fechar (`"finish"` no último
@@ -62,7 +62,12 @@ function Coachmark(props) {
62
62
  .replace("{current}", String(index() + 1))
63
63
  .replace("{total}", String(stepCount()));
64
64
  }
65
+ function hasCounter() {
66
+ return stepCount() > 1;
67
+ }
65
68
  function announceText() {
69
+ if (!hasCounter())
70
+ return currentStep().title;
66
71
  return counterText() + ": " + currentStep().title;
67
72
  }
68
73
  function nextText() {
@@ -357,6 +362,6 @@ function Coachmark(props) {
357
362
  desligarEsc();
358
363
  };
359
364
  }, []);
360
- return (_jsx(_Fragment, { children: show() ? (_jsx(_Fragment, { children: _jsx(Portal, { children: _jsxs("div", { className: rootClasses(), children: [isModal() ? (_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__veil", onMouseDown: (event) => handleVeilMouseDown(event) })) : null, hasTarget && isModal() ? (_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__cutout", style: cutoutStyle() })) : null, _jsxs("div", { role: "dialog", ref: cardRef, style: cardStyle(), "aria-modal": ariaModalText(), "aria-labelledby": titleId(), "aria-describedby": contentId(), tabIndex: -1, onKeyDown: (event) => handleKeyDown(event), className: cardClasses(), children: [_jsx("span", { "aria-live": "polite", className: "umds-coachmark__sr", children: announceText() }), _jsx(Text, { as: "span", variant: "body2", weight: "bold", color: "primary", "aria-hidden": "true", children: counterText() }), _jsx(Text, { as: "h2", variant: "subtitle1", weight: "bold", id: titleId(), children: currentStep().title }), currentStep().content ? (_jsx(Text, { as: "p", variant: "body2", color: "secondary", id: contentId(), children: currentStep().content })) : null, _jsxs("div", { className: "umds-coachmark__footer", children: [showSkip() ? (_jsx(Button, { variant: "text", size: "sm", onClick: (event) => fechar("skip"), children: skipText() })) : null, _jsxs("span", { className: "umds-coachmark__actions", children: [!isFirst() ? (_jsx(Button, { variant: "secondary", size: "md", onClick: (event) => handlePrevious(), children: previousText() })) : null, _jsx(Button, { variant: "primary", size: "md", onClick: (event) => handleNext(), children: nextText() })] })] })] })] }) }) })) : null }));
365
+ return (_jsx(_Fragment, { children: show() ? (_jsx(_Fragment, { children: _jsx(Portal, { children: _jsxs("div", { className: rootClasses(), children: [isModal() ? (_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__veil", onMouseDown: (event) => handleVeilMouseDown(event) })) : null, hasTarget && isModal() ? (_jsx("div", { "aria-hidden": "true", className: "umds-coachmark__cutout", style: cutoutStyle() })) : null, _jsxs("div", { role: "dialog", ref: cardRef, style: cardStyle(), "aria-modal": ariaModalText(), "aria-labelledby": titleId(), "aria-describedby": contentId(), tabIndex: -1, onKeyDown: (event) => handleKeyDown(event), className: cardClasses(), children: [_jsx("span", { "aria-live": "polite", className: "umds-coachmark__sr", children: announceText() }), hasCounter() ? (_jsx(Text, { as: "span", variant: "body2", weight: "bold", color: "primary", "aria-hidden": "true", children: counterText() })) : null, _jsx(Text, { as: "h2", variant: "subtitle1", weight: "bold", id: titleId(), children: currentStep().title }), currentStep().content ? (_jsx(Text, { as: "p", variant: "body2", color: "secondary", id: contentId(), children: currentStep().content })) : null, _jsxs("div", { className: "umds-coachmark__footer", children: [showSkip() ? (_jsx(Button, { variant: "text", size: "sm", onClick: (event) => fechar("skip"), children: skipText() })) : null, _jsxs("span", { className: "umds-coachmark__actions", children: [!isFirst() ? (_jsx(Button, { variant: "secondary", size: "md", onClick: (event) => handlePrevious(), children: previousText() })) : null, _jsx(Button, { variant: "primary", size: "md", onClick: (event) => handleNext(), children: nextText() })] })] })] })] }) }) })) : null }));
361
366
  }
362
367
  export default Coachmark;
@@ -207,6 +207,14 @@
207
207
  padding: var(--umds-primitive-spacing-nano) var(--umds-primitive-spacing-nano); /* 6px: candidato a novo token; 8px */
208
208
  }
209
209
 
210
+ /* Cabeçalho e linha de 44px (a altura de campo do DS). `height` na célula vale como mínimo: conteúdo mais
211
+ alto (avatar, botão) estica a linha. O documento imprime justo e fica fora. */
212
+ .umds-datatable--compact:not(.umds-datatable--document) .umds-datatable__th,
213
+ .umds-datatable--compact:not(.umds-datatable--document) .umds-datatable__td {
214
+ box-sizing: border-box;
215
+ height: var(--umds-semantic-field-height);
216
+ }
217
+
210
218
  .umds-datatable--compact .umds-datatable__th--checkbox,
211
219
  .umds-datatable--compact .umds-datatable__td--checkbox {
212
220
  padding: var(--umds-primitive-spacing-nano) var(--umds-primitive-spacing-quarck) var(--umds-primitive-spacing-nano) var(--umds-primitive-spacing-nano); /* 6px: candidato a novo token; 4px; 8px */
@@ -273,6 +281,40 @@
273
281
  background: var(--umds-semantic-color-primary-light);
274
282
  }
275
283
 
284
+ /* ── Tom por linha (rowTone) ──
285
+ Fundo claro da cor semântica; a linha selecionada ou destacada vence o tom (por isso o :not). Fica depois da
286
+ zebra para vencê-la. `muted` só esmaece o texto. */
287
+ .umds-datatable__row--negative:not(.umds-datatable__row--selected),
288
+ .umds-datatable__row--negative:not(.umds-datatable__row--selected).umds-datatable__row--striped {
289
+ background: var(--umds-semantic-color-feedback-negative-light);
290
+ }
291
+
292
+ .umds-datatable__row--positive:not(.umds-datatable__row--selected),
293
+ .umds-datatable__row--positive:not(.umds-datatable__row--selected).umds-datatable__row--striped {
294
+ background: var(--umds-semantic-color-feedback-positive-light);
295
+ }
296
+
297
+ .umds-datatable__row--info:not(.umds-datatable__row--selected),
298
+ .umds-datatable__row--info:not(.umds-datatable__row--selected).umds-datatable__row--striped {
299
+ background: var(--umds-semantic-color-feedback-info-light);
300
+ }
301
+
302
+ .umds-datatable__row--negative:not(.umds-datatable__row--selected):hover {
303
+ background: color-mix(in srgb, var(--umds-semantic-color-feedback-negative-light) 85%, var(--umds-semantic-color-background-white));
304
+ }
305
+
306
+ .umds-datatable__row--positive:not(.umds-datatable__row--selected):hover {
307
+ background: color-mix(in srgb, var(--umds-semantic-color-feedback-positive-light) 85%, var(--umds-semantic-color-background-white));
308
+ }
309
+
310
+ .umds-datatable__row--info:not(.umds-datatable__row--selected):hover {
311
+ background: color-mix(in srgb, var(--umds-semantic-color-feedback-info-light) 85%, var(--umds-semantic-color-background-white));
312
+ }
313
+
314
+ .umds-datatable__row--muted .umds-datatable__td {
315
+ color: var(--umds-semantic-color-neutral-dark-down); /* #565656: 7,3:1 sobre branco */
316
+ }
317
+
276
318
  /* ── Linha destacada (selectedRowKeys / isRowSelected) ──
277
319
  Além do fundo, uma barra de 4px à esquerda: o destaque não depende só da cor. */
278
320
  .umds-datatable__row--highlighted > :first-child {
@@ -35,8 +35,13 @@ export interface DataTableColumn {
35
35
  * @property {Array<string|number>} selectedKeys - Chaves das linhas selecionadas (controlado;
36
36
  * requer rowKey)
37
37
  * @property {string} variant - 'default' (tabela de interface) | 'document' (tabela de CONTEÚDO de documento: sem ordenação, seleção nem clique de linha, bordas finas, cabeçalho repetido e linha inteira por página na impressão)
38
- * @property {string} density - Densidade das células: 'default' | 'compact'. Com `variant="document"`,
39
- * o compacto também leva a fonte a 12px (a escala do DS não tem 13px).
38
+ * @property {string} density - Densidade das células: 'default' | 'compact'. O compacto aperta o padding e
39
+ * fixa a altura do cabeçalho e da linha em 44px (a altura de campo do DS; o conteúdo maior que isso cresce a
40
+ * linha). Com `variant="document"`, o compacto também leva a fonte a 12px (a escala do DS não tem 13px).
41
+ * @property {string|function} rowTone - Tom por linha: nome do campo da linha que traz o tom, ou função
42
+ * `(row, rowIndex) => tom`. Tons: 'negative' (vermelho claro), 'positive' (verde claro), 'info' (laranja claro)
43
+ * e 'muted' (texto esmaecido). Sem tom, a linha segue o padrão; a linha selecionada ou destacada vence o tom.
44
+ * O tom é decoração: diga o estado também em texto ou ícone (a cor sozinha não basta).
40
45
  * @property {string} headerVariant - Cabeçalho: 'default' (claro) | 'dark' (navy, texto claro; o cabeçalho
41
46
  * escuro do CrisDoc). Cores pelos tokens `background-dark` e `neutral-light-pure`, então o tema acompanha.
42
47
  * @property {boolean} headerUppercase - Rótulos do cabeçalho em caixa alta. Opt-in; vale para qualquer
@@ -98,6 +103,8 @@ export interface DataTableProps {
98
103
  /** `document`: tabela de conteúdo de documento, imprimível e sem interação. @default 'default' */
99
104
  variant?: "default" | "document";
100
105
  density?: "default" | "compact";
106
+ /** Tom por linha: nome do campo ou função `(row, rowIndex) => 'negative' | 'positive' | 'info' | 'muted'`. */
107
+ rowTone?: string | ((row: Record<string, any>, rowIndex: number) => string | undefined);
101
108
  /** `dark`: cabeçalho navy com texto claro (o do CrisDoc). @default 'default' */
102
109
  headerVariant?: "default" | "dark";
103
110
  /** Rótulos do cabeçalho em caixa alta. @default false */
@@ -511,9 +511,24 @@ function DataTable(props) {
511
511
  .filter(Boolean)
512
512
  .join(" ");
513
513
  }
514
+ function toneOf(row, index) {
515
+ if (!props.rowTone)
516
+ return "";
517
+ const bruto = typeof props.rowTone === "function"
518
+ ? props.rowTone(row, index)
519
+ : row[props.rowTone];
520
+ const tom = bruto === undefined || bruto === null ? "" : String(bruto);
521
+ return tom === "negative" ||
522
+ tom === "positive" ||
523
+ tom === "info" ||
524
+ tom === "muted"
525
+ ? tom
526
+ : "";
527
+ }
514
528
  function rowClasses(row, rowIdx) {
515
529
  return [
516
530
  "umds-datatable__row",
531
+ toneOf(row, rowIdx) ? "umds-datatable__row--" + toneOf(row, rowIdx) : "",
517
532
  isSelected(row, rowIdx) || isHighlighted(row, rowIdx)
518
533
  ? "umds-datatable__row--selected"
519
534
  : "",
@@ -257,6 +257,15 @@
257
257
  outline-offset: 0; /* exceção ao offset 2px: o botão vive DENTRO do campo, e o afastamento invadiria a borda */
258
258
  }
259
259
 
260
+ /* ── variant="ghost": a busca dentro de um painel, sem a borda escura ──
261
+ Só o repouso muda (fundo claro e borda clara). Hover, foco, erro, desabilitado e somente leitura vêm das
262
+ regras do campo padrão, que têm mais especificidade (o :where zera a das cadeias de :not aqui), então o
263
+ hover escurece a borda e o foco segue com borda azul e anel. A moldura clara não é o único sinal do campo: o ícone, o placeholder e o cursor o dizem. */
264
+ .umds-input--ghost:where(:not(.umds-input--error):not(.umds-input--disabled):not(.umds-input--readonly)) .umds-input__container {
265
+ background-color: var(--umds-semantic-color-background-light);
266
+ border-color: var(--umds-semantic-color-neutral-light-medium);
267
+ }
268
+
260
269
  /* ── borderless: o campo dentro de um grupo que já tem a moldura ──
261
270
  Sem borda, fundo e raio. O foco NÃO some: o anel do campo vira um contorno
262
271
  para dentro (WCAG 2.4.7), e o erro mantém a borda vermelha (a cor do erro não
@@ -36,6 +36,7 @@ export interface InputAction {
36
36
  * @property {InputAction[]} iconsRight - Botões de ação com ícone à direita
37
37
  * @property {IconProp} iconRight - Ícone decorativo à direita (não é botão, some para o leitor de tela). Para ação clicável, use `iconsRight`.
38
38
  * @property {boolean} grouped - O campo vive dentro de um contêiner que já desenha a moldura E o foco (o campo de chips: as tags e o input na mesma caixa). Implica `borderless`, e tira a altura de campo, o padding e o anel de foco próprio: o contêiner mostra o foco com `:focus-within`. Sem esse foco no contêiner, quem usa teclado perde o indicador (WCAG 2.4.7).
39
+ * @property {string} variant - 'default' | 'ghost'. O `ghost` é o campo de busca dentro de um painel: fundo cinza claro e borda clara (a escura do padrão some em repouso). O hover escurece a borda, e o foco, o erro, o desabilitado e o somente leitura são os do campo padrão (o anel de foco continua inteiro). Sem a prop, nada muda.
39
40
  * @property {boolean} borderless - Sem borda, fundo e raio: o campo dentro de um grupo que já tem a moldura (um Select e um Input lado a lado numa mesma caixa). O foco continua visível, em contorno, e o erro mantém a borda vermelha.
40
41
  * @property {string} mask - Máscara simples: 'cpf' | 'cnpj' | 'cep' | 'phone'. O valor exibido e o que chega em `onChange` já vêm formatados (para só os dígitos, `valor.replace(/\D/g, "")`). Aceita só dígitos, limita o tamanho e abre o teclado numérico no celular.
41
42
  * @property {function} onChange - Callback acionado A CADA TECLA (recebe o novo valor string), nos dois frameworks. O blur sai em `onBlur`.
@@ -93,6 +94,8 @@ export interface InputProps {
93
94
  iconsRight?: InputAction[];
94
95
  /** Ícone decorativo à direita (não clicável, `aria-hidden`). Para ação, use `iconsRight`. */
95
96
  iconRight?: IconProp;
97
+ /** `ghost`: o campo de busca dentro de um painel, sem a borda escura em repouso. @default 'default' */
98
+ variant?: "default" | "ghost";
96
99
  /** Sem borda, fundo e raio, para o campo dentro de um grupo que já tem a moldura. O foco e o erro continuam visíveis. @default false */
97
100
  borderless?: boolean;
98
101
  /**
@@ -52,6 +52,7 @@ function Input(props) {
52
52
  props.numberSpinner ? "umds-input--number-spinner" : "",
53
53
  props.borderless || props.grouped ? "umds-input--borderless" : "",
54
54
  props.grouped ? "umds-input--grouped" : "",
55
+ props.variant === "ghost" ? "umds-input--ghost" : "",
55
56
  `umds-input--${props.size || "md"}`,
56
57
  props.className,
57
58
  ]