@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.
- package/CHANGELOG.md +219 -0
- package/dist/components/Accordion/Accordion.d.ts +6 -0
- package/dist/components/Accordion/Accordion.js +17 -5
- package/dist/components/Badge/Badge.css +13 -4
- package/dist/components/Badge/Badge.d.ts +9 -2
- package/dist/components/Badge/Badge.js +3 -0
- package/dist/components/Button/Button.css +5 -0
- package/dist/components/Button/Button.d.ts +3 -0
- package/dist/components/Button/Button.js +1 -0
- package/dist/components/Calendar/Calendar.css +10 -0
- package/dist/components/Calendar/Calendar.d.ts +3 -0
- package/dist/components/Calendar/Calendar.js +6 -1
- package/dist/components/Coachmark/Coachmark.d.ts +2 -1
- package/dist/components/Coachmark/Coachmark.js +6 -1
- package/dist/components/DataTable/DataTable.css +42 -0
- package/dist/components/DataTable/DataTable.d.ts +9 -2
- package/dist/components/DataTable/DataTable.js +15 -0
- package/dist/components/Input/Input.css +9 -0
- package/dist/components/Input/Input.d.ts +3 -0
- package/dist/components/Input/Input.js +1 -0
- package/dist/components/Modal/Modal.css +19 -0
- package/dist/components/Modal/Modal.d.ts +7 -0
- package/dist/components/Modal/Modal.js +1 -0
- package/dist/components/SegmentedControl/SegmentedControl.css +46 -0
- package/dist/components/SegmentedControl/SegmentedControl.d.ts +3 -0
- package/dist/components/SegmentedControl/SegmentedControl.js +1 -0
- package/dist/components/Select/Select.css +28 -2
- package/dist/components/Select/Select.d.ts +7 -0
- package/dist/components/Select/Select.js +55 -9
- package/dist/components/StatCard/StatCard.css +8 -0
- package/dist/components/StatCard/StatCard.d.ts +3 -0
- package/dist/components/StatCard/StatCard.js +1 -0
- package/dist/components/Tabs/Tabs.css +11 -0
- package/dist/components/Tabs/Tabs.d.ts +7 -0
- package/dist/components/Tabs/Tabs.js +7 -1
- package/dist/components/Tag/Tag.css +3 -1
- package/dist/teste/AccordionControlado.test.d.ts +1 -0
- package/dist/teste/AccordionControlado.test.js +73 -0
- package/dist/teste/BadgeAttachedTomEPadding.test.d.ts +1 -0
- package/dist/teste/BadgeAttachedTomEPadding.test.js +43 -0
- package/dist/teste/ButtonDensidade.test.d.ts +1 -0
- package/dist/teste/ButtonDensidade.test.js +28 -0
- package/dist/teste/CalendarCompacto.test.d.ts +1 -0
- package/dist/teste/CalendarCompacto.test.js +29 -0
- package/dist/teste/CoachmarkUmPasso.test.d.ts +1 -0
- package/dist/teste/CoachmarkUmPasso.test.js +34 -0
- package/dist/teste/DataTableCompactaETom.test.d.ts +1 -0
- package/dist/teste/DataTableCompactaETom.test.js +69 -0
- package/dist/teste/InputGhost.test.d.ts +1 -0
- package/dist/teste/InputGhost.test.js +32 -0
- package/dist/teste/ModalProporcoes1Doc.test.d.ts +1 -0
- package/dist/teste/ModalProporcoes1Doc.test.js +32 -0
- package/dist/teste/SegmentedControlSuperficie.test.d.ts +1 -0
- package/dist/teste/SegmentedControlSuperficie.test.js +39 -0
- package/dist/teste/SelectItemComEscopo.test.d.ts +1 -0
- package/dist/teste/SelectItemComEscopo.test.js +54 -0
- package/dist/teste/SelectListaFiltro.test.d.ts +1 -0
- package/dist/teste/SelectListaFiltro.test.js +102 -0
- package/dist/teste/StatCardTamanhoSm.test.d.ts +1 -0
- package/dist/teste/StatCardTamanhoSm.test.js +32 -0
- package/dist/teste/TabsCompacta.test.d.ts +1 -0
- package/dist/teste/TabsCompacta.test.js +35 -0
- package/dist/teste/TagPositivoTingido.test.d.ts +1 -0
- package/dist/teste/TagPositivoTingido.test.js +54 -0
- package/dist/teste/lerArquivo.d.ts +1 -0
- package/dist/teste/lerArquivo.js +12 -0
- package/package.json +3 -3
- package/src/teste/AccordionControlado.test.tsx +87 -0
- package/src/teste/BadgeAttachedTomEPadding.test.tsx +60 -0
- package/src/teste/ButtonDensidade.test.tsx +40 -0
- package/src/teste/CalendarCompacto.test.tsx +37 -0
- package/src/teste/CoachmarkUmPasso.test.tsx +45 -0
- package/src/teste/DataTableCompactaETom.test.tsx +85 -0
- package/src/teste/InputGhost.test.tsx +42 -0
- package/src/teste/ModalProporcoes1Doc.test.tsx +36 -0
- package/src/teste/SegmentedControlSuperficie.test.tsx +52 -0
- package/src/teste/SelectItemComEscopo.test.tsx +69 -0
- package/src/teste/SelectListaFiltro.test.tsx +120 -0
- package/src/teste/StatCardTamanhoSm.test.tsx +42 -0
- package/src/teste/TabsCompacta.test.tsx +45 -0
- package/src/teste/TagPositivoTingido.test.tsx +60 -0
- package/src/teste/lerArquivo.ts +13 -0
|
@@ -407,6 +407,25 @@
|
|
|
407
407
|
background: var(--umds-semantic-color-primary-medium);
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
/* ── density="1doc": as proporções do modal do CrisDoc ──
|
|
411
|
+
Cabeçalho de 67px (o padrão dá 61), rodapé de 65px (o padrão dá 73) e raio de 12px (o DS usa 8). Sem token
|
|
412
|
+
para essas medidas: literais comentados, como o resto do modal base. As alturas são mínimas, então título
|
|
413
|
+
com subtítulo ou botão mais alto continua crescendo. Fora a tela cheia e o painel, que têm cantos próprios. */
|
|
414
|
+
.umds-modal--1doc:not(.umds-modal--fullscreen):not(.umds-modal--janela-fullscreen):not(.umds-modal--sheet) {
|
|
415
|
+
/* stylelint-disable-next-line 1ds/raio-canonico -- o 12px é a opção density="1doc" (o modal do CrisDoc); o padrão segue em 8px */
|
|
416
|
+
border-radius: 12px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.umds-modal--1doc.umds-modal--base .umds-modal__header {
|
|
420
|
+
align-items: center;
|
|
421
|
+
min-height: 67px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.umds-modal--1doc.umds-modal--base .umds-modal__footer {
|
|
425
|
+
min-height: 65px;
|
|
426
|
+
padding: var(--umds-primitive-spacing-mini) var(--umds-primitive-spacing-xxs); /* 12px 24px */
|
|
427
|
+
}
|
|
428
|
+
|
|
410
429
|
/* ── responsive: abaixo do breakpoint vira painel que sobe pela borda de baixo ──
|
|
411
430
|
Mesma anatomia do BottomSheet (largura total, cantos de cima arredondados,
|
|
412
431
|
alça, entrada por baixo). Quem decide o breakpoint é o componente (media
|
|
@@ -95,6 +95,13 @@ export interface ModalProps {
|
|
|
95
95
|
* @default 'dialog'
|
|
96
96
|
*/
|
|
97
97
|
layout?: "dialog" | "content";
|
|
98
|
+
/**
|
|
99
|
+
* Proporções do modal. `1doc` leva o cabeçalho a 67px, o rodapé a 65px e o raio a 12px, as medidas do
|
|
100
|
+
* modal do CrisDoc; o padrão (`default`) segue com 61px, 73px e 8px. Só o modal base (com título), e
|
|
101
|
+
* combina com `variant="primary"`. Tela cheia e painel (`responsive`) mantêm os cantos deles.
|
|
102
|
+
* @default 'default'
|
|
103
|
+
*/
|
|
104
|
+
density?: "default" | "1doc";
|
|
98
105
|
/** Classes CSS adicionais para o container do modal */
|
|
99
106
|
className?: string;
|
|
100
107
|
/** Se verdadeiro, renderiza o modal inline (sem fixed/overlay), útil para documentação */
|
|
@@ -149,6 +149,7 @@ function Modal(props) {
|
|
|
149
149
|
`umds-modal--${props.variant || "default"}`,
|
|
150
150
|
props.size && props.size !== "md" ? `umds-modal--${props.size}` : "",
|
|
151
151
|
ehFeedback() ? "umds-modal--feedback" : "umds-modal--base",
|
|
152
|
+
props.density === "1doc" ? "umds-modal--1doc" : "",
|
|
152
153
|
classeDaJanela(),
|
|
153
154
|
props.responsive && isCompact ? "umds-modal--sheet" : "",
|
|
154
155
|
props.className,
|
|
@@ -208,3 +208,49 @@
|
|
|
208
208
|
background-color: var(--umds-semantic-color-neutral-light-pure);
|
|
209
209
|
color: var(--umds-semantic-color-primary-pure);
|
|
210
210
|
}
|
|
211
|
+
|
|
212
|
+
/* ── variant="surface": o escolhido em superfície branca (o seletor do CrisDoc) ──
|
|
213
|
+
Trilho cinza claro sem divisores; o segmento escolhido é uma peça branca com sombra e texto escuro em
|
|
214
|
+
negrito (a seleção não depende só do tom: o peso e a sombra a dizem). O trilho deixa de recortar, para a
|
|
215
|
+
sombra aparecer. O contador do escolhido fica cinza sobre o branco e o dos outros, branco sobre o trilho. */
|
|
216
|
+
.umds-segmented--surface .umds-segmented__track {
|
|
217
|
+
background-color: var(--umds-semantic-color-neutral-light-medium);
|
|
218
|
+
border-color: var(--umds-semantic-color-neutral-light-medium);
|
|
219
|
+
overflow: visible;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.umds-segmented--surface .umds-segmented__item {
|
|
223
|
+
background-color: transparent;
|
|
224
|
+
border-right-color: transparent;
|
|
225
|
+
border-radius: var(--umds-primitive-border-radius-sm);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.umds-segmented--surface .umds-segmented__item:hover {
|
|
229
|
+
background-color: var(--umds-semantic-color-neutral-light-down);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item,
|
|
233
|
+
.umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item:hover {
|
|
234
|
+
background-color: var(--umds-semantic-color-background-white);
|
|
235
|
+
color: var(--umds-semantic-color-neutral-dark-pure);
|
|
236
|
+
font-weight: var(--umds-primitive-font-weight-bold);
|
|
237
|
+
box-shadow: var(--umds-semantic-shadow-md);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.umds-segmented--surface .umds-segmented__input:disabled + .umds-segmented__item:hover {
|
|
241
|
+
background-color: transparent;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.umds-segmented--surface .umds-segmented__input:disabled:checked + .umds-segmented__item:hover {
|
|
245
|
+
background-color: var(--umds-semantic-color-background-white);
|
|
246
|
+
color: var(--umds-semantic-color-neutral-dark-pure);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.umds-segmented--surface .umds-segmented__count {
|
|
250
|
+
background-color: var(--umds-semantic-color-background-white);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item .umds-segmented__count {
|
|
254
|
+
background-color: var(--umds-semantic-color-neutral-light-medium);
|
|
255
|
+
color: var(--umds-semantic-color-neutral-dark-pure);
|
|
256
|
+
}
|
|
@@ -40,6 +40,7 @@ export interface SegmentedControlOption {
|
|
|
40
40
|
* @property {boolean} invalid - Estado de erro.
|
|
41
41
|
* @property {string} errorMessage - Mensagem de erro do grupo.
|
|
42
42
|
* @property {string} size - Tamanho: 'sm' (32px) | 'md' (40px).
|
|
43
|
+
* @property {string} variant - 'default' (padrão): o segmento escolhido é azul cheio com texto branco. 'surface': trilho cinza claro e o segmento escolhido em superfície branca, com sombra e texto em negrito (o seletor do CrisDoc). O contador do escolhido fica cinza sobre o branco e o dos outros, branco sobre o trilho.
|
|
43
44
|
* @property {boolean} fullWidth - Os segmentos dividem a largura do container.
|
|
44
45
|
* @property {string} className - Classes CSS adicionais.
|
|
45
46
|
* @property {function} onChange - Callback com o valor escolhido.
|
|
@@ -58,6 +59,8 @@ export interface SegmentedControlProps {
|
|
|
58
59
|
invalid?: boolean;
|
|
59
60
|
errorMessage?: string;
|
|
60
61
|
size?: "sm" | "md";
|
|
62
|
+
/** `surface`: o escolhido em superfície branca sobre trilho cinza, no lugar do azul cheio. @default 'default' */
|
|
63
|
+
variant?: "default" | "surface";
|
|
61
64
|
fullWidth?: boolean;
|
|
62
65
|
className?: string;
|
|
63
66
|
onChange?: (value: string) => void;
|
|
@@ -49,6 +49,7 @@ function SegmentedControl(props) {
|
|
|
49
49
|
return [
|
|
50
50
|
"umds-segmented",
|
|
51
51
|
`umds-segmented--${props.size || "md"}`,
|
|
52
|
+
props.variant === "surface" ? "umds-segmented--surface" : "",
|
|
52
53
|
props.fullWidth ? "umds-segmented--full-width" : "",
|
|
53
54
|
props.disabled ? "umds-segmented--disabled" : "",
|
|
54
55
|
isInvalid() ? "umds-segmented--invalid" : "",
|
|
@@ -175,8 +175,10 @@
|
|
|
175
175
|
|
|
176
176
|
/* ── Variante filter: botão de filtro de barra de ferramentas ──
|
|
177
177
|
Contornado em azul, largura do conteúdo, lista mais larga que o botão. */
|
|
178
|
+
/* fit-content e não auto: o wrapper é flex (bloco), e `auto` o estica até a largura do pai. Com ele esticado,
|
|
179
|
+
o `100%` da lista passava a ser o do pai (556px numa barra) e não o do gatilho. */
|
|
178
180
|
.umds-select--filter {
|
|
179
|
-
width:
|
|
181
|
+
width: fit-content;
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
.umds-select--filter.umds-select--full-width {
|
|
@@ -224,12 +226,25 @@
|
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
.umds-select--filter .umds-select__dropdown {
|
|
227
|
-
/* 240px: três vezes o degrau xxl (80px). Sem token de largura de menu.
|
|
229
|
+
/* 240px: três vezes o degrau xxl (80px). Sem token de largura de menu. `listMinWidth` troca este mínimo
|
|
230
|
+
(estilo inline). A lista nunca é mais estreita que o gatilho (100%). */
|
|
228
231
|
width: max-content;
|
|
229
232
|
min-width: max(100%, calc(var(--umds-primitive-spacing-xxl) * 3));
|
|
230
233
|
max-width: calc(100vw - var(--umds-primitive-spacing-xxxs) * 2);
|
|
231
234
|
}
|
|
232
235
|
|
|
236
|
+
/* Perto da borda direita da janela: a lista se alinha ao fim do gatilho (`alignEnd`, medido ao abrir). */
|
|
237
|
+
.umds-select--filter.umds-select--list-end .umds-select__dropdown {
|
|
238
|
+
left: auto;
|
|
239
|
+
right: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* A busca da lista de filtro acompanha a largura da lista e não a dita: `contain: inline-size` tira a
|
|
243
|
+
largura natural do campo da conta do `max-content`. */
|
|
244
|
+
.umds-select--filter .umds-select__search-container {
|
|
245
|
+
contain: inline-size;
|
|
246
|
+
}
|
|
247
|
+
|
|
233
248
|
/* ── Variante search: o filtro dentro de uma barra de busca ──
|
|
234
249
|
Altura de campo e contorno neutro, para ficar ao lado do campo de busca. */
|
|
235
250
|
.umds-select--filter-search .umds-select__trigger {
|
|
@@ -333,6 +348,7 @@
|
|
|
333
348
|
|
|
334
349
|
.umds-select__search-input {
|
|
335
350
|
flex: 1;
|
|
351
|
+
min-width: 0;
|
|
336
352
|
border: none;
|
|
337
353
|
outline: none;
|
|
338
354
|
font-size: var(--umds-primitive-font-size-xxs);
|
|
@@ -557,3 +573,13 @@
|
|
|
557
573
|
border-bottom-color: var(--umds-semantic-color-primary-pure);
|
|
558
574
|
box-shadow: inset 0 -1px 0 var(--umds-semantic-color-primary-pure);
|
|
559
575
|
}
|
|
576
|
+
|
|
577
|
+
/* ── item no gatilho: o conteúdo do slot/função `item` da opção escolhida ── */
|
|
578
|
+
.umds-select__trigger-item {
|
|
579
|
+
display: inline-flex;
|
|
580
|
+
align-items: center;
|
|
581
|
+
min-width: 0;
|
|
582
|
+
overflow: hidden;
|
|
583
|
+
font-size: var(--umds-semantic-field-text-size);
|
|
584
|
+
color: var(--umds-semantic-field-text-color);
|
|
585
|
+
}
|
|
@@ -53,6 +53,8 @@ export interface SelectOption {
|
|
|
53
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.
|
|
54
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).
|
|
55
55
|
* @property {any} footer - Rodapé fixo da lista, com ações ("Gerenciar marcadores"). Passe o elemento correspondente (React node ou Vue slot `footer`).
|
|
56
|
+
* @property {any} item - Conteúdo de cada opção na lista, e do gatilho com a opção escolhida (seleção única). No React, uma função `({ optionData }) => nó` (`optionData` é a opção: `label`, `value` e os campos seus). No Vue, o slot com escopo `#item="{ optionData }"` (o mesmo `optionData`; `option` é um apelido). Sem ela, a opção é o `label` em texto. Na lista de seleção múltipla e na do `emptyOptionLabel` o gatilho segue mostrando o texto.
|
|
57
|
+
* @property {string|number} listMinWidth - Largura mínima da lista das variantes de filtro: um número (px) ou uma medida CSS ("220px"). Padrão: 240px. A lista tem a largura do gatilho (nunca menor), cresce até o conteúdo e nunca passa da borda da janela: perto da borda direita ela se alinha ao fim do gatilho. A busca interna acompanha a largura da lista, sem alargá-la.
|
|
56
58
|
* @quandoUsar Escolher UM valor de uma lista fechada. O valor selecionado fica visível no campo.
|
|
57
59
|
* @quandoNaoUsar Para escolher VÁRIOS valores — use `MultiSelect`. Para um menu de AÇÕES (editar, excluir, duplicar) em vez de valores, use `Dropdown`. Para 2 a 4 opções mutuamente exclusivas sempre visíveis, use `Radio`.
|
|
58
60
|
*/
|
|
@@ -110,6 +112,11 @@ export interface SelectProps {
|
|
|
110
112
|
emptyOptionLabel?: string;
|
|
111
113
|
/** Rodapé fixo da lista (ações). React node ou slot `footer` no Vue. */
|
|
112
114
|
footer?: any;
|
|
115
|
+
/**
|
|
116
|
+
* Largura mínima da lista das variantes de filtro: número (px) ou medida CSS ("220px"). A lista nunca
|
|
117
|
+
* fica mais estreita que o gatilho. @default 240 (o `filter` do DS)
|
|
118
|
+
*/
|
|
119
|
+
listMinWidth?: string | number;
|
|
113
120
|
pagination?: boolean;
|
|
114
121
|
hasMore?: boolean;
|
|
115
122
|
onLoadMore?: () => void;
|
|
@@ -3,7 +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
|
+
import { computePosition, autoUpdate, offset, flip, shift, size, } from "@floating-ui/dom";
|
|
7
7
|
import Text from "../Text/Text";
|
|
8
8
|
import Icon from "../Icon/Icon";
|
|
9
9
|
import Spinner from "../Spinner/Spinner";
|
|
@@ -38,6 +38,7 @@ function Select(props) {
|
|
|
38
38
|
const [floatW, setFloatW] = useState(() => 0);
|
|
39
39
|
const [floatMaxH, setFloatMaxH] = useState(() => 0);
|
|
40
40
|
const [listMaxH, setListMaxH] = useState(() => 0);
|
|
41
|
+
const [alignEnd, setAlignEnd] = useState(() => false);
|
|
41
42
|
const [searchTerm, setSearchTerm] = useState(() => "");
|
|
42
43
|
const [internalSelected, setInternalSelected] = useState(() => Array.isArray(props.defaultValue)
|
|
43
44
|
? props.defaultValue
|
|
@@ -150,6 +151,20 @@ function Select(props) {
|
|
|
150
151
|
}
|
|
151
152
|
return String(currentVal[0]);
|
|
152
153
|
}
|
|
154
|
+
function opcaoDoGatilho() {
|
|
155
|
+
if (props.multiple || isPlaceholder())
|
|
156
|
+
return undefined;
|
|
157
|
+
return selectedOptionsList()[0];
|
|
158
|
+
}
|
|
159
|
+
function itemNoGatilho() {
|
|
160
|
+
return typeof props.item === "function" && !!opcaoDoGatilho();
|
|
161
|
+
}
|
|
162
|
+
function conteudoDoItemNoGatilho() {
|
|
163
|
+
return props.item({
|
|
164
|
+
optionData: opcaoDoGatilho(),
|
|
165
|
+
option: opcaoDoGatilho(),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
153
168
|
function triggerSwatchColor() {
|
|
154
169
|
if (props.triggerSwatch)
|
|
155
170
|
return props.triggerSwatch;
|
|
@@ -216,6 +231,7 @@ function Select(props) {
|
|
|
216
231
|
props.variant === "search" ? "umds-select--filter-search" : "",
|
|
217
232
|
props.variant === "combo" ? "umds-select--filter-combo" : "",
|
|
218
233
|
opened() && floatMode ? "umds-select--floating" : "",
|
|
234
|
+
opened() && alignEnd ? "umds-select--list-end" : "",
|
|
219
235
|
props.triggerIcon ? "umds-select--has-trigger-icon" : "",
|
|
220
236
|
opensUp && props.searchVariant !== "inline"
|
|
221
237
|
? "umds-select--placement-top"
|
|
@@ -307,6 +323,7 @@ function Select(props) {
|
|
|
307
323
|
const flutuante = insideDialog();
|
|
308
324
|
setFloatMode(flutuante);
|
|
309
325
|
setFloatReady(false);
|
|
326
|
+
setAlignEnd(false);
|
|
310
327
|
setOpensUp(shouldOpenUp(flutuante));
|
|
311
328
|
// Abre com a opção já selecionada ativa (padrão combobox).
|
|
312
329
|
setActiveIndex(filteredOptionsList().findIndex((opt) => isOptionSelected(opt.value)));
|
|
@@ -326,6 +343,10 @@ function Select(props) {
|
|
|
326
343
|
flip({
|
|
327
344
|
fallbackStrategy: "bestFit",
|
|
328
345
|
}),
|
|
346
|
+
// Perto da borda da janela, a lista desliza para dentro (8px de respiro).
|
|
347
|
+
shift({
|
|
348
|
+
padding: 8,
|
|
349
|
+
}),
|
|
329
350
|
size({
|
|
330
351
|
apply({ availableHeight, rects }) {
|
|
331
352
|
setFloatW(rects.reference.width);
|
|
@@ -339,6 +360,22 @@ function Select(props) {
|
|
|
339
360
|
setFloatReady(true);
|
|
340
361
|
});
|
|
341
362
|
}
|
|
363
|
+
function listMinWidthCss() {
|
|
364
|
+
const pedida = props.listMinWidth;
|
|
365
|
+
if (!isFilter() || pedida === undefined || pedida === null || pedida === "")
|
|
366
|
+
return "";
|
|
367
|
+
return typeof pedida === "number" ? pedida + "px" : String(pedida);
|
|
368
|
+
}
|
|
369
|
+
function keepInWindow() {
|
|
370
|
+
if (typeof window === "undefined" || !dropdownRef.current)
|
|
371
|
+
return;
|
|
372
|
+
if (!opened() || !isFilter() || floatMode || alignEnd)
|
|
373
|
+
return;
|
|
374
|
+
const caixa = dropdownRef.current.getBoundingClientRect();
|
|
375
|
+
// 8px de respiro até a borda: o degrau nano do DS.
|
|
376
|
+
if (caixa.right > window.innerWidth - 8)
|
|
377
|
+
setAlignEnd(true);
|
|
378
|
+
}
|
|
342
379
|
function dropdownStyle() {
|
|
343
380
|
if (props.searchVariant === "inline")
|
|
344
381
|
return {
|
|
@@ -355,7 +392,11 @@ function Select(props) {
|
|
|
355
392
|
};
|
|
356
393
|
if (isFilter()) {
|
|
357
394
|
estilo.minWidth =
|
|
358
|
-
"max(" +
|
|
395
|
+
"max(" +
|
|
396
|
+
floatW +
|
|
397
|
+
"px, " +
|
|
398
|
+
(listMinWidthCss() || "calc(var(--umds-primitive-spacing-xxl) * 3)") +
|
|
399
|
+
")";
|
|
359
400
|
}
|
|
360
401
|
else {
|
|
361
402
|
estilo.width = floatW + "px";
|
|
@@ -364,11 +405,12 @@ function Select(props) {
|
|
|
364
405
|
estilo.maxHeight = floatMaxH + "px";
|
|
365
406
|
return estilo;
|
|
366
407
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
408
|
+
const estilo = {};
|
|
409
|
+
if (listMaxH)
|
|
410
|
+
estilo.maxHeight = listMaxH + "px";
|
|
411
|
+
if (listMinWidthCss())
|
|
412
|
+
estilo.minWidth = "max(100%, " + listMinWidthCss() + ")";
|
|
413
|
+
return estilo;
|
|
372
414
|
}
|
|
373
415
|
function toggle() {
|
|
374
416
|
if (props.disabled || props.readOnly)
|
|
@@ -575,6 +617,7 @@ function Select(props) {
|
|
|
575
617
|
latestOpenChangeRef.current = props.onOpenChange;
|
|
576
618
|
if (opened() && !prepared)
|
|
577
619
|
prepareOpen();
|
|
620
|
+
keepInWindow();
|
|
578
621
|
if (typeof document === "undefined")
|
|
579
622
|
return;
|
|
580
623
|
const handleClickOutside = (event) => {
|
|
@@ -600,6 +643,9 @@ function Select(props) {
|
|
|
600
643
|
prepareOpen();
|
|
601
644
|
if (!opened() && prepared)
|
|
602
645
|
setPrepared(false);
|
|
646
|
+
if (!opened() && alignEnd)
|
|
647
|
+
setAlignEnd(false);
|
|
648
|
+
keepInWindow();
|
|
603
649
|
// Lista flutuante: segue o gatilho (scroll, resize) enquanto aberta.
|
|
604
650
|
if (opened() &&
|
|
605
651
|
floatMode &&
|
|
@@ -649,7 +695,7 @@ function Select(props) {
|
|
|
649
695
|
}, []);
|
|
650
696
|
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
697
|
background: triggerSwatchColor(),
|
|
652
|
-
} })) : null, displayLabelText() ? (_jsx(Text, { as: "span", variant: "body2", className: isPlaceholder() ? "umds-select__placeholder" : "", children: displayLabelText() })) : null, props.multiple &&
|
|
698
|
+
} })) : null, itemNoGatilho() ? (_jsx("span", { className: "umds-select__trigger-item", children: conteudoDoItemNoGatilho() })) : null, displayLabelText() && !itemNoGatilho() ? (_jsx(Text, { as: "span", variant: "body2", className: isPlaceholder() ? "umds-select__placeholder" : "", children: displayLabelText() })) : null, props.multiple &&
|
|
653
699
|
props.selectionDisplay === "tags" &&
|
|
654
700
|
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: [
|
|
655
701
|
"umds-select__option",
|
|
@@ -661,7 +707,7 @@ function Select(props) {
|
|
|
661
707
|
.filter(Boolean)
|
|
662
708
|
.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
709
|
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 &&
|
|
710
|
+
} })) : null, _jsx("div", { className: "umds-select__option-content", children: _jsx(_Fragment, { children: typeof props.item === "function" ? props.item({ optionData: option, option }) : 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 &&
|
|
665
711
|
props.selectionDisplay === "list" &&
|
|
666
712
|
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] }));
|
|
667
713
|
}
|
|
@@ -290,3 +290,11 @@
|
|
|
290
290
|
.umds-stat-card--rotulo-topo.umds-stat-card--com-secundaria:not(:has(.umds-stat-card__icone)) .umds-stat-card__valor {
|
|
291
291
|
grid-column: 1 / 3;
|
|
292
292
|
}
|
|
293
|
+
|
|
294
|
+
/* ── size="sm": o KPI de 18px/800 ──
|
|
295
|
+
18px não está na escala de fonte do DS (16 e 20): é a medida dos KPIs da home do CrisDoc, mantida como literal.
|
|
296
|
+
Dois seletores de classe para vencer o 32/800 do `labelPosition="top"` sem depender da ordem. */
|
|
297
|
+
.umds-stat-card.umds-stat-card--sm .umds-stat-card__valor {
|
|
298
|
+
font-size: 1.125rem; /* 18px */
|
|
299
|
+
font-weight: var(--umds-primitive-font-weight-extrabold);
|
|
300
|
+
}
|
|
@@ -22,6 +22,7 @@ import './StatCard.css';
|
|
|
22
22
|
* @property {string} trendIntent - Se a variação é boa ou ruim (cor), independente da direção: 'positive' | 'negative' | 'neutral'
|
|
23
23
|
* @property {string} trendLabel - Texto da variação (ex.: '12% em relação ao mês anterior')
|
|
24
24
|
* @property {string} description - Texto auxiliar abaixo do rótulo
|
|
25
|
+
* @property {string} size - 'md' (padrão) | 'sm': o `sm` leva o número a 18px/800, o tamanho dos KPIs da home do CrisDoc (fora da escala de fonte do DS, que vai de 16 a 20). Vale também com `labelPosition="top"`.
|
|
25
26
|
* @property {string} labelPosition - Onde o rótulo aparece: 'bottom' (padrão, embaixo do número) | 'top' (no topo do cartão, acima do ícone e do número, com o número em 32/800). No HTML o número continua vindo antes do rótulo, então o leitor de tela anuncia igual.
|
|
26
27
|
* @property {string|number} secondaryValue - Segunda medida do cartão, menor, à direita da principal ("22,31%" do setor e "14,11%" do geral). Vem depois do resto no HTML. `0` aparece; vazio e ausente não.
|
|
27
28
|
* @property {string} secondaryLabel - O que a segunda medida significa ("Geral")
|
|
@@ -73,6 +74,8 @@ export interface StatCardProps {
|
|
|
73
74
|
secondaryValue?: string | number;
|
|
74
75
|
/** O que a segunda medida significa. */
|
|
75
76
|
secondaryLabel?: string;
|
|
77
|
+
/** Tamanho do número: `sm` é o KPI de 18px/800. @default 'md' */
|
|
78
|
+
size?: "md" | "sm";
|
|
76
79
|
/** Cor do número principal. @default 'default' */
|
|
77
80
|
valueTone?: "default" | "primary" | "positive" | "negative" | "info";
|
|
78
81
|
/**
|
|
@@ -10,6 +10,7 @@ function StatCard(props) {
|
|
|
10
10
|
"umds-stat-card",
|
|
11
11
|
`umds-stat-card--${props.variant || "neutral"}`,
|
|
12
12
|
props.align === "center" ? "umds-stat-card--centro" : "",
|
|
13
|
+
props.size === "sm" ? "umds-stat-card--sm" : "",
|
|
13
14
|
props.labelPosition === "top" ? "umds-stat-card--rotulo-topo" : "",
|
|
14
15
|
temSecundaria() ? "umds-stat-card--com-secundaria" : "",
|
|
15
16
|
props.valueTone && props.valueTone !== "default"
|
|
@@ -93,6 +93,17 @@
|
|
|
93
93
|
white-space: nowrap;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/* ============================
|
|
97
|
+
Densidade compacta (density="compact" no Tabs)
|
|
98
|
+
Altura mínima de 40px (o degrau sm do DS) e padding vertical de 8px: com o texto de 14px e a barra de
|
|
99
|
+
3px, a aba fica nos 40. Só o padding vertical (padding-block), para não desfazer o espaço do × da aba
|
|
100
|
+
fechável. O contador em pílula não muda: cinza na aba inativa e azul só na ativa.
|
|
101
|
+
============================ */
|
|
102
|
+
.umds-tabs--compact .umds-tabs__tab {
|
|
103
|
+
min-height: var(--umds-primitive-spacing-sm); /* 40px */
|
|
104
|
+
padding-block: var(--umds-primitive-spacing-nano);
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
/* ============================
|
|
97
108
|
Label
|
|
98
109
|
============================ */
|
|
@@ -14,6 +14,13 @@ export interface TabsProps {
|
|
|
14
14
|
onChange?: (index: string) => void;
|
|
15
15
|
/** Label de acessibilidade para o tablist */
|
|
16
16
|
ariaLabel?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Densidade das abas: `compact` leva a altura mínima de 48px a 40px (padding vertical de 8px), para barras
|
|
19
|
+
* de abas dentro de uma tela (o CrisDoc usa 40). O contador em pílula não muda: cinza na aba inativa e azul
|
|
20
|
+
* só na ativa, nas duas densidades.
|
|
21
|
+
* @default 'default'
|
|
22
|
+
*/
|
|
23
|
+
density?: "default" | "compact";
|
|
17
24
|
/**
|
|
18
25
|
* Identificador estável usado como base dos ids acessíveis
|
|
19
26
|
* (`{id}-tab-{index}` / `{id}-panel-{index}`). Passe o MESMO id no
|
|
@@ -6,7 +6,13 @@ import Icon from "../Icon/Icon";
|
|
|
6
6
|
import TabsContext from "./tabs.context";
|
|
7
7
|
function Tabs(props) {
|
|
8
8
|
function classes() {
|
|
9
|
-
return [
|
|
9
|
+
return [
|
|
10
|
+
"umds-tabs",
|
|
11
|
+
props.density === "compact" ? "umds-tabs--compact" : "",
|
|
12
|
+
props.className,
|
|
13
|
+
]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(" ");
|
|
10
16
|
}
|
|
11
17
|
function barClasses() {
|
|
12
18
|
return ["umds-tabs__bar", props.onAdd ? "umds-tabs__bar--com-add" : ""]
|
|
@@ -194,7 +194,9 @@
|
|
|
194
194
|
|
|
195
195
|
.umds-tag--outlined.umds-tag--positive {
|
|
196
196
|
border-color: var(--umds-semantic-color-feedback-positive-pure);
|
|
197
|
-
|
|
197
|
+
/* O contorno transparente deixa o texto sobre o fundo de quem está atrás (um card tingido de azul-claro).
|
|
198
|
+
positive-pure sobre primary-light dá 3,6:1; positive-dark dá 7,9:1 e é o par de texto do `soft`. */
|
|
199
|
+
color: var(--umds-semantic-color-feedback-positive-dark);
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
.umds-tag--outlined.umds-tag--negative {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
5
|
+
import Accordion from "../components/Accordion/Accordion";
|
|
6
|
+
const ITENS = [
|
|
7
|
+
{ id: "prazos", title: "Prazos", content: "Dez dias úteis.", defaultOpen: true },
|
|
8
|
+
{ id: "anexos", title: "Anexos", content: "Até 10 MB." },
|
|
9
|
+
{ id: "sigilo", title: "Sigilo", content: "Só o setor." },
|
|
10
|
+
];
|
|
11
|
+
const aberto = (nome) => screen.getByRole("button", { name: nome }).getAttribute("aria-expanded") === "true";
|
|
12
|
+
describe("Accordion · não controlado (como antes)", () => {
|
|
13
|
+
it("começa pelos defaultOpen e o clique abre e fecha; onOpenChange avisa a nova lista", () => {
|
|
14
|
+
const aoMudar = vi.fn();
|
|
15
|
+
render(_jsx(Accordion, { items: ITENS, multiple: true, onOpenChange: aoMudar }));
|
|
16
|
+
expect(aberto("Prazos")).toBe(true);
|
|
17
|
+
fireEvent.click(screen.getByRole("button", { name: "Anexos" }));
|
|
18
|
+
expect(aberto("Anexos")).toBe(true);
|
|
19
|
+
expect(aoMudar).toHaveBeenLastCalledWith(["prazos", "anexos"]);
|
|
20
|
+
fireEvent.click(screen.getByRole("button", { name: "Prazos" }));
|
|
21
|
+
expect(aberto("Prazos")).toBe(false);
|
|
22
|
+
expect(aoMudar).toHaveBeenLastCalledWith(["anexos"]);
|
|
23
|
+
});
|
|
24
|
+
it("sem multiple, abrir um fecha o outro", () => {
|
|
25
|
+
render(_jsx(Accordion, { items: ITENS }));
|
|
26
|
+
fireEvent.click(screen.getByRole("button", { name: "Anexos" }));
|
|
27
|
+
expect(aberto("Anexos")).toBe(true);
|
|
28
|
+
expect(aberto("Prazos")).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("Accordion · controlado (open e onOpenChange)", () => {
|
|
32
|
+
it("open manda: o que está em open está aberto, ignorando defaultOpen", () => {
|
|
33
|
+
render(_jsx(Accordion, { items: ITENS, open: ["sigilo"] }));
|
|
34
|
+
expect(aberto("Sigilo")).toBe(true);
|
|
35
|
+
expect(aberto("Prazos")).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it("o clique só pede (onOpenChange); sem o app mudar open, nada abre", () => {
|
|
38
|
+
const aoMudar = vi.fn();
|
|
39
|
+
render(_jsx(Accordion, { items: ITENS, multiple: true, open: ["prazos"], onOpenChange: aoMudar }));
|
|
40
|
+
fireEvent.click(screen.getByRole("button", { name: "Anexos" }));
|
|
41
|
+
expect(aoMudar).toHaveBeenCalledWith(["prazos", "anexos"]);
|
|
42
|
+
expect(aberto("Anexos")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it("o app guarda a escolha: com o estado dele, abre, fecha e a lista chega em ordem", () => {
|
|
45
|
+
let ultima = [];
|
|
46
|
+
function App() {
|
|
47
|
+
const [abertos, setAbertos] = useState([]);
|
|
48
|
+
return (_jsx(Accordion, { items: ITENS, multiple: true, open: abertos, onOpenChange: (ids) => {
|
|
49
|
+
ultima = ids;
|
|
50
|
+
setAbertos(ids);
|
|
51
|
+
} }));
|
|
52
|
+
}
|
|
53
|
+
render(_jsx(App, {}));
|
|
54
|
+
expect(aberto("Prazos")).toBe(false);
|
|
55
|
+
fireEvent.click(screen.getByRole("button", { name: "Sigilo" }));
|
|
56
|
+
fireEvent.click(screen.getByRole("button", { name: "Anexos" }));
|
|
57
|
+
expect(ultima).toEqual(["sigilo", "anexos"]);
|
|
58
|
+
expect(aberto("Sigilo") && aberto("Anexos")).toBe(true);
|
|
59
|
+
fireEvent.click(screen.getByRole("button", { name: "Sigilo" }));
|
|
60
|
+
expect(ultima).toEqual(["anexos"]);
|
|
61
|
+
expect(aberto("Sigilo")).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it("open vazio é controlado (tudo fechado), não ausência", () => {
|
|
64
|
+
render(_jsx(Accordion, { items: ITENS, open: [] }));
|
|
65
|
+
expect(aberto("Prazos")).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it("item desabilitado não pede nada", () => {
|
|
68
|
+
const aoMudar = vi.fn();
|
|
69
|
+
render(_jsx(Accordion, { items: [{ id: "x", title: "Bloqueado", content: "...", disabled: true }], open: [], onOpenChange: aoMudar }));
|
|
70
|
+
fireEvent.click(screen.getByRole("button", { name: "Bloqueado" }));
|
|
71
|
+
expect(aoMudar).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import Badge from "../components/Badge/Badge";
|
|
5
|
+
import { lerArquivo } from "./lerArquivo";
|
|
6
|
+
const cssBadge = await lerArquivo("../core/src/components/Badge/Badge.css");
|
|
7
|
+
const css = cssBadge.replace(/\r\n/g, "\n");
|
|
8
|
+
const bloco = (seletor) => {
|
|
9
|
+
const inicio = css.indexOf(seletor + " {");
|
|
10
|
+
return css.slice(inicio, css.indexOf("}", inicio));
|
|
11
|
+
};
|
|
12
|
+
describe("Badge 2xs · padding lateral de 8px", () => {
|
|
13
|
+
it("a regra do 2xs usa o degrau nano (8px) dos lados e o micro (2px) em cima e embaixo", () => {
|
|
14
|
+
expect(bloco(".umds-badge--2xs")).toMatch(/padding:\s*var\(--umds-primitive-spacing-micro\)\s+var\(--umds-primitive-spacing-nano\)/);
|
|
15
|
+
});
|
|
16
|
+
it("os outros tamanhos não mudam de padding", () => {
|
|
17
|
+
expect(bloco(".umds-badge--xs")).toMatch(/padding:\s*var\(--umds-primitive-spacing-micro\)\s+var\(--umds-primitive-spacing-quarck\)/);
|
|
18
|
+
expect(bloco(".umds-badge--md")).toMatch(/padding:\s*var\(--umds-primitive-spacing-quarck\)\s+var\(--umds-primitive-spacing-nano\)/);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe("Badge attached · 22px e tom escuro", () => {
|
|
22
|
+
it("o rótulo colado tem altura mínima de 22px (24 - 2), de qualquer tamanho", () => {
|
|
23
|
+
expect(bloco(".umds-badge--attached")).toMatch(/min-height:\s*calc\(var\(--umds-primitive-spacing-xxs\)\s*-\s*var\(--umds-primitive-spacing-micro\)\)/);
|
|
24
|
+
const { container } = render(_jsx(Badge, { attached: true, size: "xs", variant: "neutral", appearance: "solid", children: "Tarefa manual" }));
|
|
25
|
+
expect(container.firstElementChild).toHaveClass("umds-badge--attached", "umds-badge--xs");
|
|
26
|
+
});
|
|
27
|
+
it("attachedTone dark escreve a classe; sem ela e com light, não", () => {
|
|
28
|
+
const { container } = render(_jsxs(_Fragment, { children: [_jsx(Badge, { attached: true, attachedTone: "dark", variant: "neutral", appearance: "solid", children: "A" }), _jsx(Badge, { attached: true, variant: "neutral", appearance: "solid", children: "B" }), _jsx(Badge, { attached: true, attachedTone: "light", variant: "neutral", appearance: "solid", children: "C" })] }));
|
|
29
|
+
expect(container.children[0]).toHaveClass("umds-badge--attached-dark");
|
|
30
|
+
expect(container.children[1]).not.toHaveClass("umds-badge--attached-dark");
|
|
31
|
+
expect(container.children[2]).not.toHaveClass("umds-badge--attached-dark");
|
|
32
|
+
});
|
|
33
|
+
it("sem attached o tom é ignorado", () => {
|
|
34
|
+
const { container } = render(_jsx(Badge, { attachedTone: "dark", variant: "neutral", appearance: "solid", children: "A" }));
|
|
35
|
+
expect(container.firstElementChild).not.toHaveClass("umds-badge--attached-dark");
|
|
36
|
+
});
|
|
37
|
+
it("CSS: o escuro é neutral-dark-down com texto branco, só no neutro sólido colado", () => {
|
|
38
|
+
const regra = bloco(".umds-badge--attached.umds-badge--neutral.umds-badge--solid.umds-badge--attached-dark");
|
|
39
|
+
expect(regra).toMatch(/background-color:\s*var\(--umds-semantic-color-neutral-dark-down\)/);
|
|
40
|
+
expect(regra).toMatch(/color:\s*var\(--umds-semantic-color-neutral-light-pure\)/);
|
|
41
|
+
expect(bloco(".umds-badge--attached.umds-badge--neutral.umds-badge--solid")).toMatch(/neutral-light-medium/);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import Button from "../components/Button/Button";
|
|
5
|
+
import { lerArquivo } from "./lerArquivo";
|
|
6
|
+
const cssButton = await lerArquivo("../core/src/components/Button/Button.css");
|
|
7
|
+
const css = cssButton.replace(/\r\n/g, "\n");
|
|
8
|
+
const bloco = (seletor) => {
|
|
9
|
+
const inicio = css.indexOf(seletor + " {");
|
|
10
|
+
return css.slice(inicio, css.indexOf("}", inicio));
|
|
11
|
+
};
|
|
12
|
+
describe("Button · density", () => {
|
|
13
|
+
it("comfortable escreve a classe; sem a prop e com default o botão é o de antes", () => {
|
|
14
|
+
render(_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", density: "comfortable", children: "A" }), _jsx(Button, { size: "sm", children: "B" }), _jsx(Button, { size: "sm", density: "default", children: "C" })] }));
|
|
15
|
+
expect(screen.getByRole("button", { name: "A" })).toHaveClass("umds-button--sm", "umds-button--comfortable");
|
|
16
|
+
expect(screen.getByRole("button", { name: "B" })).not.toHaveClass("umds-button--comfortable");
|
|
17
|
+
expect(screen.getByRole("button", { name: "C" })).not.toHaveClass("umds-button--comfortable");
|
|
18
|
+
});
|
|
19
|
+
it("CSS: o sm confortável tem 12px de padding lateral; o sm de antes segue em 8px; o só-ícone fica de fora", () => {
|
|
20
|
+
expect(bloco(".umds-button--sm.umds-button--comfortable:not(.umds-button--icon)")).toMatch(/padding:\s*0\s+var\(--umds-primitive-spacing-mini\)/);
|
|
21
|
+
expect(bloco(".umds-button--sm")).toMatch(/padding:\s*0\s+var\(--umds-primitive-spacing-nano\)/);
|
|
22
|
+
});
|
|
23
|
+
it("os outros tamanhos mantêm o padding (a densidade só mexe no sm)", () => {
|
|
24
|
+
expect(bloco(".umds-button--xs")).toMatch(/padding:\s*0\s+var\(--umds-primitive-spacing-mini\)/);
|
|
25
|
+
expect(bloco(".umds-button--md")).toMatch(/padding:\s*0\s+var\(--umds-primitive-spacing-xxxs\)/);
|
|
26
|
+
expect(bloco(".umds-button--lg")).toMatch(/padding:\s*0\s+var\(--umds-primitive-spacing-xxs\)/);
|
|
27
|
+
});
|
|
28
|
+
});
|