@1doc/1ds-react 2.8.0 → 2.9.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 +1106 -0
- package/dist/components/AILabel/AILabel.js +1 -1
- package/dist/components/Accordion/Accordion.d.ts +6 -2
- package/dist/components/AttachmentCard/AttachmentCard.css +16 -0
- package/dist/components/AttachmentCard/AttachmentCard.d.ts +19 -2
- package/dist/components/AttachmentCard/AttachmentCard.js +40 -3
- package/dist/components/Avatar/Avatar.css +7 -2
- package/dist/components/Avatar/AvatarGroup.d.ts +17 -0
- package/dist/components/Avatar/AvatarGroup.js +22 -8
- package/dist/components/Badge/Badge.css +54 -2
- package/dist/components/Badge/Badge.d.ts +14 -4
- package/dist/components/Badge/Badge.js +1 -0
- package/dist/components/Box/Box.css +83 -0
- package/dist/components/Box/Box.d.ts +82 -2
- package/dist/components/Box/Box.js +42 -2
- package/dist/components/Button/Button.css +29 -0
- package/dist/components/Button/Button.d.ts +4 -4
- package/dist/components/Button/Button.js +4 -1
- package/dist/components/Calendar/Calendar.css +245 -0
- package/dist/components/Calendar/Calendar.d.ts +125 -0
- package/dist/components/Calendar/Calendar.js +320 -0
- package/dist/components/Chips/Chips.css +38 -0
- package/dist/components/Chips/Chips.d.ts +5 -2
- package/dist/components/Chips/Chips.js +2 -1
- package/dist/components/Coachmark/Coachmark.css +118 -0
- package/dist/components/Coachmark/Coachmark.d.ts +75 -0
- package/dist/components/Coachmark/Coachmark.js +321 -0
- package/dist/components/CodeBlock/CodeBlock.css +96 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +47 -0
- package/dist/components/CodeBlock/CodeBlock.js +103 -0
- package/dist/components/ColorPicker/ColorPicker.css +133 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts +73 -0
- package/dist/components/ColorPicker/ColorPicker.js +219 -0
- package/dist/components/Combobox/Combobox.css +8 -0
- package/dist/components/DataTable/DataTable.css +80 -0
- package/dist/components/DataTable/DataTable.d.ts +19 -4
- package/dist/components/DataTable/DataTable.js +59 -15
- package/dist/components/DatePicker/DatePicker.css +8 -0
- package/dist/components/DatePicker/DatePicker.js +10 -1
- package/dist/components/DescriptionList/DescriptionList.css +113 -0
- package/dist/components/DescriptionList/DescriptionList.d.ts +59 -0
- package/dist/components/DescriptionList/DescriptionList.js +51 -0
- package/dist/components/FileInput/FileInput.css +16 -0
- package/dist/components/FileInput/FileInput.d.ts +4 -2
- package/dist/components/FileInput/FileInput.js +8 -2
- package/dist/components/Grid/Grid.js +5 -5
- package/dist/components/IconPicker/IconPicker.css +107 -0
- package/dist/components/IconPicker/IconPicker.d.ts +74 -0
- package/dist/components/IconPicker/IconPicker.js +194 -0
- package/dist/components/Input/Input.css +38 -0
- package/dist/components/Input/Input.d.ts +10 -1
- package/dist/components/Input/Input.js +66 -5
- package/dist/components/Kanban/Kanban.css +4 -0
- package/dist/components/Kbd/Kbd.css +66 -0
- package/dist/components/Kbd/Kbd.d.ts +32 -0
- package/dist/components/Kbd/Kbd.js +40 -0
- package/dist/components/List/List.css +136 -0
- package/dist/components/List/List.d.ts +82 -0
- package/dist/components/List/List.js +53 -0
- package/dist/components/List/ListItem.d.ts +85 -0
- package/dist/components/List/ListItem.js +81 -0
- package/dist/components/Modal/Modal.css +80 -0
- package/dist/components/Modal/Modal.d.ts +33 -6
- package/dist/components/Modal/Modal.js +101 -6
- package/dist/components/MultiSelect/MultiSelect.css +8 -0
- package/dist/components/MultiSelect/MultiSelect.js +10 -1
- package/dist/components/OrgChart/OrgChart.css +149 -0
- package/dist/components/OrgChart/OrgChart.d.ts +83 -0
- package/dist/components/OrgChart/OrgChart.js +297 -0
- package/dist/components/OtpInput/OtpInput.css +93 -0
- package/dist/components/OtpInput/OtpInput.d.ts +60 -0
- package/dist/components/OtpInput/OtpInput.js +215 -0
- package/dist/components/PhoneInput/PhoneInput.css +8 -0
- package/dist/components/Popover/Popover.d.ts +7 -0
- package/dist/components/Popover/Popover.js +11 -1
- package/dist/components/RichTextEditor/RichTextEditor.css +203 -0
- package/dist/components/RichTextEditor/RichTextEditor.d.ts +83 -0
- package/dist/components/RichTextEditor/RichTextEditor.js +494 -0
- package/dist/components/SegmentedControl/SegmentedControl.js +7 -2
- package/dist/components/Select/Select.css +70 -0
- package/dist/components/Select/Select.d.ts +12 -0
- package/dist/components/Select/Select.js +59 -4
- package/dist/components/Stack/Stack.css +49 -0
- package/dist/components/Stack/Stack.d.ts +65 -7
- package/dist/components/Stack/Stack.js +30 -2
- package/dist/components/StatCard/StatCard.css +73 -0
- package/dist/components/StatCard/StatCard.d.ts +17 -0
- package/dist/components/StatCard/StatCard.js +4 -1
- package/dist/components/Tabs/Tab.d.ts +15 -0
- package/dist/components/Tabs/Tab.js +30 -1
- package/dist/components/Tabs/Tabs.css +132 -0
- package/dist/components/Tabs/Tabs.d.ts +7 -0
- package/dist/components/Tabs/Tabs.js +13 -2
- package/dist/components/Tag/Tag.css +85 -2
- package/dist/components/Tag/Tag.d.ts +13 -6
- package/dist/components/Tag/Tag.js +1 -0
- package/dist/components/Text/Text.js +2 -2
- package/dist/components/Textarea/Textarea.css +8 -0
- package/dist/components/TimePicker/TimePicker.css +8 -0
- package/dist/components/Tooltip/Tooltip.css +25 -0
- package/dist/components/Tooltip/Tooltip.d.ts +11 -0
- package/dist/components/Tooltip/Tooltip.js +18 -3
- package/dist/index.d.ts +25 -0
- package/dist/index.js +14 -0
- package/dist/teste/AILabel.test.d.ts +1 -0
- package/dist/teste/AILabel.test.js +33 -0
- package/dist/teste/AccordionConteudoLivre.test.d.ts +1 -0
- package/dist/teste/AccordionConteudoLivre.test.js +29 -0
- package/dist/teste/AtributosRepassados.test.d.ts +1 -0
- package/dist/teste/AtributosRepassados.test.js +52 -0
- package/dist/teste/AttachmentCardIA.test.d.ts +1 -0
- package/dist/teste/AttachmentCardIA.test.js +89 -0
- package/dist/teste/AvatarGroupOverlap.test.d.ts +1 -0
- package/dist/teste/AvatarGroupOverlap.test.js +56 -0
- package/dist/teste/BadgeTag.test.d.ts +1 -0
- package/dist/teste/BadgeTag.test.js +42 -0
- package/dist/teste/BoxItemFlex.test.d.ts +1 -0
- package/dist/teste/BoxItemFlex.test.js +54 -0
- package/dist/teste/BoxLayout.test.d.ts +1 -0
- package/dist/teste/BoxLayout.test.js +35 -0
- package/dist/teste/Button.test.d.ts +1 -0
- package/dist/teste/Button.test.js +27 -0
- package/dist/teste/Calendar.test.d.ts +1 -0
- package/dist/teste/Calendar.test.js +221 -0
- package/dist/teste/ChipsXs.test.d.ts +1 -0
- package/dist/teste/ChipsXs.test.js +31 -0
- package/dist/teste/Coachmark.test.d.ts +1 -0
- package/dist/teste/Coachmark.test.js +149 -0
- package/dist/teste/ColorPicker.test.d.ts +1 -0
- package/dist/teste/ColorPicker.test.js +132 -0
- package/dist/teste/DataTableCelulaLivre.test.d.ts +1 -0
- package/dist/teste/DataTableCelulaLivre.test.js +76 -0
- package/dist/teste/DataTableDocumento.test.d.ts +1 -0
- package/dist/teste/DataTableDocumento.test.js +92 -0
- package/dist/teste/DatePicker.test.js +23 -0
- package/dist/teste/FileInputTrigger.test.d.ts +1 -0
- package/dist/teste/FileInputTrigger.test.js +53 -0
- package/dist/teste/IconPicker.test.d.ts +1 -0
- package/dist/teste/IconPicker.test.js +130 -0
- package/dist/teste/InputMascaraEAfins.test.d.ts +1 -0
- package/dist/teste/InputMascaraEAfins.test.js +99 -0
- package/dist/teste/KbdCodeBlock.test.d.ts +1 -0
- package/dist/teste/KbdCodeBlock.test.js +95 -0
- package/dist/teste/List.test.d.ts +1 -0
- package/dist/teste/List.test.js +153 -0
- package/dist/teste/ModalVariantes.test.d.ts +1 -0
- package/dist/teste/ModalVariantes.test.js +151 -0
- package/dist/teste/NomeAcessivelSsr.test.d.ts +1 -0
- package/dist/teste/NomeAcessivelSsr.test.js +88 -0
- package/dist/teste/OrgChart.test.d.ts +1 -0
- package/dist/teste/OrgChart.test.js +162 -0
- package/dist/teste/OtpInput.test.d.ts +1 -0
- package/dist/teste/OtpInput.test.js +143 -0
- package/dist/teste/PopoverMaxHeight.test.d.ts +1 -0
- package/dist/teste/PopoverMaxHeight.test.js +38 -0
- package/dist/teste/RichTextEditor.test.d.ts +1 -0
- package/dist/teste/RichTextEditor.test.js +287 -0
- package/dist/teste/SelectPlacementFiltro.test.d.ts +1 -0
- package/dist/teste/SelectPlacementFiltro.test.js +88 -0
- package/dist/teste/Stack.test.d.ts +1 -0
- package/dist/teste/Stack.test.js +57 -0
- package/dist/teste/StatCardRotuloTopo.test.d.ts +1 -0
- package/dist/teste/StatCardRotuloTopo.test.js +34 -0
- package/dist/teste/TabsContadorFechavel.test.d.ts +1 -0
- package/dist/teste/TabsContadorFechavel.test.js +79 -0
- package/dist/teste/TooltipDark.test.d.ts +1 -0
- package/dist/teste/TooltipDark.test.js +31 -0
- package/dist/utils/calendario.d.ts +28 -0
- package/dist/utils/calendario.js +73 -0
- package/dist/utils/html-seguro.d.ts +27 -0
- package/dist/utils/html-seguro.js +97 -0
- package/dist/utils/paleta-de-cores.d.ts +26 -0
- package/dist/utils/paleta-de-cores.js +10 -0
- package/dist/utils/texto.d.ts +5 -0
- package/dist/utils/texto.js +5 -0
- package/package.json +3 -3
- package/src/index.ts +26 -0
- package/src/teste/AILabel.test.tsx +41 -0
- package/src/teste/AccordionConteudoLivre.test.tsx +39 -0
- package/src/teste/AtributosRepassados.test.tsx +73 -0
- package/src/teste/AttachmentCardIA.test.tsx +126 -0
- package/src/teste/AvatarGroupOverlap.test.tsx +74 -0
- package/src/teste/BadgeTag.test.tsx +64 -0
- package/src/teste/BoxItemFlex.test.tsx +81 -0
- package/src/teste/BoxLayout.test.tsx +50 -0
- package/src/teste/Button.test.tsx +39 -0
- package/src/teste/Calendar.test.tsx +282 -0
- package/src/teste/ChipsXs.test.tsx +41 -0
- package/src/teste/Coachmark.test.tsx +198 -0
- package/src/teste/ColorPicker.test.tsx +153 -0
- package/src/teste/DataTableCelulaLivre.test.tsx +103 -0
- package/src/teste/DataTableDocumento.test.tsx +102 -0
- package/src/teste/DatePicker.test.tsx +38 -0
- package/src/teste/FileInputTrigger.test.tsx +89 -0
- package/src/teste/IconPicker.test.tsx +145 -0
- package/src/teste/InputMascaraEAfins.test.tsx +124 -0
- package/src/teste/KbdCodeBlock.test.tsx +106 -0
- package/src/teste/List.test.tsx +269 -0
- package/src/teste/ModalVariantes.test.tsx +173 -0
- package/src/teste/NomeAcessivelSsr.test.tsx +110 -0
- package/src/teste/OrgChart.test.tsx +183 -0
- package/src/teste/OtpInput.test.tsx +163 -0
- package/src/teste/PopoverMaxHeight.test.tsx +47 -0
- package/src/teste/RichTextEditor.test.tsx +329 -0
- package/src/teste/SelectPlacementFiltro.test.tsx +110 -0
- package/src/teste/Stack.test.tsx +80 -0
- package/src/teste/StatCardRotuloTopo.test.tsx +44 -0
- package/src/teste/TabsContadorFechavel.test.tsx +144 -0
- package/src/teste/TooltipDark.test.tsx +51 -0
- package/src/utils/calendario.ts +78 -0
- package/src/utils/html-seguro.ts +94 -0
- package/src/utils/paleta-de-cores.ts +34 -0
- package/src/utils/texto.ts +5 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import Tabs from "../components/Tabs/Tabs";
|
|
4
|
+
import Tab from "../components/Tabs/Tab";
|
|
5
|
+
|
|
6
|
+
describe("Tab · count", () => {
|
|
7
|
+
it("mostra o contador em pílula e ele entra no nome da aba", () => {
|
|
8
|
+
render(
|
|
9
|
+
<Tabs value="0" ariaLabel="Tarefas">
|
|
10
|
+
<Tab label="Todas" index="0" count={31} />
|
|
11
|
+
<Tab label="Atribuídas" index="1" count={9} />
|
|
12
|
+
</Tabs>,
|
|
13
|
+
);
|
|
14
|
+
expect(screen.getByRole("tab", { name: "Todas 31" })).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByRole("tab", { name: "Atribuídas 9" })).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("o 0 aparece; sem count, não há pílula", () => {
|
|
19
|
+
const { container } = render(
|
|
20
|
+
<Tabs value="0">
|
|
21
|
+
<Tab label="Concluídas" index="0" count={0} />
|
|
22
|
+
<Tab label="Sem contador" index="1" />
|
|
23
|
+
</Tabs>,
|
|
24
|
+
);
|
|
25
|
+
const pilulas = container.querySelectorAll(".umds-tabs__count");
|
|
26
|
+
expect(pilulas).toHaveLength(1);
|
|
27
|
+
expect(pilulas[0].textContent).toBe("0");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("a pílula fica dentro da aba selecionada (que a pinta de azul pelo CSS)", () => {
|
|
31
|
+
const { container } = render(
|
|
32
|
+
<Tabs value="1">
|
|
33
|
+
<Tab label="A" index="0" count={1} />
|
|
34
|
+
<Tab label="B" index="1" count={2} />
|
|
35
|
+
</Tabs>,
|
|
36
|
+
);
|
|
37
|
+
const selecionada = container.querySelector(".umds-tabs__tab--selected");
|
|
38
|
+
expect(selecionada?.querySelector(".umds-tabs__count")?.textContent).toBe("2");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("Tab · closable", () => {
|
|
43
|
+
it("o × é um botão irmão da aba, com o nome 'Fechar <aba>'", () => {
|
|
44
|
+
render(
|
|
45
|
+
<Tabs value="0">
|
|
46
|
+
<Tab label="Documento 1" index="0" closable />
|
|
47
|
+
</Tabs>,
|
|
48
|
+
);
|
|
49
|
+
const fechar = screen.getByRole("button", { name: "Fechar Documento 1" });
|
|
50
|
+
const aba = screen.getByRole("tab");
|
|
51
|
+
expect(aba.contains(fechar)).toBe(false);
|
|
52
|
+
expect(fechar.parentElement).toBe(aba.parentElement);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("o rótulo do × vem por prop", () => {
|
|
56
|
+
render(
|
|
57
|
+
<Tabs value="0">
|
|
58
|
+
<Tab label="Doc 1" index="0" closable closeLabel="Close" />
|
|
59
|
+
</Tabs>,
|
|
60
|
+
);
|
|
61
|
+
expect(screen.getByRole("button", { name: "Close Doc 1" })).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("clicar no × chama onClose com o index e não troca de aba", () => {
|
|
65
|
+
const aoFechar = vi.fn();
|
|
66
|
+
const aoTrocar = vi.fn();
|
|
67
|
+
render(
|
|
68
|
+
<Tabs value="0" onChange={aoTrocar}>
|
|
69
|
+
<Tab label="Um" index="0" />
|
|
70
|
+
<Tab label="Dois" index="1" closable onClose={aoFechar} />
|
|
71
|
+
</Tabs>,
|
|
72
|
+
);
|
|
73
|
+
fireEvent.click(screen.getByRole("button", { name: "Fechar Dois" }));
|
|
74
|
+
expect(aoFechar).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(aoFechar.mock.calls[0][0]).toBe("1");
|
|
76
|
+
expect(aoTrocar).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("Delete na aba focada fecha; sem closable, não", () => {
|
|
80
|
+
const aoFechar = vi.fn();
|
|
81
|
+
render(
|
|
82
|
+
<Tabs value="0">
|
|
83
|
+
<Tab label="Fechável" index="0" closable onClose={aoFechar} />
|
|
84
|
+
<Tab label="Fixa" index="1" onClose={aoFechar} />
|
|
85
|
+
</Tabs>,
|
|
86
|
+
);
|
|
87
|
+
fireEvent.keyDown(screen.getByRole("tab", { name: "Fechável" }), { key: "Delete" });
|
|
88
|
+
expect(aoFechar).toHaveBeenCalledTimes(1);
|
|
89
|
+
fireEvent.keyDown(screen.getByRole("tab", { name: "Fixa" }), { key: "Delete" });
|
|
90
|
+
expect(aoFechar).toHaveBeenCalledTimes(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("sem closable não há ×", () => {
|
|
94
|
+
render(
|
|
95
|
+
<Tabs value="0">
|
|
96
|
+
<Tab label="Fixa" index="0" />
|
|
97
|
+
</Tabs>,
|
|
98
|
+
);
|
|
99
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("o tablist só tem abas como filhas de papel (o × não é tab)", () => {
|
|
103
|
+
render(
|
|
104
|
+
<Tabs value="0">
|
|
105
|
+
<Tab label="A" index="0" closable />
|
|
106
|
+
<Tab label="B" index="1" closable />
|
|
107
|
+
</Tabs>,
|
|
108
|
+
);
|
|
109
|
+
expect(screen.getAllByRole("tab")).toHaveLength(2);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("Tabs · onAdd", () => {
|
|
114
|
+
it("mostra o + fora do tablist e chama onAdd", () => {
|
|
115
|
+
const aoAdicionar = vi.fn();
|
|
116
|
+
render(
|
|
117
|
+
<Tabs value="0" onAdd={aoAdicionar}>
|
|
118
|
+
<Tab label="A" index="0" />
|
|
119
|
+
</Tabs>,
|
|
120
|
+
);
|
|
121
|
+
const mais = screen.getByRole("button", { name: "Adicionar aba" });
|
|
122
|
+
expect(screen.getByRole("tablist").contains(mais)).toBe(false);
|
|
123
|
+
fireEvent.click(mais);
|
|
124
|
+
expect(aoAdicionar).toHaveBeenCalledTimes(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("o rótulo do + vem por prop", () => {
|
|
128
|
+
render(
|
|
129
|
+
<Tabs value="0" onAdd={() => {}} addLabel="New tab">
|
|
130
|
+
<Tab label="A" index="0" />
|
|
131
|
+
</Tabs>,
|
|
132
|
+
);
|
|
133
|
+
expect(screen.getByRole("button", { name: "New tab" })).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("sem onAdd não há botão", () => {
|
|
137
|
+
render(
|
|
138
|
+
<Tabs value="0">
|
|
139
|
+
<Tab label="A" index="0" />
|
|
140
|
+
</Tabs>,
|
|
141
|
+
);
|
|
142
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import Tooltip from "../components/Tooltip/Tooltip";
|
|
4
|
+
|
|
5
|
+
async function abrir(ui: React.ReactElement) {
|
|
6
|
+
render(ui);
|
|
7
|
+
screen.getByRole("button").focus();
|
|
8
|
+
await waitFor(() => expect(screen.getByRole("tooltip")).toHaveClass("umds-tooltip__bubble--open"));
|
|
9
|
+
return screen.getByRole("tooltip");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("Tooltip · variant dark e maxWidth", () => {
|
|
13
|
+
it("dark escreve a classe no balão e no wrapper flutuante", async () => {
|
|
14
|
+
const balao = await abrir(
|
|
15
|
+
<Tooltip content="Atualizar" variant="dark">
|
|
16
|
+
<button type="button">Atualizar</button>
|
|
17
|
+
</Tooltip>,
|
|
18
|
+
);
|
|
19
|
+
expect(balao).toHaveClass("umds-tooltip__bubble--dark");
|
|
20
|
+
expect(balao.parentElement).toHaveClass("umds-tooltip__floating--dark");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("sem variant o balão é o padrão", async () => {
|
|
24
|
+
const balao = await abrir(
|
|
25
|
+
<Tooltip content="Atualizar">
|
|
26
|
+
<button type="button">Atualizar</button>
|
|
27
|
+
</Tooltip>,
|
|
28
|
+
);
|
|
29
|
+
expect(balao).not.toHaveClass("umds-tooltip__bubble--dark");
|
|
30
|
+
expect(balao.parentElement).not.toHaveClass("umds-tooltip__floating--dark");
|
|
31
|
+
expect(balao.parentElement?.style.maxWidth).toBe("");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("maxWidth vai ao wrapper flutuante, em qualquer variante", async () => {
|
|
35
|
+
const balao = await abrir(
|
|
36
|
+
<Tooltip content="Texto longo" variant="dark" maxWidth="140px">
|
|
37
|
+
<button type="button">x</button>
|
|
38
|
+
</Tooltip>,
|
|
39
|
+
);
|
|
40
|
+
expect(balao.parentElement?.style.maxWidth).toBe("140px");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("a descrição acessível não muda com a variante", () => {
|
|
44
|
+
render(
|
|
45
|
+
<Tooltip content="Atualizar" variant="dark">
|
|
46
|
+
<button type="button">Recarregar</button>
|
|
47
|
+
</Tooltip>,
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByRole("button")).toHaveAccessibleDescription("Atualizar");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datas do `Calendar`, em ISO local (`YYYY-MM-DD`) e sem biblioteca.
|
|
3
|
+
*
|
|
4
|
+
* Tudo aqui trabalha com o DIA como texto: `"2026-05-15"`. O `Date` só aparece
|
|
5
|
+
* por dentro, sempre à meia-noite LOCAL (`new Date(ano, mes, dia)`), nunca
|
|
6
|
+
* `new Date("2026-05-15")`, que o JavaScript lê como UTC e, no fuso do Brasil,
|
|
7
|
+
* devolve o dia anterior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const doisDigitos = (n: number): string => n < 10 ? "0" + n : String(n);
|
|
11
|
+
|
|
12
|
+
/** `Date` local → `YYYY-MM-DD`. */
|
|
13
|
+
export const toIso = (data: Date): string => data.getFullYear() + "-" + doisDigitos(data.getMonth() + 1) + "-" + doisDigitos(data.getDate());
|
|
14
|
+
|
|
15
|
+
/** `YYYY-MM-DD` → `Date` à meia-noite local. */
|
|
16
|
+
export const parseIso = (iso: string): Date => {
|
|
17
|
+
const partes = iso.slice(0, 10).split("-");
|
|
18
|
+
return new Date(Number(partes[0]), Number(partes[1]) - 1, Number(partes[2]));
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Aceita `Date` ou texto (`2026-05-15` e `2026-05-15T10:00`) e devolve `YYYY-MM-DD`; "" se não der. */
|
|
22
|
+
export const normalizeIso = (valor: any): string => {
|
|
23
|
+
if (!valor) return "";
|
|
24
|
+
if (typeof valor === "string") return /^\d{4}-\d{2}-\d{2}/.test(valor) ? valor.slice(0, 10) : "";
|
|
25
|
+
if (valor instanceof Date && !isNaN(valor.getTime())) return toIso(valor);
|
|
26
|
+
return "";
|
|
27
|
+
};
|
|
28
|
+
export const addDays = (iso: string, dias: number): string => {
|
|
29
|
+
const data = parseIso(iso);
|
|
30
|
+
data.setDate(data.getDate() + dias);
|
|
31
|
+
return toIso(data);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Soma meses mantendo o dia, ou o último dia do mês de chegada (31/01 + 1 mês = 28/02). */
|
|
35
|
+
export const addMonths = (iso: string, meses: number): string => {
|
|
36
|
+
const data = parseIso(iso);
|
|
37
|
+
const dia = data.getDate();
|
|
38
|
+
data.setDate(1);
|
|
39
|
+
data.setMonth(data.getMonth() + meses);
|
|
40
|
+
const ultimo = new Date(data.getFullYear(), data.getMonth() + 1, 0).getDate();
|
|
41
|
+
data.setDate(Math.min(dia, ultimo));
|
|
42
|
+
return toIso(data);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Dia da semana: 0 = domingo. */
|
|
46
|
+
export const weekdayOf = (iso: string): number => parseIso(iso).getDay();
|
|
47
|
+
|
|
48
|
+
/** Primeiro dia da semana que contém `iso`, dado o dia em que a semana começa (0 = domingo, 1 = segunda). */
|
|
49
|
+
export const startOfWeek = (iso: string, comecaEm: number): string => {
|
|
50
|
+
const recuo = (weekdayOf(iso) - comecaEm + 7) % 7;
|
|
51
|
+
return addDays(iso, -recuo);
|
|
52
|
+
};
|
|
53
|
+
export const startOfMonth = (iso: string): string => iso.slice(0, 8) + "01";
|
|
54
|
+
export const endOfMonth = (iso: string): string => {
|
|
55
|
+
const data = parseIso(iso);
|
|
56
|
+
return toIso(new Date(data.getFullYear(), data.getMonth() + 1, 0));
|
|
57
|
+
};
|
|
58
|
+
export const isSameMonth = (a: string, b: string): boolean => a.slice(0, 7) === b.slice(0, 7);
|
|
59
|
+
|
|
60
|
+
/** Os sete dias da semana que contém `iso`. */
|
|
61
|
+
export const weekDays = (iso: string, comecaEm: number): string[] => {
|
|
62
|
+
const inicio = startOfWeek(iso, comecaEm);
|
|
63
|
+
const dias: string[] = [];
|
|
64
|
+
for (let i = 0; i < 7; i++) dias.push(addDays(inicio, i));
|
|
65
|
+
return dias;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** O mês de `iso` em semanas inteiras (de 4 a 6 linhas), com os dias dos meses vizinhos que completam as pontas. */
|
|
69
|
+
export const monthMatrix = (iso: string, comecaEm: number): string[][] => {
|
|
70
|
+
const ultimo = endOfMonth(iso);
|
|
71
|
+
let cursor = startOfWeek(startOfMonth(iso), comecaEm);
|
|
72
|
+
const semanas: string[][] = [];
|
|
73
|
+
while (cursor <= ultimo) {
|
|
74
|
+
semanas.push(weekDays(cursor, comecaEm));
|
|
75
|
+
cursor = addDays(cursor, 7);
|
|
76
|
+
}
|
|
77
|
+
return semanas;
|
|
78
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML seguro para o `RichTextEditor`.
|
|
3
|
+
*
|
|
4
|
+
* O editor devolve HTML (`onChange`) e recebe HTML (`value`). HTML que vem de
|
|
5
|
+
* fora (um valor guardado, um texto colado) pode trazer `<img onerror>`,
|
|
6
|
+
* `<a href="javascript:...">`, `<script>`, `style` e classes de outro site. Este
|
|
7
|
+
* módulo reduz o HTML ao que o editor sabe escrever: negrito, itálico,
|
|
8
|
+
* sublinhado, tachado, listas, parágrafos, quebras, títulos, citação e link.
|
|
9
|
+
*
|
|
10
|
+
* Roda no navegador (precisa de DOM). Dentro de um `<template>` o HTML é inerte:
|
|
11
|
+
* nada executa e nenhuma imagem carrega enquanto se higieniza. Sem DOM
|
|
12
|
+
* (servidor), devolve o texto escapado.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const PERMITIDAS = ["A", "B", "STRONG", "I", "EM", "U", "S", "STRIKE", "DEL", "UL", "OL", "LI", "P", "BR", "DIV", "SPAN", "H1", "H2", "H3", "H4", "BLOCKQUOTE"];
|
|
16
|
+
const DESCARTADAS = ["SCRIPT", "STYLE", "IFRAME", "OBJECT", "EMBED", "LINK", "META", "TEMPLATE", "NOSCRIPT", "SVG", "MATH", "FORM", "INPUT", "BUTTON", "TEXTAREA", "SELECT"];
|
|
17
|
+
const escapar = (texto: string): string => texto.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Endereço de link que o editor aceita: `http:`, `https:`, `mailto:`, `tel:`,
|
|
21
|
+
* caminho (`/x`, `./x`, `../x`) ou âncora (`#x`). Devolve o endereço limpo, ou ""
|
|
22
|
+
* se for perigoso (`javascript:`, `data:`, `vbscript:`...).
|
|
23
|
+
*/
|
|
24
|
+
export const enderecoSeguro = (bruto: string): string => {
|
|
25
|
+
const texto = (bruto || "").trim();
|
|
26
|
+
if (!texto) return "";
|
|
27
|
+
// Caracteres de controle e espaços no meio do esquema são o truque clássico (`java\nscript:`).
|
|
28
|
+
const semControle = texto.replace(/[\u0000-\u001f\u007f\s]+/g, "");
|
|
29
|
+
if (/^(https?:\/\/|mailto:|tel:|\/|\.\.?\/|#)/i.test(semControle)) return texto;
|
|
30
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(semControle)) return "";
|
|
31
|
+
return "";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Devolve o endereço pronto para `href`: o seguro como veio, ou `https://` + o texto quando não tem esquema. "" se perigoso ou vazio. */
|
|
35
|
+
export const normalizarEndereco = (bruto: string): string => {
|
|
36
|
+
const texto = (bruto || "").trim();
|
|
37
|
+
if (!texto) return "";
|
|
38
|
+
const seguro = enderecoSeguro(texto);
|
|
39
|
+
if (seguro) return seguro;
|
|
40
|
+
const semControle = texto.replace(/[\u0000-\u001f\u007f\s]+/g, "");
|
|
41
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(semControle)) return "";
|
|
42
|
+
return "https://" + texto;
|
|
43
|
+
};
|
|
44
|
+
const limparNo = (no: any, saida: any, documento: any): void => {
|
|
45
|
+
const filhos = Array.prototype.slice.call(no.childNodes);
|
|
46
|
+
for (let i = 0; i < filhos.length; i++) {
|
|
47
|
+
const filho = filhos[i];
|
|
48
|
+
if (filho.nodeType === 3) {
|
|
49
|
+
saida.appendChild(documento.createTextNode(filho.nodeValue));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (filho.nodeType !== 1) continue;
|
|
53
|
+
const tag = String(filho.tagName).toUpperCase();
|
|
54
|
+
if (DESCARTADAS.indexOf(tag) !== -1) continue;
|
|
55
|
+
if (PERMITIDAS.indexOf(tag) === -1) {
|
|
56
|
+
// Tag desconhecida: some a tag, fica o conteúdo.
|
|
57
|
+
limparNo(filho, saida, documento);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const novo = documento.createElement(tag.toLowerCase());
|
|
61
|
+
if (tag === "A") {
|
|
62
|
+
const href = enderecoSeguro(filho.getAttribute("href") || "");
|
|
63
|
+
if (href) {
|
|
64
|
+
novo.setAttribute("href", href);
|
|
65
|
+
novo.setAttribute("rel", "noopener noreferrer");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
limparNo(filho, novo, documento);
|
|
69
|
+
saida.appendChild(novo);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Reduz `html` ao que o editor escreve. Sem DOM, devolve o texto escapado. */
|
|
74
|
+
export const sanitizarHtml = (html: string): string => {
|
|
75
|
+
const bruto = html || "";
|
|
76
|
+
if (typeof document === "undefined") return escapar(bruto);
|
|
77
|
+
const molde: any = document.createElement("template");
|
|
78
|
+
molde.innerHTML = bruto;
|
|
79
|
+
const saida: any = document.createElement("div");
|
|
80
|
+
limparNo(molde.content, saida, document);
|
|
81
|
+
return saida.innerHTML;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** O editor está "vazio": sem texto, sem lista e sem quebra solta (o `<br>` que o navegador deixa). */
|
|
85
|
+
export const htmlVazio = (html: string): boolean => {
|
|
86
|
+
const texto = (html || "").replace(/<br\s*\/?>/gi, "").replace(/<\/?(div|p|span)[^>]*>/gi, "").trim();
|
|
87
|
+
return texto === "";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** O `<a>` que o editor escreve quando o link não tem texto escolhido: o endereço (já seguro) é o href e o texto. */
|
|
91
|
+
export const htmlDoLink = (endereco: string): string => {
|
|
92
|
+
const seguro = escapar(endereco);
|
|
93
|
+
return '<a href="' + seguro + '">' + seguro + "</a>";
|
|
94
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paleta de cor categórica do 1DS: os primitivos `--umds-primitive-<família>-<tom>`.
|
|
3
|
+
*
|
|
4
|
+
* É a lista que o `ColorPicker` mostra por padrão. Ela existe como constante
|
|
5
|
+
* exportada para o app mapear o valor guardado (o nome do token) de volta para
|
|
6
|
+
* um rótulo, ou montar a própria lista a partir dela (`COLOR_PALETTE.filter(...)`).
|
|
7
|
+
*
|
|
8
|
+
* Primitivo não muda com o tema: é o certo para cor de DADO (marcador, tipo de
|
|
9
|
+
* documento, setor), que precisa ser a mesma no claro e no escuro. Para cor com
|
|
10
|
+
* PAPEL (primário, erro, sucesso) use os semânticos `--umds-semantic-color-*`,
|
|
11
|
+
* que não entram aqui.
|
|
12
|
+
*/
|
|
13
|
+
export type PaletteColor = {
|
|
14
|
+
/** Nome da variável CSS, ex.: `--umds-primitive-blue-pure`. Use como `var(${value})`. */
|
|
15
|
+
value: string;
|
|
16
|
+
/** Nome em português, ex.: "Azul escuro". */
|
|
17
|
+
label: string;
|
|
18
|
+
/** Família: blue, red, green, orange, yellow, purple ou gray. */
|
|
19
|
+
family: string;
|
|
20
|
+
/** Tom claro o bastante para o ícone de marcação sair escuro (contraste mínimo de 3:1). */
|
|
21
|
+
onLight: boolean;
|
|
22
|
+
};
|
|
23
|
+
const cor = (family: string, tone: string, label: string, onLight: boolean): PaletteColor => ({
|
|
24
|
+
value: "--umds-primitive-" + family + "-" + tone,
|
|
25
|
+
label,
|
|
26
|
+
family,
|
|
27
|
+
onLight
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/** Os 31 primitivos de cor (6 famílias x 4 tons + 7 cinzas), do mais escuro ao mais claro. */
|
|
31
|
+
export const COLOR_PALETTE: PaletteColor[] = [cor("blue", "dark", "Azul escuro", false), cor("blue", "pure", "Azul", false), cor("blue", "medium", "Azul médio", false), cor("blue", "light", "Azul claro", true), cor("red", "dark", "Vermelho escuro", false), cor("red", "pure", "Vermelho", false), cor("red", "medium", "Vermelho médio", false), cor("red", "light", "Vermelho claro", true), cor("green", "dark", "Verde escuro", false), cor("green", "pure", "Verde", false), cor("green", "medium", "Verde médio", false), cor("green", "light", "Verde claro", true), cor("orange", "dark", "Laranja escuro", false), cor("orange", "pure", "Laranja", false), cor("orange", "medium", "Laranja médio", true), cor("orange", "light", "Laranja claro", true), cor("yellow", "dark", "Amarelo escuro", false), cor("yellow", "pure", "Amarelo", true), cor("yellow", "medium", "Amarelo médio", true), cor("yellow", "light", "Amarelo claro", true), cor("purple", "dark", "Roxo escuro", false), cor("purple", "pure", "Roxo", false), cor("purple", "medium", "Roxo médio", false), cor("purple", "light", "Roxo claro", true), cor("gray", "darkest", "Cinza muito escuro", false), cor("gray", "darker", "Cinza escuro", false), cor("gray", "dark", "Cinza", false), cor("gray", "medium", "Cinza médio", true), cor("gray", "light", "Cinza claro", true), cor("gray", "lighter", "Cinza mais claro", true), cor("gray", "lightest", "Cinza muito claro", true)];
|
|
32
|
+
|
|
33
|
+
/** `#RRGGBB` completo (o `#` é opcional). Hexadecimal curto (`#FFF`) não vale. */
|
|
34
|
+
export const isHexColor = (texto: string): boolean => /^#?[0-9a-fA-F]{6}$/.test((texto || "").trim())
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texto para comparar: minúsculo, sem acento e sem espaço nas pontas. É o que a
|
|
3
|
+
* busca do `IconPicker` usa para achar "usuario" em "Usuário".
|
|
4
|
+
*/
|
|
5
|
+
export const semAcento = (texto: string): string => String(texto || "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim()
|