@1doc/1ds-react 2.13.0 → 2.15.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 +62 -59
- package/dist/components/AILabel/AILabel.css +1 -1
- package/dist/components/Alert/Alert.css +1 -1
- package/dist/components/AppShell/AppShell.css +4 -0
- package/dist/components/AttachmentCard/AttachmentCard.css +2 -2
- package/dist/components/Avatar/Avatar.d.ts +8 -3
- package/dist/components/Avatar/Avatar.js +28 -4
- package/dist/components/BottomNavigation/BottomNavigation.css +2 -2
- package/dist/components/BottomSheet/BottomSheet.css +2 -2
- package/dist/components/Box/Box.css +3 -3
- 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/Card/Card.css +5 -5
- package/dist/components/Chart/Chart.css +1 -1
- package/dist/components/Coachmark/Coachmark.css +1 -1
- package/dist/components/Combobox/Combobox.css +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.css +1 -1
- 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/DatePicker/DatePicker.css +1 -1
- package/dist/components/Drawer/Drawer.css +2 -2
- package/dist/components/Dropdown/Dropdown.css +3 -1
- package/dist/components/Dropdown/Dropdown.d.ts +17 -0
- package/dist/components/Dropdown/Dropdown.js +22 -2
- package/dist/components/EmptyState/EmptyState.css +12 -0
- package/dist/components/EmptyState/EmptyState.d.ts +3 -0
- package/dist/components/EmptyState/EmptyState.js +1 -0
- package/dist/components/Kanban/Kanban.css +2 -2
- package/dist/components/Kanban/KanbanCard.css +2 -2
- package/dist/components/List/List.css +83 -0
- package/dist/components/List/List.d.ts +3 -0
- package/dist/components/List/List.js +6 -2
- package/dist/components/List/ListItem.d.ts +19 -0
- package/dist/components/List/ListItem.js +5 -1
- package/dist/components/Modal/Modal.css +3 -4
- package/dist/components/MultiSelect/MultiSelect.css +1 -1
- package/dist/components/OrgChart/OrgChart.css +2 -2
- package/dist/components/PhoneInput/PhoneInput.css +2 -2
- package/dist/components/Popover/Popover.css +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.css +1 -1
- package/dist/components/Select/Select.css +1 -1
- package/dist/components/StatCard/StatCard.css +99 -5
- package/dist/components/StatCard/StatCard.d.ts +12 -6
- package/dist/components/StatCard/StatCard.js +6 -2
- package/dist/components/Switch/Switch.css +2 -2
- package/dist/components/Text/Text.d.ts +6 -0
- package/dist/components/TimePicker/TimePicker.css +1 -1
- package/dist/components/Toast/Toast.css +2 -2
- package/dist/components/Tooltip/Tooltip.css +23 -3
- package/dist/components/Tooltip/Tooltip.js +33 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/teste/PedidosMatheus.test.d.ts +1 -0
- package/dist/teste/PedidosMatheus.test.js +91 -0
- package/dist/teste/PedidosMatheus2.test.d.ts +1 -0
- package/dist/teste/PedidosMatheus2.test.js +82 -0
- package/dist/teste/SegmentedControlSuperficie.test.js +1 -1
- package/dist/teste/StatCardTamanhoSm.test.js +8 -0
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/teste/PedidosMatheus.test.tsx +129 -0
- package/src/teste/PedidosMatheus2.test.tsx +102 -0
- package/src/teste/SegmentedControlSuperficie.test.tsx +1 -1
- package/src/teste/StatCardTamanhoSm.test.tsx +9 -0
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
background: var(--umds-semantic-color-background-elevated); /* superfície elevada: igual ao white no light; mais clara no dark (decisão 25/07) */
|
|
124
124
|
border: 1px solid var(--umds-semantic-color-neutral-light-medium); /* #EBEBEB */
|
|
125
125
|
border-radius: var(--umds-primitive-border-radius-sm); /* 8px — protótipo --radius-lg 12px, aproximado para sm (coerente com input e dígitos) */
|
|
126
|
-
box-shadow: var(--umds-semantic-shadow-md);
|
|
126
|
+
box-shadow: var(--umds-semantic-shadow-bottom-md);
|
|
127
127
|
padding: var(--umds-primitive-spacing-xxxs); /* 16px — protótipo --space-4 */
|
|
128
128
|
box-sizing: border-box;
|
|
129
129
|
animation: umds-timepicker-in var(--umds-semantic-motion-duration-fast) var(--umds-semantic-motion-easing-standard) both;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Variantes semânticas (prop `variant`): positive, negative, info, neutral
|
|
5
5
|
(warning do protótipo mesclado no info — ambos laranja feedback-info).
|
|
6
6
|
Tokens do protótipo mapeados para os tokens 1DS.
|
|
7
|
-
Snap 16/07 (decisões novas): sombra → --umds-semantic-shadow-md (token novo,
|
|
7
|
+
Snap 16/07 (decisões novas): sombra → --umds-semantic-shadow-bottom-md (token novo,
|
|
8
8
|
valor idêntico ao literal do protótipo); radius 12px → md 16px (overlay
|
|
9
9
|
flutuante → md, como Dropdown/Modal); radius 6px → sm 8px; font-size
|
|
10
10
|
11px → xxxs 12px; spacing 12px → xxxs 16px (mesmas decisões do Alert —
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
padding: var(--umds-primitive-spacing-xxxs, 16px); /* era 12px 16px (protótipo --space-3/--space-4) — snap 16/07 */
|
|
25
25
|
border-radius: var(--umds-primitive-border-radius-sm); /* 8px universal (rodada ago/2026) — a exceção "overlay flutuante → md" caiu */
|
|
26
26
|
border: 1px solid transparent;
|
|
27
|
-
box-shadow: var(--umds-semantic-shadow-md); /* protótipo --shadow-md — token novo com valor idêntico */
|
|
27
|
+
box-shadow: var(--umds-semantic-shadow-bottom-md); /* protótipo --shadow-md — token novo com valor idêntico */
|
|
28
28
|
font-family: var(--umds-semantic-font-family-base);
|
|
29
29
|
box-sizing: border-box;
|
|
30
30
|
/* O Portal usa display: contents e não posiciona nada — o próprio toast
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
───────────────────────────────────────────── */
|
|
9
9
|
|
|
10
10
|
.umds-tooltip {
|
|
11
|
+
/* Bloco de contenção da cópia escondida (__descricao, absolute): sem ele,
|
|
12
|
+
ela escapa do overflow de uma área que rola e estica a página. */
|
|
13
|
+
position: relative;
|
|
11
14
|
display: inline-flex;
|
|
12
15
|
height: fit-content;
|
|
13
16
|
}
|
|
@@ -44,9 +47,12 @@
|
|
|
44
47
|
flex-direction: column;
|
|
45
48
|
position: relative;
|
|
46
49
|
padding: var(--umds-primitive-spacing-mini); /* protótipo --space-3: sem equivalente 1DS exato */
|
|
47
|
-
|
|
50
|
+
/* Superfície elevada, como o Card: igual ao branco nos temas claros e no
|
|
51
|
+
alto contraste; nos escuros é mais clara que a página (o neutral-light-pure
|
|
52
|
+
de antes era quase da cor do fundo, e o balão sumia). */
|
|
53
|
+
background-color: var(--umds-semantic-color-background-elevated);
|
|
48
54
|
border-radius: var(--umds-primitive-border-radius-sm); /* 8px */
|
|
49
|
-
box-shadow: var(--umds-
|
|
55
|
+
box-shadow: var(--umds-semantic-shadow-bottom-lg);
|
|
50
56
|
font-family: var(--umds-semantic-font-family-base);
|
|
51
57
|
font-size: var(--umds-primitive-font-size-xxxs); /* 12px */
|
|
52
58
|
line-height: 1.25;
|
|
@@ -90,7 +96,7 @@
|
|
|
90
96
|
position: absolute;
|
|
91
97
|
width: 8px;
|
|
92
98
|
height: 8px;
|
|
93
|
-
background-color: var(--umds-semantic-color-
|
|
99
|
+
background-color: var(--umds-semantic-color-background-elevated);
|
|
94
100
|
transform: rotate(45deg);
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -178,6 +184,20 @@
|
|
|
178
184
|
border-color: var(--umds-semantic-color-background-dark);
|
|
179
185
|
}
|
|
180
186
|
|
|
187
|
+
/* ── Temas escuros (1doc-dark e os *-dark da paleta) ──
|
|
188
|
+
A sombra preta não aparece sobre fundo escuro: o balão ganha o traço do Card
|
|
189
|
+
(neutral-light-medium), por dentro para não mudar o tamanho. A seta troca o
|
|
190
|
+
traço das duas faces expostas pelo mesmo tom e continua o contorno. O
|
|
191
|
+
variant="dark" (balão claro invertido) não precisa: ele contrasta sozinho. */
|
|
192
|
+
[data-theme$="-dark"] .umds-tooltip__bubble:not(.umds-tooltip__bubble--dark) {
|
|
193
|
+
outline: var(--umds-primitive-border-width-hairline) solid var(--umds-semantic-color-neutral-light-medium);
|
|
194
|
+
outline-offset: calc(-1 * var(--umds-primitive-border-width-hairline));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
[data-theme$="-dark"] .umds-tooltip__bubble:not(.umds-tooltip__bubble--dark) .umds-tooltip__arrow {
|
|
198
|
+
border-color: var(--umds-semantic-color-neutral-light-medium);
|
|
199
|
+
}
|
|
200
|
+
|
|
181
201
|
/* ── Alto contraste ──
|
|
182
202
|
O balão usa neutral-light-pure, que neste tema é preto como a página, e a
|
|
183
203
|
sombra não aparece: sem traço o balão some. Outline para dentro (não
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import './Tooltip.css';
|
|
4
4
|
import { useState, useRef, useEffect } from "react";
|
|
5
|
-
import { computePosition, autoUpdate, offset, flip, shift, } from "@floating-ui/dom";
|
|
5
|
+
import { computePosition, autoUpdate, offset, flip, shift, arrow, } from "@floating-ui/dom";
|
|
6
6
|
import Icon from "../Icon/Icon";
|
|
7
7
|
import Portal from "../Portal/Portal";
|
|
8
8
|
import { gerarId } from "../../utils/id";
|
|
@@ -28,6 +28,7 @@ function Tooltip(props) {
|
|
|
28
28
|
const [posY, setPosY] = useState(() => 0);
|
|
29
29
|
const [finalSide, setFinalSide] = useState(() => "top");
|
|
30
30
|
const [finalAlign, setFinalAlign] = useState(() => "center");
|
|
31
|
+
const [arrowOffset, setArrowOffset] = useState(() => -1);
|
|
31
32
|
const [bubbleId, setBubbleId] = useState(() => "");
|
|
32
33
|
const [descId, setDescId] = useState(() => "");
|
|
33
34
|
function bubbleIdText() {
|
|
@@ -92,6 +93,12 @@ function Tooltip(props) {
|
|
|
92
93
|
padding: 8,
|
|
93
94
|
}),
|
|
94
95
|
];
|
|
96
|
+
// A seta acompanha o gatilho quando o balão é empurrado para caber.
|
|
97
|
+
if (arrowRef.current)
|
|
98
|
+
middlewares.push(arrow({
|
|
99
|
+
element: arrowRef.current,
|
|
100
|
+
padding: 8,
|
|
101
|
+
}));
|
|
95
102
|
computePosition(triggerRef.current, floatingRef.current, {
|
|
96
103
|
placement,
|
|
97
104
|
strategy: "fixed",
|
|
@@ -105,6 +112,14 @@ function Tooltip(props) {
|
|
|
105
112
|
const parts = position.placement.split("-");
|
|
106
113
|
setFinalSide(parts[0]);
|
|
107
114
|
setFinalAlign(parts[1] || "center");
|
|
115
|
+
// Só quando o shift deslocou o balão a seta sai da posição estática
|
|
116
|
+
// (as 12 classes side×align): nos outros casos o visual não muda.
|
|
117
|
+
const deslocamento = position.middlewareData.shift;
|
|
118
|
+
const seta = position.middlewareData.arrow;
|
|
119
|
+
const empurrado = !!deslocamento && (deslocamento.x !== 0 || deslocamento.y !== 0);
|
|
120
|
+
const vertical = parts[0] === "left" || parts[0] === "right";
|
|
121
|
+
const eixo = seta ? (vertical ? seta.y : seta.x) : undefined;
|
|
122
|
+
setArrowOffset(empurrado && typeof eixo === "number" ? eixo : -1);
|
|
108
123
|
setPositioned(true);
|
|
109
124
|
});
|
|
110
125
|
}
|
|
@@ -273,6 +288,22 @@ function Tooltip(props) {
|
|
|
273
288
|
"umds-tooltip__arrow--align-" + finalAlign,
|
|
274
289
|
].join(" ");
|
|
275
290
|
}
|
|
291
|
+
function arrowStyle() {
|
|
292
|
+
if (arrowOffset < 0)
|
|
293
|
+
return undefined;
|
|
294
|
+
if (finalSide === "left" || finalSide === "right") {
|
|
295
|
+
return {
|
|
296
|
+
top: arrowOffset + "px",
|
|
297
|
+
bottom: "auto",
|
|
298
|
+
transform: "rotate(45deg)",
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
left: arrowOffset + "px",
|
|
303
|
+
right: "auto",
|
|
304
|
+
transform: "rotate(45deg)",
|
|
305
|
+
};
|
|
306
|
+
}
|
|
276
307
|
useEffect(() => {
|
|
277
308
|
if (!triggerRef.current)
|
|
278
309
|
return;
|
|
@@ -315,6 +346,6 @@ function Tooltip(props) {
|
|
|
315
346
|
stopKeydownWatch();
|
|
316
347
|
};
|
|
317
348
|
}, []);
|
|
318
|
-
return (_jsxs("span", { ref: triggerRef, className: ["umds-tooltip", props.className].filter(Boolean).join(" "), onMouseEnter: (event) => handleOpen(), onMouseLeave: (event) => handleClose(event), children: [_jsx(_Fragment, { children: props.children }), _jsxs("span", { className: "umds-tooltip__descricao", "aria-hidden": "true", id: descIdText(), children: [props.title ? _jsx(_Fragment, { children: tituloDescrito() }) : null, props.content ? _jsx(_Fragment, { children: props.content }) : null] }), _jsx(Portal, { children: mounted ? (_jsx("span", { ref: floatingRef, className: floatingClasses(), style: floatingStyle(), children: _jsxs("span", { role: "tooltip", className: bubbleClasses(), id: bubbleIdText(), "aria-hidden": !open, children: [_jsxs("span", { className: "umds-tooltip__row", children: [props.icon ? (_jsx(Icon, { size: "sm", variant: "inherit", icon: props.icon })) : null, _jsxs("span", { className: "umds-tooltip__text", children: [props.title ? (_jsx("span", { className: "umds-tooltip__title", children: props.title })) : null, props.content ? (_jsx("span", { className: "umds-tooltip__content", children: _jsx(_Fragment, { children: props.content }) })) : null] })] }), _jsx("span", { ref: arrowRef, className: arrowClasses() })] }) })) : null })] }));
|
|
349
|
+
return (_jsxs("span", { ref: triggerRef, className: ["umds-tooltip", props.className].filter(Boolean).join(" "), onMouseEnter: (event) => handleOpen(), onMouseLeave: (event) => handleClose(event), children: [_jsx(_Fragment, { children: props.children }), _jsxs("span", { className: "umds-tooltip__descricao", "aria-hidden": "true", id: descIdText(), children: [props.title ? _jsx(_Fragment, { children: tituloDescrito() }) : null, props.content ? _jsx(_Fragment, { children: props.content }) : null] }), _jsx(Portal, { children: mounted ? (_jsx("span", { ref: floatingRef, className: floatingClasses(), style: floatingStyle(), children: _jsxs("span", { role: "tooltip", className: bubbleClasses(), id: bubbleIdText(), "aria-hidden": !open, children: [_jsxs("span", { className: "umds-tooltip__row", children: [props.icon ? (_jsx(Icon, { size: "sm", variant: "inherit", icon: props.icon })) : null, _jsxs("span", { className: "umds-tooltip__text", children: [props.title ? (_jsx("span", { className: "umds-tooltip__title", children: props.title })) : null, props.content ? (_jsx("span", { className: "umds-tooltip__content", children: _jsx(_Fragment, { children: props.content }) })) : null] })] }), _jsx("span", { ref: arrowRef, className: arrowClasses(), style: arrowStyle() })] }) })) : null })] }));
|
|
319
350
|
}
|
|
320
351
|
export default Tooltip;
|
package/dist/index.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ export { default as Input } from "./components/Input/Input";
|
|
|
50
50
|
export { default as Kanban } from "./components/Kanban/Kanban";
|
|
51
51
|
export { default as KanbanCard } from "./components/Kanban/KanbanCard";
|
|
52
52
|
export { default as KanbanColumn } from "./components/Kanban/KanbanColumn";
|
|
53
|
+
export { default as Counter } from "./components/Counter/Counter";
|
|
54
|
+
export type * from "./components/Counter/Counter";
|
|
53
55
|
export { default as Kbd } from "./components/Kbd/Kbd";
|
|
54
56
|
export { default as Link } from "./components/Link/Link";
|
|
55
57
|
export { default as LineChart } from "./components/Chart/LineChart";
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,7 @@ export { default as Input } from "./components/Input/Input";
|
|
|
56
56
|
export { default as Kanban } from "./components/Kanban/Kanban";
|
|
57
57
|
export { default as KanbanCard } from "./components/Kanban/KanbanCard";
|
|
58
58
|
export { default as KanbanColumn } from "./components/Kanban/KanbanColumn";
|
|
59
|
+
export { default as Counter } from "./components/Counter/Counter";
|
|
59
60
|
export { default as Kbd } from "./components/Kbd/Kbd";
|
|
60
61
|
export { default as Link } from "./components/Link/Link";
|
|
61
62
|
export { default as LineChart } from "./components/Chart/LineChart";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import Button from "../components/Button/Button";
|
|
5
|
+
import Counter from "../components/Counter/Counter";
|
|
6
|
+
import Dropdown from "../components/Dropdown/Dropdown";
|
|
7
|
+
import ListItem from "../components/List/ListItem";
|
|
8
|
+
import StatCard from "../components/StatCard/StatCard";
|
|
9
|
+
describe("Counter", () => {
|
|
10
|
+
it("sem animação mostra o valor final formatado em pt-BR", () => {
|
|
11
|
+
const { container } = render(_jsx(Counter, { value: 1284, disableAnimation: true }));
|
|
12
|
+
expect(container.querySelector(".umds-counter__numero")).toHaveTextContent("1.284");
|
|
13
|
+
expect(container.querySelector(".umds-counter__numero")).toHaveAttribute("aria-hidden", "true");
|
|
14
|
+
expect(container.querySelector(".umds-counter__leitura")).toHaveTextContent("1.284");
|
|
15
|
+
});
|
|
16
|
+
it("casas decimais seguem o value; prefixo e sufixo entram no texto", () => {
|
|
17
|
+
const { container } = render(_jsxs(_Fragment, { children: [_jsx(Counter, { value: 22.31, suffix: "%", disableAnimation: true }), _jsx(Counter, { value: 15890.5, prefix: "R$ ", decimals: 2, disableAnimation: true })] }));
|
|
18
|
+
const leituras = container.querySelectorAll(".umds-counter__leitura");
|
|
19
|
+
expect(leituras[0]).toHaveTextContent("22,31%");
|
|
20
|
+
expect(leituras[1].textContent).toBe("R$ 15.890,50");
|
|
21
|
+
});
|
|
22
|
+
it("conta do from até o valor e termina no valor exato", async () => {
|
|
23
|
+
vi.useFakeTimers();
|
|
24
|
+
const { container } = render(_jsx(Counter, { value: 500, duration: 100 }));
|
|
25
|
+
const numero = () => container.querySelector(".umds-counter__numero").textContent;
|
|
26
|
+
expect(numero()).toBe("0");
|
|
27
|
+
await act(async () => {
|
|
28
|
+
vi.advanceTimersByTime(500);
|
|
29
|
+
});
|
|
30
|
+
expect(numero()).toBe("500");
|
|
31
|
+
// A reserva e a leitura já nascem com o valor final.
|
|
32
|
+
expect(container.querySelector(".umds-counter__leitura")).toHaveTextContent("500");
|
|
33
|
+
vi.useRealTimers();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe("ListItem · actions", () => {
|
|
37
|
+
it("as ações ficam no li, fora do elemento clicável", () => {
|
|
38
|
+
const { container } = render(_jsx("ul", { children: _jsx(ListItem, { href: "#doc", title: "Memorando", actions: _jsx("button", { type: "button", children: "Mais" }) }) }));
|
|
39
|
+
const acao = screen.getByRole("button", { name: "Mais" });
|
|
40
|
+
expect(acao.closest("a")).toBeNull();
|
|
41
|
+
expect(acao.parentElement).toHaveClass("umds-list-item__actions");
|
|
42
|
+
expect(container.querySelector("li > a")).toHaveAttribute("href", "#doc");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("Button · href", () => {
|
|
46
|
+
it("com href é um link; desabilitado volta a ser botão", () => {
|
|
47
|
+
render(_jsxs(_Fragment, { children: [_jsx(Button, { href: "#inbox", children: "Inbox" }), _jsx(Button, { href: "#cal", target: "_blank", children: "Calend\u00E1rio" }), _jsx(Button, { href: "#fila", disabled: true, children: "Fila" })] }));
|
|
48
|
+
expect(screen.getByRole("link", { name: "Inbox" })).toHaveAttribute("href", "#inbox");
|
|
49
|
+
expect(screen.getByRole("link", { name: "Calendário" })).toHaveAttribute("rel", "noopener noreferrer");
|
|
50
|
+
expect(screen.getByRole("button", { name: "Fila" })).toBeDisabled();
|
|
51
|
+
});
|
|
52
|
+
it("loading segura a navegação do link", () => {
|
|
53
|
+
render(_jsx(Button, { href: "#inbox", loading: true, loadingPosition: "start", children: "Inbox" }));
|
|
54
|
+
const link = screen.getByRole("link", { name: "Inbox" });
|
|
55
|
+
const evento = new MouseEvent("click", { bubbles: true, cancelable: true });
|
|
56
|
+
link.dispatchEvent(evento);
|
|
57
|
+
expect(evento.defaultPrevented).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("Dropdown · href", () => {
|
|
61
|
+
it("item com href vira <a role=menuitem>, e o onSelect continua", async () => {
|
|
62
|
+
const onSelect = vi.fn();
|
|
63
|
+
render(_jsx(Dropdown, { ariaLabel: "A\u00E7\u00F5es", onSelect: onSelect, items: [
|
|
64
|
+
{ key: "assinar", label: "Assinar", href: "#assinar" },
|
|
65
|
+
{ key: "arquivar", label: "Arquivar" },
|
|
66
|
+
], children: _jsx("button", { type: "button", children: "Abrir" }) }));
|
|
67
|
+
fireEvent.click(screen.getByRole("button", { name: "Abrir" }));
|
|
68
|
+
const assinar = await screen.findByRole("menuitem", { name: "Assinar" });
|
|
69
|
+
expect(assinar.tagName).toBe("A");
|
|
70
|
+
expect(assinar).toHaveAttribute("href", "#assinar");
|
|
71
|
+
expect(screen.getByRole("menuitem", { name: "Arquivar" }).tagName).toBe("BUTTON");
|
|
72
|
+
fireEvent.click(assinar);
|
|
73
|
+
expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ key: "assinar" }));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("StatCard · loading e secondaryTone", () => {
|
|
77
|
+
it("loading troca os números por Skeleton e marca aria-busy", () => {
|
|
78
|
+
const { container } = render(_jsx(StatCard, { value: "22,31%", label: "Qualidade", secondaryValue: "14,11%", trendLabel: "+2%", trend: "up", loading: true }));
|
|
79
|
+
const cartao = container.querySelector(".umds-stat-card");
|
|
80
|
+
expect(cartao).toHaveAttribute("aria-busy", "true");
|
|
81
|
+
expect(cartao.textContent).not.toContain("22,31%");
|
|
82
|
+
expect(cartao.textContent).not.toContain("14,11%");
|
|
83
|
+
expect(cartao.textContent).not.toContain("+2%");
|
|
84
|
+
expect(cartao.textContent).toContain("Qualidade");
|
|
85
|
+
});
|
|
86
|
+
it("secondaryTone escreve a classe; default não", () => {
|
|
87
|
+
const { container } = render(_jsxs(_Fragment, { children: [_jsx(StatCard, { value: "1", secondaryValue: "2", secondaryTone: "positive" }), _jsx(StatCard, { value: "1", secondaryValue: "2", secondaryTone: "default" })] }));
|
|
88
|
+
expect(container.children[0]).toHaveClass("umds-stat-card--secundario-positive");
|
|
89
|
+
expect(container.children[1].className).not.toContain("secundario-");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import Avatar from "../components/Avatar/Avatar";
|
|
5
|
+
import Dropdown from "../components/Dropdown/Dropdown";
|
|
6
|
+
import EmptyState from "../components/EmptyState/EmptyState";
|
|
7
|
+
import List from "../components/List/List";
|
|
8
|
+
import ListItem from "../components/List/ListItem";
|
|
9
|
+
import { lerArquivo } from "./lerArquivo";
|
|
10
|
+
describe("Avatar · fallback e name", () => {
|
|
11
|
+
it("a foto que falha volta para as iniciais do name", () => {
|
|
12
|
+
const onError = vi.fn();
|
|
13
|
+
const { container } = render(_jsx(Avatar, { src: "/quebrada.png", name: "Cris Lima", onError: onError }));
|
|
14
|
+
fireEvent.error(container.querySelector("img"));
|
|
15
|
+
expect(container.querySelector("img")).toBeNull();
|
|
16
|
+
expect(container.textContent).toBe("CL");
|
|
17
|
+
expect(container.firstElementChild).toHaveClass("umds-avatar--letter");
|
|
18
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
19
|
+
});
|
|
20
|
+
it("sem name, a falha mostra o children", () => {
|
|
21
|
+
const { container } = render(_jsx(Avatar, { src: "/quebrada.png", children: "ML" }));
|
|
22
|
+
fireEvent.error(container.querySelector("img"));
|
|
23
|
+
expect(container.textContent).toBe("ML");
|
|
24
|
+
});
|
|
25
|
+
it("um src novo tenta a foto de novo", () => {
|
|
26
|
+
const { container, rerender } = render(_jsx(Avatar, { src: "/quebrada.png", name: "Cris Lima" }));
|
|
27
|
+
fireEvent.error(container.querySelector("img"));
|
|
28
|
+
rerender(_jsx(Avatar, { src: "/boa.png", name: "Cris Lima" }));
|
|
29
|
+
expect(container.querySelector("img")).toHaveAttribute("src", "/boa.png");
|
|
30
|
+
});
|
|
31
|
+
it("name gera as iniciais (primeiro e último nome) e o nome acessível", () => {
|
|
32
|
+
const { container } = render(_jsx(Avatar, { name: "Matheus de Souza" }));
|
|
33
|
+
expect(container.textContent).toBe("MS");
|
|
34
|
+
expect(container.firstElementChild).toHaveAttribute("aria-label", "Matheus de Souza");
|
|
35
|
+
expect(container.firstElementChild).toHaveAttribute("role", "img");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("Dropdown · onSelect no item", () => {
|
|
39
|
+
it("chama a ação do item com os campos próprios, e depois o onSelect do Dropdown", async () => {
|
|
40
|
+
const ordem = [];
|
|
41
|
+
const doItem = vi.fn((item) => ordem.push("item:" + item.command));
|
|
42
|
+
const doMenu = vi.fn(() => ordem.push("menu"));
|
|
43
|
+
render(_jsx(Dropdown, { ariaLabel: "A\u00E7\u00F5es", onSelect: doMenu, items: [{ key: "a", label: "Assinar", command: "assinar", onSelect: doItem }], children: _jsx("button", { type: "button", children: "Abrir" }) }));
|
|
44
|
+
fireEvent.click(screen.getByRole("button", { name: "Abrir" }));
|
|
45
|
+
fireEvent.click(await screen.findByRole("menuitem", { name: "Assinar" }));
|
|
46
|
+
expect(ordem).toEqual(["item:assinar", "menu"]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("ListItem · variant e li", () => {
|
|
50
|
+
it("variant primary marca a classe sem aria-current", () => {
|
|
51
|
+
render(_jsx("ul", { children: _jsx(ListItem, { variant: "primary", href: "#c", title: "Circular" }) }));
|
|
52
|
+
const link = screen.getByRole("link", { name: "Circular" });
|
|
53
|
+
expect(link).toHaveClass("umds-list-item--primary");
|
|
54
|
+
expect(link).not.toHaveAttribute("aria-current");
|
|
55
|
+
});
|
|
56
|
+
it("liClassName e liStyle vão para o li", () => {
|
|
57
|
+
const { container } = render(_jsx("ul", { children: _jsx(ListItem, { title: "Memorando", liClassName: "entra", liStyle: { "--ordem": 2 }, onClick: () => { } }) }));
|
|
58
|
+
const li = container.querySelector("li");
|
|
59
|
+
expect(li).toHaveClass("umds-list__item");
|
|
60
|
+
expect(li).toHaveClass("entra");
|
|
61
|
+
expect(li.style.getPropertyValue("--ordem")).toBe("2");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("EmptyState · fill e List · emptyFill", () => {
|
|
65
|
+
it("fill escreve a classe; List repassa emptyFill", () => {
|
|
66
|
+
const { container } = render(_jsxs(_Fragment, { children: [_jsx(EmptyState, { title: "Vazio", fill: true }), _jsx(List, { ariaLabel: "Docs", isEmpty: true, emptyFill: true })] }));
|
|
67
|
+
expect(container.querySelector(".umds-empty-state--fill")).not.toBeNull();
|
|
68
|
+
expect(container.querySelector(".umds-list--empty-fill .umds-empty-state--fill")).not.toBeNull();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("AppShell e StatCard · CSS", () => {
|
|
72
|
+
it("o AppShell dá a cor de texto do tema a quem herda", async () => {
|
|
73
|
+
const css = (await lerArquivo("../core/src/components/AppShell/AppShell.css")).replace(/\r\n/g, "\n");
|
|
74
|
+
const bloco = css.slice(css.indexOf(".umds-app-shell {"), css.indexOf("}", css.indexOf(".umds-app-shell {")));
|
|
75
|
+
expect(bloco).toContain("color: var(--umds-semantic-color-neutral-dark-pure)");
|
|
76
|
+
});
|
|
77
|
+
it("a StatCard com segunda medida empilha em largura pequena", async () => {
|
|
78
|
+
const css = (await lerArquivo("../core/src/components/StatCard/StatCard.css")).replace(/\r\n/g, "\n");
|
|
79
|
+
expect(css).toContain("container-type: inline-size");
|
|
80
|
+
expect(css).toMatch(/@container \(max-width: 240px\)/);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -27,7 +27,7 @@ describe("SegmentedControl · variant surface", () => {
|
|
|
27
27
|
const escolhido = bloco(t, ".umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item,\n.umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item:hover");
|
|
28
28
|
expect(escolhido).toMatch(/background-color:\s*var\(--umds-semantic-color-background-white\)/);
|
|
29
29
|
expect(escolhido).toMatch(/font-weight:\s*var\(--umds-primitive-font-weight-bold\)/);
|
|
30
|
-
expect(escolhido).toMatch(/box-shadow:\s*var\(--umds-semantic-shadow-md\)/);
|
|
30
|
+
expect(escolhido).toMatch(/box-shadow:\s*var\(--umds-semantic-shadow-bottom-md\)/);
|
|
31
31
|
expect(bloco(t, ".umds-segmented--surface .umds-segmented__track")).toMatch(/background-color:\s*var\(--umds-semantic-color-neutral-light-medium\)/);
|
|
32
32
|
expect(t).toMatch(/\.umds-segmented__input:checked \+ \.umds-segmented__item,\n\.umds-segmented__input:checked \+ \.umds-segmented__item:hover \{\n\s*background-color:\s*var\(--umds-semantic-color-primary-pure\)/);
|
|
33
33
|
});
|
|
@@ -23,6 +23,14 @@ describe("StatCard · size", () => {
|
|
|
23
23
|
const { container } = render(_jsx(StatCard, { value: 44, label: "N\u00E3o lidos", size: "sm", labelPosition: "top" }));
|
|
24
24
|
expect(container.firstElementChild).toHaveClass("umds-stat-card--sm", "umds-stat-card--rotulo-topo");
|
|
25
25
|
});
|
|
26
|
+
it("a segunda medida tem o mesmo tamanho e peso do número, no sm e no rótulo no topo", () => {
|
|
27
|
+
const sm = bloco(".umds-stat-card.umds-stat-card--sm .umds-stat-card__valor-secundario");
|
|
28
|
+
expect(sm).toMatch(/font-size:\s*1\.125rem/);
|
|
29
|
+
expect(sm).toMatch(/font-weight:\s*var\(--umds-primitive-font-weight-extrabold\)/);
|
|
30
|
+
const topo = bloco(".umds-stat-card--rotulo-topo .umds-stat-card__valor-secundario");
|
|
31
|
+
expect(topo).toMatch(/font-size:\s*var\(--umds-primitive-font-size-lg\)/);
|
|
32
|
+
expect(topo).toMatch(/font-weight:\s*var\(--umds-primitive-font-weight-extrabold\)/);
|
|
33
|
+
});
|
|
26
34
|
it("o número continua antes do rótulo no HTML", () => {
|
|
27
35
|
const { container } = render(_jsx(StatCard, { value: 44, label: "N\u00E3o lidos", size: "sm" }));
|
|
28
36
|
const valor = container.querySelector(".umds-stat-card__valor");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1doc/1ds-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.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-core": "2.15.0",
|
|
49
|
+
"@1doc/1ds-tokens": "0.8.0"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -58,6 +58,8 @@ export { default as Input } from "./components/Input/Input";
|
|
|
58
58
|
export { default as Kanban } from "./components/Kanban/Kanban";
|
|
59
59
|
export { default as KanbanCard } from "./components/Kanban/KanbanCard";
|
|
60
60
|
export { default as KanbanColumn } from "./components/Kanban/KanbanColumn";
|
|
61
|
+
export { default as Counter } from "./components/Counter/Counter";
|
|
62
|
+
export type * from "./components/Counter/Counter";
|
|
61
63
|
export { default as Kbd } from "./components/Kbd/Kbd";
|
|
62
64
|
export { default as Link } from "./components/Link/Link";
|
|
63
65
|
export { default as LineChart } from "./components/Chart/LineChart";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import Button from "../components/Button/Button";
|
|
4
|
+
import Counter from "../components/Counter/Counter";
|
|
5
|
+
import Dropdown from "../components/Dropdown/Dropdown";
|
|
6
|
+
import ListItem from "../components/List/ListItem";
|
|
7
|
+
import StatCard from "../components/StatCard/StatCard";
|
|
8
|
+
|
|
9
|
+
describe("Counter", () => {
|
|
10
|
+
it("sem animação mostra o valor final formatado em pt-BR", () => {
|
|
11
|
+
const { container } = render(<Counter value={1284} disableAnimation />);
|
|
12
|
+
expect(container.querySelector(".umds-counter__numero")).toHaveTextContent("1.284");
|
|
13
|
+
expect(container.querySelector(".umds-counter__numero")).toHaveAttribute("aria-hidden", "true");
|
|
14
|
+
expect(container.querySelector(".umds-counter__leitura")).toHaveTextContent("1.284");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("casas decimais seguem o value; prefixo e sufixo entram no texto", () => {
|
|
18
|
+
const { container } = render(
|
|
19
|
+
<>
|
|
20
|
+
<Counter value={22.31} suffix="%" disableAnimation />
|
|
21
|
+
<Counter value={15890.5} prefix="R$ " decimals={2} disableAnimation />
|
|
22
|
+
</>,
|
|
23
|
+
);
|
|
24
|
+
const leituras = container.querySelectorAll(".umds-counter__leitura");
|
|
25
|
+
expect(leituras[0]).toHaveTextContent("22,31%");
|
|
26
|
+
expect(leituras[1].textContent).toBe("R$ 15.890,50");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("conta do from até o valor e termina no valor exato", async () => {
|
|
30
|
+
vi.useFakeTimers();
|
|
31
|
+
const { container } = render(<Counter value={500} duration={100} />);
|
|
32
|
+
const numero = () => container.querySelector(".umds-counter__numero")!.textContent;
|
|
33
|
+
expect(numero()).toBe("0");
|
|
34
|
+
await act(async () => {
|
|
35
|
+
vi.advanceTimersByTime(500);
|
|
36
|
+
});
|
|
37
|
+
expect(numero()).toBe("500");
|
|
38
|
+
// A reserva e a leitura já nascem com o valor final.
|
|
39
|
+
expect(container.querySelector(".umds-counter__leitura")).toHaveTextContent("500");
|
|
40
|
+
vi.useRealTimers();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("ListItem · actions", () => {
|
|
45
|
+
it("as ações ficam no li, fora do elemento clicável", () => {
|
|
46
|
+
const { container } = render(
|
|
47
|
+
<ul>
|
|
48
|
+
<ListItem href="#doc" title="Memorando" actions={<button type="button">Mais</button>} />
|
|
49
|
+
</ul>,
|
|
50
|
+
);
|
|
51
|
+
const acao = screen.getByRole("button", { name: "Mais" });
|
|
52
|
+
expect(acao.closest("a")).toBeNull();
|
|
53
|
+
expect(acao.parentElement).toHaveClass("umds-list-item__actions");
|
|
54
|
+
expect(container.querySelector("li > a")).toHaveAttribute("href", "#doc");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("Button · href", () => {
|
|
59
|
+
it("com href é um link; desabilitado volta a ser botão", () => {
|
|
60
|
+
render(
|
|
61
|
+
<>
|
|
62
|
+
<Button href="#inbox">Inbox</Button>
|
|
63
|
+
<Button href="#cal" target="_blank">Calendário</Button>
|
|
64
|
+
<Button href="#fila" disabled>Fila</Button>
|
|
65
|
+
</>,
|
|
66
|
+
);
|
|
67
|
+
expect(screen.getByRole("link", { name: "Inbox" })).toHaveAttribute("href", "#inbox");
|
|
68
|
+
expect(screen.getByRole("link", { name: "Calendário" })).toHaveAttribute("rel", "noopener noreferrer");
|
|
69
|
+
expect(screen.getByRole("button", { name: "Fila" })).toBeDisabled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("loading segura a navegação do link", () => {
|
|
73
|
+
render(<Button href="#inbox" loading loadingPosition="start">Inbox</Button>);
|
|
74
|
+
const link = screen.getByRole("link", { name: "Inbox" });
|
|
75
|
+
const evento = new MouseEvent("click", { bubbles: true, cancelable: true });
|
|
76
|
+
link.dispatchEvent(evento);
|
|
77
|
+
expect(evento.defaultPrevented).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("Dropdown · href", () => {
|
|
82
|
+
it("item com href vira <a role=menuitem>, e o onSelect continua", async () => {
|
|
83
|
+
const onSelect = vi.fn();
|
|
84
|
+
render(
|
|
85
|
+
<Dropdown
|
|
86
|
+
ariaLabel="Ações"
|
|
87
|
+
onSelect={onSelect}
|
|
88
|
+
items={[
|
|
89
|
+
{ key: "assinar", label: "Assinar", href: "#assinar" },
|
|
90
|
+
{ key: "arquivar", label: "Arquivar" },
|
|
91
|
+
]}
|
|
92
|
+
>
|
|
93
|
+
<button type="button">Abrir</button>
|
|
94
|
+
</Dropdown>,
|
|
95
|
+
);
|
|
96
|
+
fireEvent.click(screen.getByRole("button", { name: "Abrir" }));
|
|
97
|
+
const assinar = await screen.findByRole("menuitem", { name: "Assinar" });
|
|
98
|
+
expect(assinar.tagName).toBe("A");
|
|
99
|
+
expect(assinar).toHaveAttribute("href", "#assinar");
|
|
100
|
+
expect(screen.getByRole("menuitem", { name: "Arquivar" }).tagName).toBe("BUTTON");
|
|
101
|
+
fireEvent.click(assinar);
|
|
102
|
+
expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ key: "assinar" }));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("StatCard · loading e secondaryTone", () => {
|
|
107
|
+
it("loading troca os números por Skeleton e marca aria-busy", () => {
|
|
108
|
+
const { container } = render(
|
|
109
|
+
<StatCard value="22,31%" label="Qualidade" secondaryValue="14,11%" trendLabel="+2%" trend="up" loading />,
|
|
110
|
+
);
|
|
111
|
+
const cartao = container.querySelector(".umds-stat-card")!;
|
|
112
|
+
expect(cartao).toHaveAttribute("aria-busy", "true");
|
|
113
|
+
expect(cartao.textContent).not.toContain("22,31%");
|
|
114
|
+
expect(cartao.textContent).not.toContain("14,11%");
|
|
115
|
+
expect(cartao.textContent).not.toContain("+2%");
|
|
116
|
+
expect(cartao.textContent).toContain("Qualidade");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("secondaryTone escreve a classe; default não", () => {
|
|
120
|
+
const { container } = render(
|
|
121
|
+
<>
|
|
122
|
+
<StatCard value="1" secondaryValue="2" secondaryTone="positive" />
|
|
123
|
+
<StatCard value="1" secondaryValue="2" secondaryTone="default" />
|
|
124
|
+
</>,
|
|
125
|
+
);
|
|
126
|
+
expect(container.children[0]).toHaveClass("umds-stat-card--secundario-positive");
|
|
127
|
+
expect(container.children[1].className).not.toContain("secundario-");
|
|
128
|
+
});
|
|
129
|
+
});
|