@1doc/1ds-react 2.12.2 → 2.14.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 +41 -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/Button/Button.css +6 -1
- package/dist/components/Button/Button.d.ts +11 -2
- package/dist/components/Button/Button.js +15 -3
- package/dist/components/Counter/Counter.css +43 -0
- package/dist/components/Counter/Counter.d.ts +57 -0
- package/dist/components/Counter/Counter.js +99 -0
- package/dist/components/Dropdown/Dropdown.css +2 -0
- package/dist/components/Dropdown/Dropdown.d.ts +9 -0
- package/dist/components/Dropdown/Dropdown.js +20 -2
- package/dist/components/List/List.css +47 -0
- package/dist/components/List/ListItem.d.ts +10 -0
- package/dist/components/List/ListItem.js +1 -1
- package/dist/components/StatCard/StatCard.css +57 -2
- package/dist/components/StatCard/StatCard.d.ts +12 -6
- package/dist/components/StatCard/StatCard.js +6 -2
- package/dist/components/Tooltip/Tooltip.css +3 -0
- package/dist/components/Tooltip/Tooltip.js +33 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/teste/BreadcrumbTruncado.test.d.ts +1 -0
- package/dist/teste/BreadcrumbTruncado.test.js +40 -0
- package/dist/teste/PedidosMatheus.test.d.ts +1 -0
- package/dist/teste/PedidosMatheus.test.js +91 -0
- package/dist/teste/StatCardTamanhoSm.test.js +8 -0
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/teste/BreadcrumbTruncado.test.tsx +52 -0
- package/src/teste/PedidosMatheus.test.tsx +129 -0
- package/src/teste/StatCardTamanhoSm.test.tsx +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @1doc/1ds-react
|
|
2
2
|
|
|
3
|
+
## 2.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- aca81ec: Ajustes pedidos na migração de produto para o 1DS, e um componente novo.
|
|
8
|
+
|
|
9
|
+
- **Counter** (novo): número que conta até o valor, para KPI de painel e total de páginas. Formato do `Intl.NumberFormat` (pt-BR por padrão), `prefix`/`suffix`/`decimals`, largura do valor final reservada, leitor de tela ouvindo só o valor final e respeito a `prefers-reduced-motion`.
|
|
10
|
+
- **ListItem**: slot `actions`, renderizado no `li` ao lado do elemento clicável (o ⋮ de menu deixa de ficar dentro do link). No Vue, o ListItem com `li` passa a ter raiz única, então funciona dentro de `TransitionGroup`.
|
|
11
|
+
- **Tooltip**: a cópia escondida do texto não escapa mais de área com rolagem (o wrapper ganhou `position: relative`), e a seta acompanha o gatilho quando o balão é empurrado para caber perto da borda.
|
|
12
|
+
- **Dropdown**: `href`, `target` e `rel` no `DropdownItem`. Com `href`, o item é um `<a role="menuitem">` (Ctrl+clique e nova aba funcionam), e o `onSelect` continua sendo chamado.
|
|
13
|
+
- **Button**: `href`, `target` e `rel`. Com `href`, o Button é um `<a>` com a mesma aparência; desabilitado, volta a ser `<button disabled>`. O hover do `variant="icon"` virou uma camada translúcida da cor do ícone: no fundo claro o cinza é o mesmo de antes, e sobre fundo colorido não vira mais um quadrado branco.
|
|
14
|
+
- **StatCard**: `secondaryTone` (cor da segunda medida), `loading` (os números viram Skeleton e o cartão ganha `aria-busy`) e `value` aceitando um nó, como um `Counter` (slot `value` no Vue).
|
|
15
|
+
|
|
16
|
+
## 2.13.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 1a927c5: StatCard: a segunda medida (`secondaryValue`) passa a ter o mesmo tamanho e peso do número principal, em todas as combinações (`md`, `sm` e `labelPosition="top"`). Com o rótulo no topo, o `secondaryLabel` fica na mesma linha da `description` ("Geral" ao lado de "Meu Setor"), em vez de colado embaixo do número.
|
|
21
|
+
|
|
22
|
+
## 2.13.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- ab0ee2b: feat(breadcrumb): `truncate`, a trilha numa linha só
|
|
27
|
+
|
|
28
|
+
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
|
|
29
|
+
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
|
|
30
|
+
`title` de cada item. Feito para barras de altura fixa, como o `AppSubheader`, quando o último nível é um texto longo: o CrisDoc
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
```vue
|
|
34
|
+
<Breadcrumb
|
|
35
|
+
truncate
|
|
36
|
+
:items="[
|
|
37
|
+
{ label: '1Doc', href: '/' },
|
|
38
|
+
{ label: 'Proc. Administrativo 412/2026' },
|
|
39
|
+
{ label: assunto },
|
|
40
|
+
]"
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
3
44
|
## 2.12.2
|
|
4
45
|
|
|
5
46
|
### 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;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
line-height: 1;
|
|
23
23
|
white-space: nowrap;
|
|
24
24
|
text-decoration: none;
|
|
25
|
+
box-sizing: border-box; /* com href o Button é <a>, que nasce content-box */
|
|
25
26
|
|
|
26
27
|
transition:
|
|
27
28
|
background-color var(--umds-semantic-motion-duration-fast) var(--umds-semantic-motion-easing-standard),
|
|
@@ -210,8 +211,12 @@
|
|
|
210
211
|
.umds-button--icon.umds-button--md { width: var(--umds-primitive-spacing-sm); } /* 40px */
|
|
211
212
|
.umds-button--icon.umds-button--lg { width: var(--umds-primitive-spacing-md); } /* 48px */
|
|
212
213
|
|
|
214
|
+
/* Camada de estado translúcida, da cor do próprio ícone: funciona sobre
|
|
215
|
+
qualquer fundo (o ⋮ num item azul selecionado não vira um quadrado branco).
|
|
216
|
+
12% de neutral-dark-down (#565656) sobre o branco dá #EBEBEB, o mesmo
|
|
217
|
+
neutral-light-medium de antes: no fundo claro o visual não muda. */
|
|
213
218
|
.umds-button--icon:hover:not(:disabled) {
|
|
214
|
-
background-color:
|
|
219
|
+
background-color: color-mix(in srgb, currentColor 12%, transparent);
|
|
215
220
|
}
|
|
216
221
|
|
|
217
222
|
.umds-button--icon:disabled {
|
|
@@ -22,8 +22,11 @@ import './Button.css';
|
|
|
22
22
|
* @property {boolean} autoFocus - Foca o botão automaticamente ao montar
|
|
23
23
|
* @property {number} tabIndex - Ordem de tabulação nativa
|
|
24
24
|
* @property {object} style - Estilos inline aplicados ao botão (objeto no React, string/objeto no Vue)
|
|
25
|
-
* @
|
|
26
|
-
* @
|
|
25
|
+
* @property {string} href - Botão que NAVEGA: com `href` o Button vira `<a>` com a mesma aparência (Ctrl+clique e "abrir em nova aba" funcionam). Desabilitado, volta a ser `<button disabled>`.
|
|
26
|
+
* @property {string} target - Destino do link (`_blank` ganha `rel="noopener noreferrer"`)
|
|
27
|
+
* @property {string} rel - Relação do link; sobrescreve o padrão do `target`
|
|
28
|
+
* @quandoUsar AÇÃO que muda estado: salvar, enviar, abrir modal, confirmar. Use `variant="icon"` para ação só com ícone (sempre com `ariaLabel`). Para navegação com cara de botão (o "Ver todos" de um rodapé de card), passe `href`: sai um `<a>` de verdade.
|
|
29
|
+
* @quandoNaoUsar Para navegar com cara de texto, no meio de uma frase, use `Link`. Nunca navegue por `onClick`: sem `href` não há Ctrl+clique nem "abrir em nova aba".
|
|
27
30
|
*/
|
|
28
31
|
export interface ButtonProps {
|
|
29
32
|
variant?: "primary" | "secondary" | "neutral" | "neutral-text" | "text" | "negative" | "negative-outlined" | "icon";
|
|
@@ -48,6 +51,12 @@ export interface ButtonProps {
|
|
|
48
51
|
form?: string;
|
|
49
52
|
autoFocus?: boolean;
|
|
50
53
|
tabIndex?: number;
|
|
54
|
+
/** Botão que navega: vira `<a>` com a mesma aparência. Desabilitado, volta a ser `<button>`. */
|
|
55
|
+
href?: string;
|
|
56
|
+
/** Destino do link. `_blank` ganha `rel="noopener noreferrer"`. */
|
|
57
|
+
target?: string;
|
|
58
|
+
/** Relação do link. Sobrescreve o padrão do `target`. */
|
|
59
|
+
rel?: string;
|
|
51
60
|
}
|
|
52
61
|
import { IconProp } from "../Icon/Icon";
|
|
53
62
|
declare function Button(props: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -63,15 +63,27 @@ function Button(props) {
|
|
|
63
63
|
function endIconIsIcon() {
|
|
64
64
|
return isIconValue(props.endIcon);
|
|
65
65
|
}
|
|
66
|
+
function isLink() {
|
|
67
|
+
return !!props.href && !props.disabled;
|
|
68
|
+
}
|
|
69
|
+
function relValue() {
|
|
70
|
+
if (props.rel)
|
|
71
|
+
return props.rel;
|
|
72
|
+
return props.target === "_blank" ? "noopener noreferrer" : undefined;
|
|
73
|
+
}
|
|
66
74
|
function handleClick(event) {
|
|
67
75
|
// Clique bloqueado durante o carregamento — evita dupla submissão
|
|
68
|
-
// sem mudar o visual (o botão não fica disabled).
|
|
69
|
-
|
|
76
|
+
// sem mudar o visual (o botão não fica disabled). No `<a>`, segura
|
|
77
|
+
// também a navegação.
|
|
78
|
+
if (props.loading) {
|
|
79
|
+
if (event && event.preventDefault)
|
|
80
|
+
event.preventDefault();
|
|
70
81
|
return;
|
|
82
|
+
}
|
|
71
83
|
if (props.onClick) {
|
|
72
84
|
props.onClick(event);
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
|
-
return (_jsxs("button", { type: props.type || "button", disabled: props.disabled, id: props.id, name: props.name, value: props.value, form: props.form, autoFocus: props.autoFocus || undefined, tabIndex: props.tabIndex, "aria-label": props.ariaLabel, "aria-busy": props.loading, onClick: (event) => handleClick(event), className: classes(), children: [showLoadingAtStart() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showStartIcon() ? (_jsx(_Fragment, { children: startIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.startIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.startIcon }) })) })) : null, showLoadingAtCenter() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showLoadingAtCenter() ? (_jsx("span", { className: "umds-button__visually-hidden", children: props.children })) : null, showChildren() ? _jsx(_Fragment, { children: props.children }) : null, showEndIcon() ? (_jsx(_Fragment, { children: endIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.endIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.endIcon }) })) })) : null, showLoadingAtEnd() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null] }));
|
|
87
|
+
return (_jsx(_Fragment, { children: isLink() ? (_jsx(_Fragment, { children: _jsxs("a", { href: props.href, target: props.target, rel: relValue(), id: props.id, tabIndex: props.tabIndex, "aria-label": props.ariaLabel, "aria-busy": props.loading, onClick: (event) => handleClick(event), className: classes(), children: [showLoadingAtStart() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showStartIcon() ? (_jsx(_Fragment, { children: startIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.startIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.startIcon }) })) })) : null, showLoadingAtCenter() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showLoadingAtCenter() ? (_jsx("span", { className: "umds-button__visually-hidden", children: props.children })) : null, showChildren() ? _jsx(_Fragment, { children: props.children }) : null, showEndIcon() ? (_jsx(_Fragment, { children: endIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.endIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.endIcon }) })) })) : null, showLoadingAtEnd() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null] }) })) : (_jsx(_Fragment, { children: _jsxs("button", { type: props.type || "button", disabled: props.disabled, id: props.id, name: props.name, value: props.value, form: props.form, autoFocus: props.autoFocus || undefined, tabIndex: props.tabIndex, "aria-label": props.ariaLabel, "aria-busy": props.loading, onClick: (event) => handleClick(event), className: classes(), children: [showLoadingAtStart() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showStartIcon() ? (_jsx(_Fragment, { children: startIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.startIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.startIcon }) })) })) : null, showLoadingAtCenter() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null, showLoadingAtCenter() ? (_jsx("span", { className: "umds-button__visually-hidden", children: props.children })) : null, showChildren() ? _jsx(_Fragment, { children: props.children }) : null, showEndIcon() ? (_jsx(_Fragment, { children: endIconIsIcon() ? (_jsx(Icon, { variant: "inherit", icon: props.endIcon, size: iconSize() })) : (_jsx(_Fragment, { children: _jsx(_Fragment, { children: props.endIcon }) })) })) : null, showLoadingAtEnd() ? (_jsx("span", { "aria-hidden": "true", className: "umds-button__loading", children: _jsx(Icon, { variant: "inherit", icon: props.loadingIcon || faRefresh, size: iconSize() }) })) : null] }) })) }));
|
|
76
88
|
}
|
|
77
89
|
export default Button;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────
|
|
2
|
+
COUNTER
|
|
3
|
+
Número que conta até o valor. Herda fonte, tamanho e cor de onde está.
|
|
4
|
+
A reserva (cópia invisível do valor final) e o número que corre ocupam a
|
|
5
|
+
mesma célula da grade: a largura é a do valor final desde o primeiro quadro.
|
|
6
|
+
───────────────────────────────────────────── */
|
|
7
|
+
|
|
8
|
+
.umds-counter {
|
|
9
|
+
/* Contém a cópia escondida (__leitura, absolute): sem isso ela escapa do
|
|
10
|
+
overflow de uma área que rola (o mesmo bug corrigido no Tooltip). */
|
|
11
|
+
position: relative;
|
|
12
|
+
display: inline-grid;
|
|
13
|
+
font-variant-numeric: tabular-nums;
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.umds-counter__reserva,
|
|
18
|
+
.umds-counter__numero {
|
|
19
|
+
grid-area: 1 / 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.umds-counter__reserva {
|
|
23
|
+
visibility: hidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.umds-counter__numero {
|
|
27
|
+
justify-self: end;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Valor final para o leitor de tela. Receita visually-hidden; os 1px não são
|
|
31
|
+
medida de design. */
|
|
32
|
+
.umds-counter__leitura {
|
|
33
|
+
position: absolute;
|
|
34
|
+
width: 1px;
|
|
35
|
+
height: 1px;
|
|
36
|
+
margin: -1px;
|
|
37
|
+
padding: 0;
|
|
38
|
+
border: 0;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
clip: rect(0, 0, 0, 0);
|
|
41
|
+
clip-path: inset(50%);
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import './Counter.css';
|
|
2
|
+
/**
|
|
3
|
+
* Counter — número que conta até o valor.
|
|
4
|
+
*
|
|
5
|
+
* Ao aparecer, o número sobe de `from` (0) até `value`; quando `value` muda,
|
|
6
|
+
* conta do valor atual até o novo. É o número do painel ("1.284 processos") e o
|
|
7
|
+
* total de páginas da caixa de entrada. Herda fonte, tamanho e cor de onde
|
|
8
|
+
* está, então serve dentro de um título, de um `StatCard` ou no meio do texto.
|
|
9
|
+
*
|
|
10
|
+
* Três decisões que valem saber:
|
|
11
|
+
*
|
|
12
|
+
* - **O leitor de tela lê só o valor final.** O número que corre é
|
|
13
|
+
* `aria-hidden`; uma cópia escondida com o valor final é o que se anuncia.
|
|
14
|
+
* Ninguém ouve "1, 37, 210, 1.284".
|
|
15
|
+
* - **A largura não pula.** Uma cópia invisível do valor final reserva o
|
|
16
|
+
* espaço, e os algarismos são tabulares: o texto em volta não dança enquanto
|
|
17
|
+
* o número conta.
|
|
18
|
+
* - **Movimento reduzido é respeitado.** Com `prefers-reduced-motion` (ou
|
|
19
|
+
* `disableAnimation`) o número aparece direto no valor final.
|
|
20
|
+
*
|
|
21
|
+
* O formato é o do `Intl.NumberFormat` em `locale` (pt-BR: "1.284", "22,31").
|
|
22
|
+
* As casas decimais seguem as do `value`, a menos que `decimals` diga outra
|
|
23
|
+
* coisa.
|
|
24
|
+
* @property {number} value - Valor final (número ou texto numérico)
|
|
25
|
+
* @property {number} from - De onde a contagem começa ao montar (padrão 0)
|
|
26
|
+
* @property {number} duration - Duração da contagem em ms (padrão 1000)
|
|
27
|
+
* @property {number} decimals - Casas decimais (padrão: as do `value`)
|
|
28
|
+
* @property {string} locale - Idioma do formato (padrão "pt-BR")
|
|
29
|
+
* @property {string} prefix - Texto antes do número ("R$ ")
|
|
30
|
+
* @property {string} suffix - Texto depois do número ("%", " páginas")
|
|
31
|
+
* @property {boolean} disableAnimation - Mostra o valor final direto, sem contar
|
|
32
|
+
* @property {string} className - Classes CSS adicionais
|
|
33
|
+
* @quandoUsar Número de destaque que merece atenção ao aparecer: KPI de painel, total de páginas ou de itens de uma caixa de entrada, contadores de resultado.
|
|
34
|
+
* @quandoNaoUsar Para contagem pequena presa a outro elemento (não lidos de uma aba), use `Badge`. Para número que muda o tempo todo (cronômetro, campo editável), mostre o texto direto: a animação a cada mudança cansa. Em tabela (`DataTable`), número não anima.
|
|
35
|
+
*/
|
|
36
|
+
export interface CounterProps {
|
|
37
|
+
/** Valor final. Aceita número ou texto numérico ("1284", "22.31"). */
|
|
38
|
+
value: number | string;
|
|
39
|
+
/** De onde a contagem começa ao montar. @default 0 */
|
|
40
|
+
from?: number;
|
|
41
|
+
/** Duração da contagem, em milissegundos. @default 1000 */
|
|
42
|
+
duration?: number;
|
|
43
|
+
/** Casas decimais. Sem a prop, as do `value`. */
|
|
44
|
+
decimals?: number;
|
|
45
|
+
/** Idioma do formato do número. @default 'pt-BR' */
|
|
46
|
+
locale?: string;
|
|
47
|
+
/** Texto antes do número ("R$ "). */
|
|
48
|
+
prefix?: string;
|
|
49
|
+
/** Texto depois do número ("%", " páginas"). */
|
|
50
|
+
suffix?: string;
|
|
51
|
+
/** Mostra o valor final direto, sem contar. @default false */
|
|
52
|
+
disableAnimation?: boolean;
|
|
53
|
+
/** Classes CSS adicionais. */
|
|
54
|
+
className?: string;
|
|
55
|
+
}
|
|
56
|
+
declare function Counter(props: CounterProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export default Counter;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import './Counter.css';
|
|
4
|
+
import { useState, useRef, useEffect } from "react";
|
|
5
|
+
function Counter(props) {
|
|
6
|
+
const quadroRef = useRef(null);
|
|
7
|
+
const ultimoRef = useRef(null);
|
|
8
|
+
const alvoRef = useRef(null);
|
|
9
|
+
const [exibido, setExibido] = useState(() => null);
|
|
10
|
+
function alvo() {
|
|
11
|
+
const numero = Number(props.value);
|
|
12
|
+
return isFinite(numero) ? numero : 0;
|
|
13
|
+
}
|
|
14
|
+
function casas() {
|
|
15
|
+
if (typeof props.decimals === "number")
|
|
16
|
+
return props.decimals;
|
|
17
|
+
const texto = String(props.value);
|
|
18
|
+
const ponto = texto.indexOf(".");
|
|
19
|
+
return ponto < 0 ? 0 : Math.min(texto.length - ponto - 1, 4);
|
|
20
|
+
}
|
|
21
|
+
function formatar(numero) {
|
|
22
|
+
const digitos = casas();
|
|
23
|
+
const formatado = new Intl.NumberFormat(props.locale || "pt-BR", {
|
|
24
|
+
minimumFractionDigits: digitos,
|
|
25
|
+
maximumFractionDigits: digitos,
|
|
26
|
+
}).format(numero);
|
|
27
|
+
return (props.prefix || "") + formatado + (props.suffix || "");
|
|
28
|
+
}
|
|
29
|
+
function semAnimacao() {
|
|
30
|
+
if (props.disableAnimation)
|
|
31
|
+
return true;
|
|
32
|
+
if (typeof window === "undefined" || !window.matchMedia)
|
|
33
|
+
return false;
|
|
34
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
35
|
+
}
|
|
36
|
+
function parar() {
|
|
37
|
+
if (quadroRef.current !== null && typeof window !== "undefined") {
|
|
38
|
+
window.cancelAnimationFrame(quadroRef.current);
|
|
39
|
+
}
|
|
40
|
+
quadroRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
function animar(de, para) {
|
|
43
|
+
parar();
|
|
44
|
+
const duracao = typeof props.duration === "number" ? props.duration : 1000;
|
|
45
|
+
if (semAnimacao() ||
|
|
46
|
+
duracao <= 0 ||
|
|
47
|
+
de === para ||
|
|
48
|
+
typeof window === "undefined") {
|
|
49
|
+
ultimoRef.current = para;
|
|
50
|
+
setExibido(para);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const inicio = performance.now();
|
|
54
|
+
const passo = (agora) => {
|
|
55
|
+
const progresso = Math.min(1, (agora - inicio) / duracao);
|
|
56
|
+
// ease-out cúbico: rápido no começo, assenta devagar no valor.
|
|
57
|
+
const suave = 1 - Math.pow(1 - progresso, 3);
|
|
58
|
+
const atual = progresso >= 1 ? para : de + (para - de) * suave;
|
|
59
|
+
ultimoRef.current = atual;
|
|
60
|
+
setExibido(atual);
|
|
61
|
+
quadroRef.current =
|
|
62
|
+
progresso < 1 ? window.requestAnimationFrame(passo) : null;
|
|
63
|
+
};
|
|
64
|
+
quadroRef.current = window.requestAnimationFrame(passo);
|
|
65
|
+
}
|
|
66
|
+
function textoNaTela() {
|
|
67
|
+
if (exibido !== null)
|
|
68
|
+
return formatar(exibido);
|
|
69
|
+
if (props.disableAnimation)
|
|
70
|
+
return formatar(alvo());
|
|
71
|
+
return formatar(typeof props.from === "number" ? props.from : 0);
|
|
72
|
+
}
|
|
73
|
+
function textoFinal() {
|
|
74
|
+
return formatar(alvo());
|
|
75
|
+
}
|
|
76
|
+
function classes() {
|
|
77
|
+
return ["umds-counter", props.className].filter(Boolean).join(" ");
|
|
78
|
+
}
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const para = alvo();
|
|
81
|
+
alvoRef.current = para;
|
|
82
|
+
animar(typeof props.from === "number" ? props.from : 0, para);
|
|
83
|
+
}, []);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const para = alvo();
|
|
86
|
+
// O React roda este efeito também na montagem: o onMount já cuidou dela.
|
|
87
|
+
if (ultimoRef.current === null || alvoRef.current === para)
|
|
88
|
+
return;
|
|
89
|
+
alvoRef.current = para;
|
|
90
|
+
animar(ultimoRef.current, para);
|
|
91
|
+
}, [props.value]);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
return () => {
|
|
94
|
+
parar();
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
return (_jsxs("span", { className: classes(), children: [_jsx("span", { className: "umds-counter__reserva", "aria-hidden": "true", children: textoFinal() }), _jsx("span", { className: "umds-counter__numero", "aria-hidden": "true", children: textoNaTela() }), _jsx("span", { className: "umds-counter__leitura", children: textoFinal() })] }));
|
|
98
|
+
}
|
|
99
|
+
export default Counter;
|
|
@@ -55,10 +55,12 @@
|
|
|
55
55
|
align-items: center;
|
|
56
56
|
gap: var(--umds-primitive-spacing-nano); /* 8px */
|
|
57
57
|
width: 100%;
|
|
58
|
+
box-sizing: border-box; /* o item com href é <a>, que nasce content-box */
|
|
58
59
|
padding: var(--umds-primitive-spacing-nano) var(--umds-primitive-spacing-xxxs); /* 8px 16px */
|
|
59
60
|
background: none;
|
|
60
61
|
border: none;
|
|
61
62
|
text-align: left;
|
|
63
|
+
text-decoration: none;
|
|
62
64
|
font-family: var(--umds-semantic-font-family-base);
|
|
63
65
|
font-size: var(--umds-primitive-font-size-xxs); /* 14px */
|
|
64
66
|
font-weight: 400;
|
|
@@ -7,6 +7,9 @@ import './Dropdown.css';
|
|
|
7
7
|
* @property {boolean} disabled - Desabilita o item (não selecionável).
|
|
8
8
|
* @property {boolean} danger - Aplica o estilo destrutivo (vermelho).
|
|
9
9
|
* @property {boolean} dividerAfter - Adiciona um divisor abaixo do item.
|
|
10
|
+
* @property {string} href - Item que navega: vira `<a>` de verdade (Ctrl+clique e "abrir em nova aba" funcionam). O `onSelect` continua sendo chamado.
|
|
11
|
+
* @property {string} target - Destino do link (`_blank` ganha `rel="noopener noreferrer"`).
|
|
12
|
+
* @property {string} rel - Relação do link; sobrescreve o padrão do `target`.
|
|
10
13
|
*/
|
|
11
14
|
export interface DropdownItem {
|
|
12
15
|
key: string;
|
|
@@ -15,6 +18,12 @@ export interface DropdownItem {
|
|
|
15
18
|
disabled?: boolean;
|
|
16
19
|
danger?: boolean;
|
|
17
20
|
dividerAfter?: boolean;
|
|
21
|
+
/** Item que navega: vira `<a>` (Ctrl+clique e nova aba funcionam). Desabilitado, volta a ser botão. */
|
|
22
|
+
href?: string;
|
|
23
|
+
/** Destino do link. `_blank` ganha `rel="noopener noreferrer"`. */
|
|
24
|
+
target?: string;
|
|
25
|
+
/** Relação do link. Sobrescreve o padrão do `target`. */
|
|
26
|
+
rel?: string;
|
|
18
27
|
}
|
|
19
28
|
/**
|
|
20
29
|
* Dropdown component — menu de ações ancorado a um trigger.
|
|
@@ -302,8 +302,21 @@ function Dropdown(props) {
|
|
|
302
302
|
activateIndex(nextEnabledIndex(total, -1));
|
|
303
303
|
}
|
|
304
304
|
else if (key === "Enter" || key === " ") {
|
|
305
|
-
event.preventDefault();
|
|
306
305
|
const item = itemList()[focusedIndex()];
|
|
306
|
+
// Item com href é <a>: o Enter fica com o navegador, que dispara o
|
|
307
|
+
// clique do link (e respeita Ctrl/Shift para nova aba/janela). O
|
|
308
|
+
// onClick do <a> chama o handleSelect. Espaço não ativa link no
|
|
309
|
+
// navegador, então vira um clique de verdade.
|
|
310
|
+
if (item && item.href && !item.disabled) {
|
|
311
|
+
if (key === " ") {
|
|
312
|
+
event.preventDefault();
|
|
313
|
+
const alvo = event.target;
|
|
314
|
+
if (alvo && alvo.click)
|
|
315
|
+
alvo.click();
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
event.preventDefault();
|
|
307
320
|
if (item)
|
|
308
321
|
handleSelect(item);
|
|
309
322
|
}
|
|
@@ -312,6 +325,11 @@ function Dropdown(props) {
|
|
|
312
325
|
close();
|
|
313
326
|
}
|
|
314
327
|
}
|
|
328
|
+
function relDoItem(item) {
|
|
329
|
+
if (item.rel)
|
|
330
|
+
return item.rel;
|
|
331
|
+
return item.target === "_blank" ? "noopener noreferrer" : undefined;
|
|
332
|
+
}
|
|
315
333
|
function itemClasses(item) {
|
|
316
334
|
return [
|
|
317
335
|
"umds-dropdown__item",
|
|
@@ -370,6 +388,6 @@ function Dropdown(props) {
|
|
|
370
388
|
}, []);
|
|
371
389
|
return (_jsxs("span", { ref: triggerRef, className: triggerClasses(), style: {
|
|
372
390
|
display: "inline-flex",
|
|
373
|
-
}, children: [_jsx("span", { className: "umds-dropdown__trigger", ref: triggerButtonRef, role: triggerIsInteractive ? undefined : "button", tabIndex: triggerIsInteractive ? undefined : 0, "aria-haspopup": triggerIsInteractive ? undefined : "menu", "aria-expanded": triggerIsInteractive ? undefined : ariaExpandedText(), onClick: (event) => toggle(), onKeyDown: (event) => handleTriggerKeydown(event), children: _jsx(_Fragment, { children: props.children }) }), _jsx(Portal, { children: mounted ? (_jsx("span", { className: "umds-dropdown__floating", ref: floatingRef, style: floatingStyle(), children: _jsx("ul", { className: "umds-dropdown__menu", role: "menu", ref: menuRef, "aria-label": props.ariaLabel, tabIndex: -1, style: menuStyle(), onKeyDown: (event) => handleMenuKeydown(event), children: itemList()?.map((item, index) => (_jsx("li", { role: "none", children: _jsxs("button", { type: "button", role: "menuitem", className: itemClasses(item), tabIndex: index === activeIndex ? 0 : -1, "data-active": index === activeIndex, disabled: item.disabled, onClick: (event) => handleSelect(item), children: [item.icon ? (_jsx("span", { className: "umds-dropdown__item-icon", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: item.icon }) })) : null, _jsx("span", { className: "umds-dropdown__item-label", children: item.label })] }) }, item.key))) }) })) : null })] }));
|
|
391
|
+
}, children: [_jsx("span", { className: "umds-dropdown__trigger", ref: triggerButtonRef, role: triggerIsInteractive ? undefined : "button", tabIndex: triggerIsInteractive ? undefined : 0, "aria-haspopup": triggerIsInteractive ? undefined : "menu", "aria-expanded": triggerIsInteractive ? undefined : ariaExpandedText(), onClick: (event) => toggle(), onKeyDown: (event) => handleTriggerKeydown(event), children: _jsx(_Fragment, { children: props.children }) }), _jsx(Portal, { children: mounted ? (_jsx("span", { className: "umds-dropdown__floating", ref: floatingRef, style: floatingStyle(), children: _jsx("ul", { className: "umds-dropdown__menu", role: "menu", ref: menuRef, "aria-label": props.ariaLabel, tabIndex: -1, style: menuStyle(), onKeyDown: (event) => handleMenuKeydown(event), children: itemList()?.map((item, index) => (_jsx("li", { role: "none", children: item.href && !item.disabled ? (_jsxs("a", { role: "menuitem", href: item.href, target: item.target, rel: relDoItem(item), className: itemClasses(item), tabIndex: index === activeIndex ? 0 : -1, "data-active": index === activeIndex, onClick: (event) => handleSelect(item), children: [item.icon ? (_jsx("span", { className: "umds-dropdown__item-icon", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: item.icon }) })) : null, _jsx("span", { className: "umds-dropdown__item-label", children: item.label })] })) : (_jsx(_Fragment, { children: _jsxs("button", { type: "button", role: "menuitem", className: itemClasses(item), tabIndex: index === activeIndex ? 0 : -1, "data-active": index === activeIndex, disabled: item.disabled, onClick: (event) => handleSelect(item), children: [item.icon ? (_jsx("span", { className: "umds-dropdown__item-icon", "aria-hidden": "true", children: _jsx(Icon, { size: "sm", variant: "inherit", icon: item.icon }) })) : null, _jsx("span", { className: "umds-dropdown__item-label", children: item.label })] }) })) }, item.key))) }) })) : null })] }));
|
|
374
392
|
}
|
|
375
393
|
export default Dropdown;
|
|
@@ -134,3 +134,50 @@ ol.umds-list {
|
|
|
134
134
|
display: block;
|
|
135
135
|
color: var(--umds-semantic-color-neutral-dark-down);
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
/* ── Ações da linha (slot `actions`) ──
|
|
139
|
+
O controle fica no `li`, AO LADO do elemento clicável: clicar no ⋮ não
|
|
140
|
+
aciona a linha, e não há botão dentro de link. O `li` vira a linha visual
|
|
141
|
+
(flex), e o hover e a seleção passam do elemento para o `li`, para o fundo
|
|
142
|
+
cobrir as ações também. */
|
|
143
|
+
.umds-list__item:has(> .umds-list-item__actions) {
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: var(--umds-primitive-spacing-quarck); /* 4px */
|
|
147
|
+
padding-right: var(--umds-primitive-spacing-nano); /* 8px */
|
|
148
|
+
border-radius: var(--umds-primitive-border-radius-sm); /* 8px */
|
|
149
|
+
transition: background-color var(--umds-semantic-motion-duration-fast) var(--umds-semantic-motion-easing-standard);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.umds-list__item:has(> .umds-list-item__actions) > .umds-list-item {
|
|
153
|
+
flex: 1 1 auto;
|
|
154
|
+
width: auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.umds-list__item:has(> .umds-list-item__actions) > .umds-list-item,
|
|
158
|
+
.umds-list__item:has(> .umds-list-item__actions) > .umds-list-item.umds-list-item--interactive:hover {
|
|
159
|
+
background: transparent;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.umds-list__item:has(> .umds-list-item--interactive:not(.umds-list-item--disabled)):has(> .umds-list-item__actions):hover {
|
|
163
|
+
background: var(--umds-semantic-color-background-light);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.umds-list__item:has(> .umds-list-item--selected):has(> .umds-list-item__actions) {
|
|
167
|
+
background: var(--umds-semantic-color-primary-light);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.umds-list__item:has(> .umds-list-item--selected.umds-list-item--interactive):has(> .umds-list-item__actions):hover {
|
|
171
|
+
background: color-mix(
|
|
172
|
+
in srgb,
|
|
173
|
+
var(--umds-semantic-color-primary-light) 85%,
|
|
174
|
+
var(--umds-semantic-color-background-white)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.umds-list-item__actions {
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
flex-shrink: 0;
|
|
181
|
+
align-items: center;
|
|
182
|
+
gap: var(--umds-primitive-spacing-quarck); /* 4px */
|
|
183
|
+
}
|
|
@@ -27,6 +27,12 @@ export type ListItemTag = "button" | "a" | "div";
|
|
|
27
27
|
* HTML: é o título da linha. O atributo `title` (a dica nativa do navegador) é a
|
|
28
28
|
* prop `tooltip`.
|
|
29
29
|
*
|
|
30
|
+
* `actions` é o lugar de controles da linha (o ⋮ de um menu, um botão de
|
|
31
|
+
* excluir): fica no `li`, AO LADO do elemento clicável e não dentro dele, então
|
|
32
|
+
* o clique no controle não aciona a linha e o HTML continua válido. A linha toda
|
|
33
|
+
* (com as ações) ganha o fundo de hover e de seleção. Só existe com o `li`
|
|
34
|
+
* (sem `standalone`).
|
|
35
|
+
*
|
|
30
36
|
* `standalone` tira o `li`: o ListItem vira só o elemento interativo, para uso
|
|
31
37
|
* fora de um `List` (botão de número de página, item de carrossel, linha dentro
|
|
32
38
|
* de um `div`). Fora de `ul`/`ol`, o `li` é HTML inválido.
|
|
@@ -43,6 +49,7 @@ export type ListItemTag = "button" | "a" | "div";
|
|
|
43
49
|
* @property {any} meta - Terceira linha, menor (data, contexto): texto, ou slot `meta` (nó)
|
|
44
50
|
* @property {any} start - Slot `start`: conteúdo antes do texto (avatar, ícone)
|
|
45
51
|
* @property {any} end - Slot `end`: conteúdo no fim da linha (badge, seta, valor)
|
|
52
|
+
* @property {any} actions - Slot `actions`: controles da linha (menu ⋮, botão), fora do elemento clicável
|
|
46
53
|
* @property {function} onClick - Callback do clique (ou de Enter/Espaço no as="div")
|
|
47
54
|
* @property {string} className - Classes CSS adicionais, no elemento interativo
|
|
48
55
|
* @slot start - Conteúdo antes do texto.
|
|
@@ -50,6 +57,7 @@ export type ListItemTag = "button" | "a" | "div";
|
|
|
50
57
|
* @slot subtitle - Segunda linha (substitui a prop `subtitle`).
|
|
51
58
|
* @slot meta - Terceira linha (substitui a prop `meta`).
|
|
52
59
|
* @slot end - Conteúdo no fim da linha.
|
|
60
|
+
* @slot actions - Controles da linha, ao lado do elemento clicável (não dentro dele).
|
|
53
61
|
* @quandoUsar Itens de uma lista em que a linha toda é a ação: documentos não lidos, notificações, resultados de busca, opções de menu de filtro, itens de navegação secundária. Hover, foco visível e seleção já vêm resolvidos.
|
|
54
62
|
* @quandoNaoUsar Para dados comparados coluna a coluna, use `DataTable`. Para um cartão com título, ações e elevação própria, use `Card`. Para um botão solto fora de lista, use `Button`. Para a navegação principal do produto, use `SidePanelItem` ou `AppNavItem`.
|
|
55
63
|
*/
|
|
@@ -81,6 +89,8 @@ export interface ListItemProps {
|
|
|
81
89
|
start?: any;
|
|
82
90
|
/** Conteúdo no fim da linha (badge, seta, valor). No Vue, slot `end`. */
|
|
83
91
|
end?: any;
|
|
92
|
+
/** Controles da linha (menu ⋮, botão), fora do elemento clicável. Só com o `li`. No Vue, slot `actions`. */
|
|
93
|
+
actions?: any;
|
|
84
94
|
/** Chamado no clique da linha (ou em Enter/Espaço no `as="div"`). */
|
|
85
95
|
onClick?: (event: any) => void;
|
|
86
96
|
/** Classes CSS adicionais, no elemento interativo. */
|