@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 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 ["umds-breadcrumb", props.className].filter(Boolean).join(" ");
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
- max-width: calc(100vw - var(--umds-primitive-spacing-xxxs) * 2);
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) => 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: {
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, { size: "md", 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] }));
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, não", () => {
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")).toBeNull();
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.12.1",
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-core": "2.12.1",
49
- "@1doc/1ds-tokens": "0.7.0"
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, não", () => {
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")).toBeNull();
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
+ });