@1doc/1ds-react 2.12.1 → 2.13.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 +29 -0
- package/dist/components/Breadcrumb/Breadcrumb.css +50 -0
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +9 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +11 -2
- package/dist/components/Select/Select.css +62 -1
- package/dist/components/Select/Select.js +16 -2
- package/dist/components/Tabs/Tab.js +1 -1
- package/dist/components/Tabs/Tabs.css +3 -0
- package/dist/teste/BreadcrumbTruncado.test.d.ts +1 -0
- package/dist/teste/BreadcrumbTruncado.test.js +40 -0
- package/dist/teste/SelectFiltroCorETamanho.test.js +30 -2
- package/package.json +3 -3
- package/src/teste/BreadcrumbTruncado.test.tsx +52 -0
- package/src/teste/SelectFiltroCorETamanho.test.tsx +35 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @1doc/1ds-react
|
|
2
2
|
|
|
3
|
+
## 2.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ab0ee2b: feat(breadcrumb): `truncate`, a trilha numa linha só
|
|
8
|
+
|
|
9
|
+
O `Breadcrumb` ganha `truncate`. A trilha passa a ocupar uma linha só e, quando não cabe, a página atual (o último item) encolhe
|
|
10
|
+
primeiro, com reticências, até 80px (separador incluído); só então os níveis anteriores encolhem. O separador nunca encolhe e o rótulo inteiro fica no
|
|
11
|
+
`title` de cada item. Feito para barras de altura fixa, como o `AppSubheader`, quando o último nível é um texto longo: o CrisDoc
|
|
12
|
+
mostra ali o número e o assunto do processo aberto, e sem isto a trilha quebrava em três linhas no celular. Sem a prop, nada muda.
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<Breadcrumb
|
|
16
|
+
truncate
|
|
17
|
+
:items="[
|
|
18
|
+
{ label: '1Doc', href: '/' },
|
|
19
|
+
{ label: 'Proc. Administrativo 412/2026' },
|
|
20
|
+
{ label: assunto },
|
|
21
|
+
]"
|
|
22
|
+
/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 2.12.2
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- 42a97e7: Select (variantes de filtro): a lista fica como a do CrisDoc. Teto de 300px, com o nome longo cortado por reticências; a opção marcada sem fundo, em azul, com o ✓ de 12px à esquerda; e, aberta pelo mouse, sem o contorno do cursor de teclado nem a régua azul da busca, que voltam assim que a navegação é pelo teclado. Numa lista com cores, a opção sem cor (ex.: "Sem marcadores") ganha uma amostra tracejada, e a opção "sem filtro" (`emptyOptionLabel`) segue sem nenhuma. O Select de formulário não muda.
|
|
30
|
+
- f584230: Tabs: o ícone da aba passa de 20px (`size="md"`) para 14px, a altura do texto da aba. Com 20px ele ficava maior que a palavra ao lado. O texto continua em 14px.
|
|
31
|
+
|
|
3
32
|
## 2.12.1
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
|
@@ -70,3 +70,53 @@
|
|
|
70
70
|
font-weight: 600;
|
|
71
71
|
cursor: default;
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
/* ── truncate: uma linha só, com reticências ──
|
|
75
|
+
A página atual encolhe primeiro: o `flex-shrink` alto faz o algoritmo do
|
|
76
|
+
flex tirar dela quase todo o espaço que falta, até a largura mínima; dali
|
|
77
|
+
em diante o que ainda falta se divide entre os níveis anteriores. O 100000
|
|
78
|
+
é uma razão de encolhimento, não uma medida, e precisa ser enorme: o flex
|
|
79
|
+
reparte o encolhimento na proporção (razão × largura), e com 100 os níveis
|
|
80
|
+
anteriores ainda levavam de 0,1 a 0,8px, o bastante para virar "1D…" e
|
|
81
|
+
"Emiss…" com espaço sobrando. O separador nunca encolhe.
|
|
82
|
+
Com rótulo final mais curto que o mínimo, o item ocupa o mínimo e a sobra
|
|
83
|
+
fica depois do texto, no fim da trilha, onde não aparece. */
|
|
84
|
+
.umds-breadcrumb--truncate {
|
|
85
|
+
min-width: 0;
|
|
86
|
+
max-width: 100%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__list {
|
|
90
|
+
flex-wrap: nowrap;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__item {
|
|
94
|
+
min-width: 0;
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__item:last-child {
|
|
99
|
+
flex-shrink: 100000;
|
|
100
|
+
min-width: var(--umds-primitive-spacing-xxl); /* 80px com o separador: "Reforma…" ainda se lê */
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__item:only-child {
|
|
104
|
+
min-width: 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__sep {
|
|
108
|
+
flex-shrink: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__link,
|
|
112
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__current {
|
|
113
|
+
display: block;
|
|
114
|
+
min-width: 0;
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
text-overflow: ellipsis;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Em bloco, o alvo de 24px vem da altura da linha, e o texto fica centrado. */
|
|
120
|
+
.umds-breadcrumb--truncate .umds-breadcrumb__link {
|
|
121
|
+
line-height: var(--umds-primitive-spacing-xxs); /* 24px */
|
|
122
|
+
}
|
|
@@ -14,6 +14,7 @@ export interface BreadcrumbItem {
|
|
|
14
14
|
* Breadcrumb component — navegação estrutural (trilha de páginas).
|
|
15
15
|
* @property {BreadcrumbItem[]} items - Lista de itens da trilha; o último é a página atual
|
|
16
16
|
* @property {string} separator - Separador entre itens (padrão '/')
|
|
17
|
+
* @property {boolean} truncate - Trilha numa linha só, com reticências quando não cabe
|
|
17
18
|
* @property {string} className - Classes CSS adicionais
|
|
18
19
|
* @quandoUsar Mostrar ONDE o usuário está na hierarquia do sistema e permitir subir de nível. Use em páginas com profundidade 2 ou mais.
|
|
19
20
|
* @quandoNaoUsar Para progresso dentro de um fluxo de etapas — use `Stepper`. Para navegar entre seções da mesma página, use `Tabs`.
|
|
@@ -23,6 +24,14 @@ export interface BreadcrumbProps {
|
|
|
23
24
|
ariaLabel?: string;
|
|
24
25
|
items: BreadcrumbItem[];
|
|
25
26
|
separator?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Mantém a trilha numa linha só. Quando não cabe, a página atual (último item)
|
|
29
|
+
* encolhe primeiro, com reticências, até uma largura mínima; só então os
|
|
30
|
+
* níveis anteriores encolhem. O rótulo inteiro fica no `title` de cada item.
|
|
31
|
+
* Use em barras de altura fixa (subheader) e quando o último nível é um texto
|
|
32
|
+
* longo, como o assunto de um processo. @default false
|
|
33
|
+
*/
|
|
34
|
+
truncate?: boolean;
|
|
26
35
|
className?: string;
|
|
27
36
|
}
|
|
28
37
|
declare function Breadcrumb(props: BreadcrumbProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,11 +6,20 @@ function Breadcrumb(props) {
|
|
|
6
6
|
return props.separator || "/";
|
|
7
7
|
}
|
|
8
8
|
function classes() {
|
|
9
|
-
return [
|
|
9
|
+
return [
|
|
10
|
+
"umds-breadcrumb",
|
|
11
|
+
props.truncate ? "umds-breadcrumb--truncate" : "",
|
|
12
|
+
props.className,
|
|
13
|
+
]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(" ");
|
|
10
16
|
}
|
|
11
17
|
function itemList() {
|
|
12
18
|
return props.items || [];
|
|
13
19
|
}
|
|
20
|
+
function titleOf(item) {
|
|
21
|
+
return props.truncate ? item.label : undefined;
|
|
22
|
+
}
|
|
14
23
|
function isLast(index) {
|
|
15
24
|
return index === (props.items || []).length - 1;
|
|
16
25
|
}
|
|
@@ -24,6 +33,6 @@ function Breadcrumb(props) {
|
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
|
-
return (_jsx("nav", { "aria-label": props.ariaLabel || "Navegação estrutural", className: classes(), children: _jsx("ol", { className: "umds-breadcrumb__list", children: itemList()?.map((item, index) => (_jsxs("li", { className: "umds-breadcrumb__item", children: [index > 0 ? (_jsx("span", { "aria-hidden": "true", className: "umds-breadcrumb__sep", children: sep() })) : null, !isLast(index) && !!item.href ? (_jsx("a", { className: "umds-breadcrumb__link", href: item.href, children: item.label })) : null, !isLast(index) && !item.href ? (_jsx("span", { role: "button", className: "umds-breadcrumb__link", tabIndex: 0, onClick: (event) => item.onClick && item.onClick(), onKeyDown: (event) => handleItemKey(event, item), children: item.label })) : null, isLast(index) ? (_jsx("span", { "aria-current": "page", className: "umds-breadcrumb__current", children: item.label })) : null] }, index))) }) }));
|
|
36
|
+
return (_jsx("nav", { "aria-label": props.ariaLabel || "Navegação estrutural", className: classes(), children: _jsx("ol", { className: "umds-breadcrumb__list", children: itemList()?.map((item, index) => (_jsxs("li", { className: "umds-breadcrumb__item", children: [index > 0 ? (_jsx("span", { "aria-hidden": "true", className: "umds-breadcrumb__sep", children: sep() })) : null, !isLast(index) && !!item.href ? (_jsx("a", { className: "umds-breadcrumb__link", href: item.href, title: titleOf(item), children: item.label })) : null, !isLast(index) && !item.href ? (_jsx("span", { role: "button", className: "umds-breadcrumb__link", tabIndex: 0, title: titleOf(item), onClick: (event) => item.onClick && item.onClick(), onKeyDown: (event) => handleItemKey(event, item), children: item.label })) : null, isLast(index) ? (_jsx("span", { "aria-current": "page", className: "umds-breadcrumb__current", title: titleOf(item), children: item.label })) : null] }, index))) }) }));
|
|
28
37
|
}
|
|
29
38
|
export default Breadcrumb;
|
|
@@ -230,7 +230,62 @@
|
|
|
230
230
|
(estilo inline). A lista nunca é mais estreita que o gatilho (100%). */
|
|
231
231
|
width: max-content;
|
|
232
232
|
min-width: max(100%, calc(var(--umds-primitive-spacing-xxl) * 3));
|
|
233
|
-
|
|
233
|
+
/* Teto de 300px, a lista do CrisDoc: o nome longo corta com reticências em vez de esticar a lista.
|
|
234
|
+
Literal intencional (sem token de largura de menu). Gatilho mais largo que isso ainda vence, porque
|
|
235
|
+
o min-width (100%) ganha do max-width. */
|
|
236
|
+
max-width: min(300px, calc(100vw - var(--umds-primitive-spacing-xxxs) * 2));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Opção do filtro numa linha só: o que não cabe nos 300px vira reticências. */
|
|
240
|
+
.umds-select--filter .umds-select__option-content .umds-text {
|
|
241
|
+
overflow: hidden;
|
|
242
|
+
white-space: nowrap;
|
|
243
|
+
text-overflow: ellipsis;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ── Filtro: a opção marcada como no CrisDoc ──
|
|
247
|
+
Sem fundo, texto azul em semibold e o ✓ de 12px à ESQUERDA (no DOM ele vem depois do texto; o
|
|
248
|
+
`order` só muda a posição visual, a leitura segue a mesma). O fundo azul-claro do Select de
|
|
249
|
+
formulário continua lá; no filtro o azul do texto já diz qual está valendo. */
|
|
250
|
+
.umds-select--filter .umds-select__option--selected,
|
|
251
|
+
.umds-select--filter .umds-select__option--selected.umds-select__option--active {
|
|
252
|
+
background-color: transparent;
|
|
253
|
+
color: var(--umds-semantic-color-primary-pure);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.umds-select--filter .umds-select__option--selected:hover {
|
|
257
|
+
background-color: var(--umds-semantic-color-background-light);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.umds-select--filter .umds-select__option--selected .umds-text {
|
|
261
|
+
color: inherit;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.umds-select--filter .umds-select__option-check {
|
|
265
|
+
order: -1;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.umds-select--filter .umds-select__option-check .umds-icon {
|
|
269
|
+
width: var(--umds-primitive-font-size-xxxs);
|
|
270
|
+
height: var(--umds-primitive-font-size-xxxs);
|
|
271
|
+
font-size: var(--umds-primitive-font-size-xxxs); /* 12px */
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* ── Filtro aberto pelo mouse: sem cursor de teclado e sem a régua azul da busca ──
|
|
275
|
+
O Select abre com a opção marcada ativa (padrão combobox) e foca a busca, e as duas marcas de foco
|
|
276
|
+
apareciam mesmo para quem clicou. Elas voltam assim que a navegação é pelo teclado
|
|
277
|
+
(`umds-select--teclado`: abriu por tecla ou andou por seta), que é para quem servem. */
|
|
278
|
+
.umds-select--filter:not(.umds-select--teclado) .umds-select__option--active {
|
|
279
|
+
outline: none;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.umds-select--filter:not(.umds-select--teclado) .umds-select__option--active:not(:hover):not(.umds-select__option--selected) {
|
|
283
|
+
background-color: transparent;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.umds-select--filter:not(.umds-select--teclado) .umds-select__search-container:focus-within {
|
|
287
|
+
border-bottom-color: var(--umds-semantic-color-neutral-light-medium);
|
|
288
|
+
box-shadow: none;
|
|
234
289
|
}
|
|
235
290
|
|
|
236
291
|
/* Perto da borda direita da janela: a lista se alinha ao fim do gatilho (`alignEnd`, medido ao abrir). */
|
|
@@ -322,6 +377,12 @@
|
|
|
322
377
|
border: var(--umds-primitive-border-width-hairline) solid var(--umds-semantic-color-neutral-light-down);
|
|
323
378
|
}
|
|
324
379
|
|
|
380
|
+
/* Amostra da opção sem cor numa lista com cores: só o contorno, tracejado. */
|
|
381
|
+
.umds-select__swatch--vazio {
|
|
382
|
+
background: transparent;
|
|
383
|
+
border-style: dashed;
|
|
384
|
+
}
|
|
385
|
+
|
|
325
386
|
/* ── Lista flutuante (dentro de diálogo) ──
|
|
326
387
|
O estilo inline (fixed, translate, largura e altura medidas) vem do componente. */
|
|
327
388
|
.umds-select--floating .umds-select__dropdown {
|
|
@@ -49,6 +49,7 @@ function Select(props) {
|
|
|
49
49
|
const [shouldFocusSearch, setShouldFocusSearch] = useState(() => false);
|
|
50
50
|
const [shouldFocusTrigger, setShouldFocusTrigger] = useState(() => false);
|
|
51
51
|
const [activeIndex, setActiveIndex] = useState(() => -1);
|
|
52
|
+
const [teclado, setTeclado] = useState(() => false);
|
|
52
53
|
const [uid, setUid] = useState(() => "");
|
|
53
54
|
function opened() {
|
|
54
55
|
if (props.open !== undefined)
|
|
@@ -60,6 +61,16 @@ function Select(props) {
|
|
|
60
61
|
props.variant === "search" ||
|
|
61
62
|
props.variant === "combo");
|
|
62
63
|
}
|
|
64
|
+
function isAmostraVazia(option) {
|
|
65
|
+
if (!isFilter() || option.color || option.value === "")
|
|
66
|
+
return false;
|
|
67
|
+
const lista = props.options || [];
|
|
68
|
+
return lista.some((o) => !!o.color);
|
|
69
|
+
}
|
|
70
|
+
function toggleByPointer() {
|
|
71
|
+
setTeclado(false);
|
|
72
|
+
toggle();
|
|
73
|
+
}
|
|
63
74
|
function setOpen(next) {
|
|
64
75
|
if (next === opened())
|
|
65
76
|
return;
|
|
@@ -232,6 +243,7 @@ function Select(props) {
|
|
|
232
243
|
props.variant === "combo" ? "umds-select--filter-combo" : "",
|
|
233
244
|
opened() && floatMode ? "umds-select--floating" : "",
|
|
234
245
|
opened() && alignEnd ? "umds-select--list-end" : "",
|
|
246
|
+
opened() && teclado ? "umds-select--teclado" : "",
|
|
235
247
|
props.triggerIcon ? "umds-select--has-trigger-icon" : "",
|
|
236
248
|
opensUp && props.searchVariant !== "inline"
|
|
237
249
|
? "umds-select--placement-top"
|
|
@@ -474,6 +486,7 @@ function Select(props) {
|
|
|
474
486
|
if (next >= opts.length)
|
|
475
487
|
next = 0;
|
|
476
488
|
setActiveIndex(next);
|
|
489
|
+
setTeclado(true);
|
|
477
490
|
}
|
|
478
491
|
function handleTriggerKeyDown(event) {
|
|
479
492
|
if (props.disabled || props.readOnly)
|
|
@@ -494,6 +507,7 @@ function Select(props) {
|
|
|
494
507
|
}
|
|
495
508
|
event.preventDefault();
|
|
496
509
|
if (!opened()) {
|
|
510
|
+
setTeclado(true);
|
|
497
511
|
toggle();
|
|
498
512
|
return;
|
|
499
513
|
}
|
|
@@ -693,7 +707,7 @@ function Select(props) {
|
|
|
693
707
|
}
|
|
694
708
|
};
|
|
695
709
|
}, []);
|
|
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) =>
|
|
710
|
+
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) => toggleByPointer(), 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: {
|
|
697
711
|
background: triggerSwatchColor(),
|
|
698
712
|
} })) : 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 &&
|
|
699
713
|
props.selectionDisplay === "tags" &&
|
|
@@ -707,7 +721,7 @@ function Select(props) {
|
|
|
707
721
|
.filter(Boolean)
|
|
708
722
|
.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: {
|
|
709
723
|
background: option.color,
|
|
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 &&
|
|
724
|
+
} })) : null, isAmostraVazia(option) ? (_jsx("span", { "aria-hidden": "true", className: "umds-select__swatch umds-select__swatch--vazio" })) : 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 &&
|
|
711
725
|
props.selectionDisplay === "list" &&
|
|
712
726
|
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] }));
|
|
713
727
|
}
|
|
@@ -110,6 +110,6 @@ function Tab(props) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
const tabsContext = useContext(TabsContext);
|
|
113
|
-
return (_jsxs("span", { role: "presentation", className: itemClasses(), children: [_jsxs("button", { role: "tab", id: tabId(), "aria-controls": panelId(), "data-tab-index": props.index, "aria-selected": isSelected(), "aria-disabled": props.disabled, tabIndex: isSelected() ? 0 : -1, disabled: props.disabled, onClick: (event) => handleClick(), onKeyDown: (event) => handleKeyDown(event), className: classes(), children: [props.icon ? (_jsx(Icon, {
|
|
113
|
+
return (_jsxs("span", { role: "presentation", className: itemClasses(), children: [_jsxs("button", { role: "tab", id: tabId(), "aria-controls": panelId(), "data-tab-index": props.index, "aria-selected": isSelected(), "aria-disabled": props.disabled, tabIndex: isSelected() ? 0 : -1, disabled: props.disabled, onClick: (event) => handleClick(), onKeyDown: (event) => handleKeyDown(event), className: classes(), children: [props.icon ? (_jsx(Icon, { customSize: "14px", variant: "inherit", icon: props.icon })) : null, _jsx("span", { className: labelClasses(), children: props.label }), temContador() ? (_jsx("span", { className: "umds-tabs__count", children: props.count })) : null] }), props.closable ? (_jsx("button", { type: "button", className: "umds-tabs__close", "aria-label": closeAriaText(), disabled: props.disabled, tabIndex: -1, onClick: (event) => handleClose(event), children: _jsx(Icon, { variant: "inherit", customSize: "10px", icon: faXmark }) })) : null] }));
|
|
114
114
|
}
|
|
115
115
|
export default Tab;
|
|
@@ -120,6 +120,9 @@
|
|
|
120
120
|
Icon
|
|
121
121
|
(o componente Icon não aceita className — o seletor mira a classe raiz
|
|
122
122
|
.umds-icon dentro da tab; a cor já é herdada via variant="inherit")
|
|
123
|
+
O ícone tem 14px, a altura do texto da aba (customSize no Tab.lite.tsx).
|
|
124
|
+
Com o size="md" de 20px ele ficava maior que a palavra ao lado (decisão do
|
|
125
|
+
Cris, 13/10).
|
|
123
126
|
============================ */
|
|
124
127
|
.umds-tabs__tab .umds-icon {
|
|
125
128
|
flex-shrink: 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import Breadcrumb from "../components/Breadcrumb/Breadcrumb";
|
|
4
|
+
import { render } from "@testing-library/react";
|
|
5
|
+
import { lerArquivo } from "./lerArquivo";
|
|
6
|
+
const cssBreadcrumb = (await lerArquivo("../core/src/components/Breadcrumb/Breadcrumb.css")).replace(/\r\n/g, "\n");
|
|
7
|
+
const bloco = (texto, seletor) => {
|
|
8
|
+
const inicio = texto.indexOf(seletor + " {");
|
|
9
|
+
expect(inicio, seletor).toBeGreaterThanOrEqual(0);
|
|
10
|
+
return texto.slice(inicio, texto.indexOf("}", inicio));
|
|
11
|
+
};
|
|
12
|
+
const ITENS = [
|
|
13
|
+
{ label: "1Doc", href: "#" },
|
|
14
|
+
{ label: "Proc. Administrativo 412/2026", onClick: () => { } },
|
|
15
|
+
{ label: "Reforma da cobertura do Ginásio Poliesportivo Municipal" },
|
|
16
|
+
];
|
|
17
|
+
describe("Breadcrumb · truncate", () => {
|
|
18
|
+
it("truncate escreve a classe na raiz; sem a prop, não", () => {
|
|
19
|
+
const { container } = render(_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: ITENS, truncate: true }), _jsx(Breadcrumb, { items: ITENS })] }));
|
|
20
|
+
expect(container.children[0]).toHaveClass("umds-breadcrumb--truncate");
|
|
21
|
+
expect(container.children[1]).not.toHaveClass("umds-breadcrumb--truncate");
|
|
22
|
+
});
|
|
23
|
+
it("com truncate, o rótulo inteiro vai no title do link, do item clicável e da página atual; sem, nenhum title", () => {
|
|
24
|
+
const { container } = render(_jsx(Breadcrumb, { items: ITENS, truncate: true }));
|
|
25
|
+
expect(container.querySelector("a.umds-breadcrumb__link")).toHaveAttribute("title", "1Doc");
|
|
26
|
+
expect(container.querySelector("[role=button].umds-breadcrumb__link")).toHaveAttribute("title", "Proc. Administrativo 412/2026");
|
|
27
|
+
expect(container.querySelector(".umds-breadcrumb__current")).toHaveAttribute("title", "Reforma da cobertura do Ginásio Poliesportivo Municipal");
|
|
28
|
+
const sem = render(_jsx(Breadcrumb, { items: ITENS }));
|
|
29
|
+
expect(sem.container.querySelectorAll("[title]")).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
it("CSS: uma linha; a página atual encolhe primeiro até 80px; o texto ganha reticências; o padrão segue quebrando linha", () => {
|
|
32
|
+
expect(bloco(cssBreadcrumb, ".umds-breadcrumb--truncate .umds-breadcrumb__list")).toMatch(/flex-wrap:\s*nowrap/);
|
|
33
|
+
const ultimo = bloco(cssBreadcrumb, ".umds-breadcrumb--truncate .umds-breadcrumb__item:last-child");
|
|
34
|
+
expect(ultimo).toMatch(/flex-shrink:\s*100000;/);
|
|
35
|
+
expect(ultimo).toMatch(/min-width:\s*var\(--umds-primitive-spacing-xxl\)/);
|
|
36
|
+
expect(bloco(cssBreadcrumb, ".umds-breadcrumb--truncate .umds-breadcrumb__sep")).toMatch(/flex-shrink:\s*0/);
|
|
37
|
+
expect(cssBreadcrumb).toMatch(/\.umds-breadcrumb--truncate \.umds-breadcrumb__current \{[^}]*text-overflow:\s*ellipsis/);
|
|
38
|
+
expect(bloco(cssBreadcrumb, ".umds-breadcrumb__list")).toMatch(/flex-wrap:\s*wrap/);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -37,14 +37,15 @@ describe("Select filtro · tamanho e fundo", () => {
|
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
39
|
describe("Select · cor por opção", () => {
|
|
40
|
-
it("a lista mostra uma amostra por opção com color; sem color,
|
|
40
|
+
it("a lista mostra uma amostra por opção com color; sem color, a amostra tracejada (filtro)", () => {
|
|
41
41
|
const { container } = render(_jsx(Select, { ariaLabel: "x", options: MARCADORES, variant: "filter" }));
|
|
42
42
|
fireEvent.click(screen.getByRole("combobox"));
|
|
43
43
|
const opcoes = [...container.querySelectorAll('[role="option"]')];
|
|
44
44
|
expect(opcoes[0].querySelector(".umds-select__swatch")).toHaveAttribute("aria-hidden", "true");
|
|
45
45
|
expect(opcoes[0].querySelector(".umds-select__swatch").style.background).toContain("--umds-semantic-color-feedback-negative-pure");
|
|
46
46
|
expect(opcoes[1].querySelector(".umds-select__swatch")).not.toBeNull();
|
|
47
|
-
expect(opcoes[2].querySelector(".umds-select__swatch")).
|
|
47
|
+
expect(opcoes[2].querySelector(".umds-select__swatch")).toHaveClass("umds-select__swatch--vazio");
|
|
48
|
+
expect(opcoes[2].querySelector(".umds-select__swatch")).toHaveAttribute("aria-hidden", "true");
|
|
48
49
|
});
|
|
49
50
|
it("o gatilho ganha a cor da opção escolhida (seleção única)", () => {
|
|
50
51
|
const { container } = render(_jsx(Select, { ariaLabel: "x", options: MARCADORES, variant: "filter", placeholder: "Marcadores", value: "revisao" }));
|
|
@@ -75,3 +76,30 @@ describe("Select · cor por opção", () => {
|
|
|
75
76
|
expect(screen.getByRole("option", { name: "Urgente" })).toBeInTheDocument();
|
|
76
77
|
});
|
|
77
78
|
});
|
|
79
|
+
describe("Select filtro · lista como a do CrisDoc", () => {
|
|
80
|
+
it("no filtro com cores, a opção sem cor ganha a amostra tracejada e o 'sem filtro' não ganha nenhuma", () => {
|
|
81
|
+
const { container } = render(_jsx(Select, { ariaLabel: "x", options: MARCADORES, variant: "filter", placeholder: "Marcadores", emptyOptionLabel: "Todos" }));
|
|
82
|
+
fireEvent.click(screen.getByRole("combobox"));
|
|
83
|
+
const vazias = container.querySelectorAll('[role="option"] .umds-select__swatch--vazio');
|
|
84
|
+
expect(vazias).toHaveLength(1);
|
|
85
|
+
expect(vazias[0].closest('[role="option"]')).toHaveTextContent("Sem marcador");
|
|
86
|
+
expect(screen.getByRole("option", { name: "Todos" }).querySelector(".umds-select__swatch")).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
it("no Select de formulário a opção sem cor segue sem amostra", () => {
|
|
89
|
+
const { container } = render(_jsx(Select, { ariaLabel: "x", options: MARCADORES }));
|
|
90
|
+
fireEvent.click(screen.getByRole("combobox"));
|
|
91
|
+
expect(container.querySelector(".umds-select__swatch--vazio")).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
it("aberto pelo mouse não marca navegação por teclado; tecla e seta marcam", () => {
|
|
94
|
+
const { container } = render(_jsx(Select, { ariaLabel: "x", options: MARCADORES, variant: "filter", placeholder: "Marcadores" }));
|
|
95
|
+
const raiz = () => container.firstElementChild;
|
|
96
|
+
const gatilho = screen.getByRole("combobox");
|
|
97
|
+
fireEvent.click(gatilho);
|
|
98
|
+
expect(raiz()).not.toHaveClass("umds-select--teclado");
|
|
99
|
+
fireEvent.keyDown(gatilho, { key: "ArrowDown" });
|
|
100
|
+
expect(raiz()).toHaveClass("umds-select--teclado");
|
|
101
|
+
fireEvent.keyDown(gatilho, { key: "Escape" });
|
|
102
|
+
fireEvent.keyDown(gatilho, { key: "Enter" });
|
|
103
|
+
expect(raiz()).toHaveClass("umds-select--teclado");
|
|
104
|
+
});
|
|
105
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1doc/1ds-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"react-dom": "^18.0.0",
|
|
46
46
|
"typescript": "^5.0.0",
|
|
47
47
|
"vitest": "^3.2.7",
|
|
48
|
-
"@1doc/1ds-
|
|
49
|
-
"@1doc/1ds-
|
|
48
|
+
"@1doc/1ds-tokens": "0.7.0",
|
|
49
|
+
"@1doc/1ds-core": "2.13.0"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import Breadcrumb from "../components/Breadcrumb/Breadcrumb";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { lerArquivo } from "./lerArquivo";
|
|
5
|
+
|
|
6
|
+
const cssBreadcrumb = (await lerArquivo("../core/src/components/Breadcrumb/Breadcrumb.css")).replace(/\r\n/g, "\n");
|
|
7
|
+
const bloco = (texto: string, seletor: string) => {
|
|
8
|
+
const inicio = texto.indexOf(seletor + " {");
|
|
9
|
+
expect(inicio, seletor).toBeGreaterThanOrEqual(0);
|
|
10
|
+
return texto.slice(inicio, texto.indexOf("}", inicio));
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const ITENS = [
|
|
14
|
+
{ label: "1Doc", href: "#" },
|
|
15
|
+
{ label: "Proc. Administrativo 412/2026", onClick: () => {} },
|
|
16
|
+
{ label: "Reforma da cobertura do Ginásio Poliesportivo Municipal" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
describe("Breadcrumb · truncate", () => {
|
|
20
|
+
it("truncate escreve a classe na raiz; sem a prop, não", () => {
|
|
21
|
+
const { container } = render(
|
|
22
|
+
<>
|
|
23
|
+
<Breadcrumb items={ITENS} truncate />
|
|
24
|
+
<Breadcrumb items={ITENS} />
|
|
25
|
+
</>,
|
|
26
|
+
);
|
|
27
|
+
expect(container.children[0]).toHaveClass("umds-breadcrumb--truncate");
|
|
28
|
+
expect(container.children[1]).not.toHaveClass("umds-breadcrumb--truncate");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("com truncate, o rótulo inteiro vai no title do link, do item clicável e da página atual; sem, nenhum title", () => {
|
|
32
|
+
const { container } = render(<Breadcrumb items={ITENS} truncate />);
|
|
33
|
+
expect(container.querySelector("a.umds-breadcrumb__link")).toHaveAttribute("title", "1Doc");
|
|
34
|
+
expect(container.querySelector("[role=button].umds-breadcrumb__link")).toHaveAttribute("title", "Proc. Administrativo 412/2026");
|
|
35
|
+
expect(container.querySelector(".umds-breadcrumb__current")).toHaveAttribute(
|
|
36
|
+
"title",
|
|
37
|
+
"Reforma da cobertura do Ginásio Poliesportivo Municipal",
|
|
38
|
+
);
|
|
39
|
+
const sem = render(<Breadcrumb items={ITENS} />);
|
|
40
|
+
expect(sem.container.querySelectorAll("[title]")).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("CSS: uma linha; a página atual encolhe primeiro até 80px; o texto ganha reticências; o padrão segue quebrando linha", () => {
|
|
44
|
+
expect(bloco(cssBreadcrumb, ".umds-breadcrumb--truncate .umds-breadcrumb__list")).toMatch(/flex-wrap:\s*nowrap/);
|
|
45
|
+
const ultimo = bloco(cssBreadcrumb, ".umds-breadcrumb--truncate .umds-breadcrumb__item:last-child");
|
|
46
|
+
expect(ultimo).toMatch(/flex-shrink:\s*100000;/);
|
|
47
|
+
expect(ultimo).toMatch(/min-width:\s*var\(--umds-primitive-spacing-xxl\)/);
|
|
48
|
+
expect(bloco(cssBreadcrumb, ".umds-breadcrumb--truncate .umds-breadcrumb__sep")).toMatch(/flex-shrink:\s*0/);
|
|
49
|
+
expect(cssBreadcrumb).toMatch(/\.umds-breadcrumb--truncate \.umds-breadcrumb__current \{[^}]*text-overflow:\s*ellipsis/);
|
|
50
|
+
expect(bloco(cssBreadcrumb, ".umds-breadcrumb__list")).toMatch(/flex-wrap:\s*wrap/);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -43,14 +43,15 @@ describe("Select filtro · tamanho e fundo", () => {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
describe("Select · cor por opção", () => {
|
|
46
|
-
it("a lista mostra uma amostra por opção com color; sem color,
|
|
46
|
+
it("a lista mostra uma amostra por opção com color; sem color, a amostra tracejada (filtro)", () => {
|
|
47
47
|
const { container } = render(<Select ariaLabel="x" options={MARCADORES} variant="filter" />);
|
|
48
48
|
fireEvent.click(screen.getByRole("combobox"));
|
|
49
49
|
const opcoes = [...container.querySelectorAll('[role="option"]')];
|
|
50
50
|
expect(opcoes[0].querySelector(".umds-select__swatch")).toHaveAttribute("aria-hidden", "true");
|
|
51
51
|
expect((opcoes[0].querySelector(".umds-select__swatch") as HTMLElement).style.background).toContain("--umds-semantic-color-feedback-negative-pure");
|
|
52
52
|
expect(opcoes[1].querySelector(".umds-select__swatch")).not.toBeNull();
|
|
53
|
-
expect(opcoes[2].querySelector(".umds-select__swatch")).
|
|
53
|
+
expect(opcoes[2].querySelector(".umds-select__swatch")).toHaveClass("umds-select__swatch--vazio");
|
|
54
|
+
expect(opcoes[2].querySelector(".umds-select__swatch")).toHaveAttribute("aria-hidden", "true");
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
it("o gatilho ganha a cor da opção escolhida (seleção única)", () => {
|
|
@@ -88,3 +89,35 @@ describe("Select · cor por opção", () => {
|
|
|
88
89
|
expect(screen.getByRole("option", { name: "Urgente" })).toBeInTheDocument();
|
|
89
90
|
});
|
|
90
91
|
});
|
|
92
|
+
|
|
93
|
+
describe("Select filtro · lista como a do CrisDoc", () => {
|
|
94
|
+
it("no filtro com cores, a opção sem cor ganha a amostra tracejada e o 'sem filtro' não ganha nenhuma", () => {
|
|
95
|
+
const { container } = render(
|
|
96
|
+
<Select ariaLabel="x" options={MARCADORES} variant="filter" placeholder="Marcadores" emptyOptionLabel="Todos" />,
|
|
97
|
+
);
|
|
98
|
+
fireEvent.click(screen.getByRole("combobox"));
|
|
99
|
+
const vazias = container.querySelectorAll('[role="option"] .umds-select__swatch--vazio');
|
|
100
|
+
expect(vazias).toHaveLength(1);
|
|
101
|
+
expect(vazias[0].closest('[role="option"]')).toHaveTextContent("Sem marcador");
|
|
102
|
+
expect(screen.getByRole("option", { name: "Todos" }).querySelector(".umds-select__swatch")).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("no Select de formulário a opção sem cor segue sem amostra", () => {
|
|
106
|
+
const { container } = render(<Select ariaLabel="x" options={MARCADORES} />);
|
|
107
|
+
fireEvent.click(screen.getByRole("combobox"));
|
|
108
|
+
expect(container.querySelector(".umds-select__swatch--vazio")).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("aberto pelo mouse não marca navegação por teclado; tecla e seta marcam", () => {
|
|
112
|
+
const { container } = render(<Select ariaLabel="x" options={MARCADORES} variant="filter" placeholder="Marcadores" />);
|
|
113
|
+
const raiz = () => container.firstElementChild as HTMLElement;
|
|
114
|
+
const gatilho = screen.getByRole("combobox");
|
|
115
|
+
fireEvent.click(gatilho);
|
|
116
|
+
expect(raiz()).not.toHaveClass("umds-select--teclado");
|
|
117
|
+
fireEvent.keyDown(gatilho, { key: "ArrowDown" });
|
|
118
|
+
expect(raiz()).toHaveClass("umds-select--teclado");
|
|
119
|
+
fireEvent.keyDown(gatilho, { key: "Escape" });
|
|
120
|
+
fireEvent.keyDown(gatilho, { key: "Enter" });
|
|
121
|
+
expect(raiz()).toHaveClass("umds-select--teclado");
|
|
122
|
+
});
|
|
123
|
+
});
|