@1doc/1ds-react 0.0.1 → 2.7.1

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +667 -0
  2. package/README.md +138 -6
  3. package/package.json +43 -10
  4. package/src/index.ts +161 -16
  5. package/src/teste/AttachmentCard.test.tsx +161 -0
  6. package/src/teste/Chart.test.tsx +233 -0
  7. package/src/teste/Combobox.test.tsx +251 -0
  8. package/src/teste/DataTable.test.tsx +70 -0
  9. package/src/teste/DatePicker.test.tsx +86 -0
  10. package/src/teste/Drawer.test.tsx +265 -0
  11. package/src/teste/Dropdown.test.tsx +217 -0
  12. package/src/teste/Input.test.tsx +64 -0
  13. package/src/teste/Kanban.test.tsx +333 -0
  14. package/src/teste/MultiSelect.test.tsx +73 -0
  15. package/src/teste/Podium.test.tsx +107 -0
  16. package/src/teste/Popover.test.tsx +221 -0
  17. package/src/teste/SegmentedControl.test.tsx +183 -0
  18. package/src/teste/Timeline.test.tsx +100 -0
  19. package/src/teste/Tree.test.tsx +74 -0
  20. package/src/teste/setup.ts +7 -0
  21. package/src/utils/bandeiras.ts +22 -0
  22. package/src/utils/chart.ts +682 -0
  23. package/src/utils/density.ts +27 -0
  24. package/src/utils/overlay-stack.ts +26 -0
  25. package/src/utils/theme.ts +63 -47
  26. package/src/utils/toast.ts +89 -0
  27. package/vitest.config.ts +28 -0
  28. package/dist/components/Alert/Alert.css +0 -188
  29. package/dist/components/Alert/Alert.d.ts +0 -65
  30. package/dist/components/Alert/Alert.js +0 -56
  31. package/dist/components/Avatar/Avatar.css +0 -88
  32. package/dist/components/Avatar/Avatar.d.ts +0 -34
  33. package/dist/components/Avatar/Avatar.js +0 -16
  34. package/dist/components/Avatar/AvatarGroup.d.ts +0 -12
  35. package/dist/components/Avatar/AvatarGroup.js +0 -8
  36. package/dist/components/Badge/Badge.css +0 -155
  37. package/dist/components/Badge/Badge.d.ts +0 -46
  38. package/dist/components/Badge/Badge.js +0 -17
  39. package/dist/components/Button/Button.css +0 -199
  40. package/dist/components/Button/Button.d.ts +0 -23
  41. package/dist/components/Button/Button.js +0 -62
  42. package/dist/components/Checkbox/Checkbox.css +0 -122
  43. package/dist/components/Checkbox/Checkbox.d.ts +0 -21
  44. package/dist/components/Checkbox/Checkbox.js +0 -49
  45. package/dist/components/Divider/Divider.css +0 -81
  46. package/dist/components/Divider/Divider.d.ts +0 -18
  47. package/dist/components/Divider/Divider.js +0 -26
  48. package/dist/components/Grid/Grid.css +0 -154
  49. package/dist/components/Grid/Grid.d.ts +0 -21
  50. package/dist/components/Grid/Grid.js +0 -53
  51. package/dist/components/Icon/Icon.css +0 -41
  52. package/dist/components/Icon/Icon.d.ts +0 -12
  53. package/dist/components/Icon/Icon.js +0 -29
  54. package/dist/components/Input/Input.css +0 -190
  55. package/dist/components/Input/Input.d.ts +0 -32
  56. package/dist/components/Input/Input.js +0 -58
  57. package/dist/components/Link/Link.css +0 -76
  58. package/dist/components/Link/Link.d.ts +0 -15
  59. package/dist/components/Link/Link.js +0 -19
  60. package/dist/components/Logo/Logo.css +0 -32
  61. package/dist/components/Logo/Logo.d.ts +0 -10
  62. package/dist/components/Logo/Logo.js +0 -16
  63. package/dist/components/Modal/Modal.css +0 -155
  64. package/dist/components/Modal/Modal.d.ts +0 -36
  65. package/dist/components/Modal/Modal.js +0 -55
  66. package/dist/components/Radio/Radio.css +0 -114
  67. package/dist/components/Radio/Radio.d.ts +0 -19
  68. package/dist/components/Radio/Radio.js +0 -39
  69. package/dist/components/Select/Select.css +0 -291
  70. package/dist/components/Select/Select.d.ts +0 -38
  71. package/dist/components/Select/Select.js +0 -246
  72. package/dist/components/Spinner/Spinner.css +0 -60
  73. package/dist/components/Spinner/Spinner.d.ts +0 -16
  74. package/dist/components/Spinner/Spinner.js +0 -16
  75. package/dist/components/Switch/Switch.css +0 -127
  76. package/dist/components/Switch/Switch.d.ts +0 -18
  77. package/dist/components/Switch/Switch.js +0 -57
  78. package/dist/components/Tabs/Tab.d.ts +0 -19
  79. package/dist/components/Tabs/Tab.js +0 -76
  80. package/dist/components/Tabs/TabContent.d.ts +0 -15
  81. package/dist/components/Tabs/TabContent.js +0 -11
  82. package/dist/components/Tabs/Tabs.css +0 -124
  83. package/dist/components/Tabs/Tabs.d.ts +0 -20
  84. package/dist/components/Tabs/Tabs.js +0 -14
  85. package/dist/components/Tabs/TabsContent.d.ts +0 -16
  86. package/dist/components/Tabs/TabsContent.js +0 -12
  87. package/dist/components/Tabs/tabs.context.d.ts +0 -2
  88. package/dist/components/Tabs/tabs.context.js +0 -2
  89. package/dist/components/Tag/Tag.css +0 -140
  90. package/dist/components/Tag/Tag.d.ts +0 -63
  91. package/dist/components/Tag/Tag.js +0 -48
  92. package/dist/components/Text/Text.css +0 -1213
  93. package/dist/components/Text/Text.d.ts +0 -70
  94. package/dist/components/Text/Text.js +0 -89
  95. package/dist/components/Textarea/Textarea.css +0 -133
  96. package/dist/components/Textarea/Textarea.d.ts +0 -28
  97. package/dist/components/Textarea/Textarea.js +0 -72
  98. package/dist/index.d.ts +0 -25
  99. package/dist/index.js +0 -27
  100. package/dist/utils/theme.d.ts +0 -30
  101. package/dist/utils/theme.js +0 -106
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @1doc/1ds-react
2
2
 
3
- Biblioteca de componentes React do 1ds Design System.
3
+ Biblioteca de componentes React do 1DS Design System.
4
+
5
+ 📚 **Documentação viva (Storybook):** https://1dsux.vercel.app
4
6
 
5
7
  ## Instalação
6
8
 
@@ -8,17 +10,24 @@ Biblioteca de componentes React do 1ds Design System.
8
10
  pnpm add @1doc/1ds-react @1doc/1ds-tokens
9
11
  ```
10
12
 
13
+ `@1doc/1ds-tokens` é **peerDependency** — o app instala e importa os CSS de
14
+ tokens diretamente (ver abaixo).
15
+
11
16
  ## Configuração
12
17
 
13
- Importe o CSS dos tokens no seu arquivo principal (`App.tsx` ou `main.tsx`):
18
+ > [!IMPORTANT]
19
+ > Importe o CSS dos tokens no ponto de entrada da aplicação (`main.tsx`),
20
+ > **nesta ordem**. Sem esses imports, os componentes ficam sem estilo.
14
21
 
15
22
  ```tsx
16
- import "@1doc/1ds-tokens/dist/css/primitives.css";
17
- import "@1doc/1ds-tokens/dist/css/semantic.css";
18
- import "@1doc/1ds-tokens/dist/css/theme-1doc.css";
19
- import "iconoir/css/iconoir.css";
23
+ import "@1doc/1ds-tokens/css/primitives.css";
24
+ import "@1doc/1ds-tokens/css/semantic.css";
25
+ import "@1doc/1ds-tokens/css/theme-1doc.css"; // tema da sua marca (ver Temas)
20
26
  ```
21
27
 
28
+ Os caminhos `dist/css/...` continuam funcionando, mas prefira os subpaths
29
+ estáveis `css/...` acima.
30
+
22
31
  ## Uso
23
32
 
24
33
  ```tsx
@@ -32,3 +41,126 @@ function App() {
32
41
  );
33
42
  }
34
43
  ```
44
+
45
+ ## Componentes
46
+
47
+ Accordion · Alert · Avatar/AvatarGroup · Badge · Breadcrumb · Button · Card ·
48
+ Checkbox · Chips · ConfirmDialog · DataTable · DatePicker · Divider · Dropdown ·
49
+ EmptyState · FileInput · Grid · Icon · Input · Link · Logo · Modal · MultiSelect ·
50
+ PageHeader · Pagination · PhoneInput · Portal · ProgressBar · Radio · Select ·
51
+ Skeleton · Spinner · Stepper · Switch · Tabs · Tag · Text · Textarea ·
52
+ TimePicker · Toast/ToastStack · Tooltip
53
+
54
+ Props e exemplos de cada componente: Storybook (link acima) ou os `.d.ts`
55
+ (JSDoc em português em todas as props).
56
+
57
+ > [!NOTE]
58
+ > As interfaces de props são **estritas** (sem `[key: string]: any`): typo de
59
+ > prop é erro de compilação. Atributos arbitrários (`data-*` etc.) **não são
60
+ > repassados ao DOM** pelos componentes — antes compilavam mas eram
61
+ > silenciosamente descartados; agora o TypeScript avisa.
62
+
63
+ ## Ícones
64
+
65
+ Os ícones **internos** dos componentes (chevrons, calendário, setas de
66
+ ordenação, ×, etc.) funcionam **out-of-the-box** — cada componente importa
67
+ seus glifos diretamente (tree-shakeable, só o que é usado entra no bundle).
68
+
69
+ Para ícones do **seu app** (`<Icon icon="...">`, `startIcon`, `Tab.icon`...),
70
+ há dois caminhos:
71
+
72
+ ```tsx
73
+ // 1) Recomendado: objeto importado (tree-shakeable, typo = erro de compilação)
74
+ import { faHouse } from "@fortawesome/free-solid-svg-icons";
75
+ <Icon icon={faHouse} />;
76
+
77
+ // 2) Por nome (string): exige registrar os ícones na library UMA vez no bootstrap
78
+ import { library } from "@1doc/1ds-react";
79
+ import { faHouse, faGear } from "@fortawesome/free-solid-svg-icons";
80
+ library.add(faHouse, faGear);
81
+ <Icon icon="house" />;
82
+ ```
83
+
84
+ > [!WARNING]
85
+ > Evite `library.add(fas)` (pacote inteiro): adiciona ~900 kB ao bundle.
86
+ > Registre apenas os ícones que o app usa, ou prefira objetos importados.
87
+ > Use packs FontAwesome na faixa **6.x** (compatível com o svg-core do DS).
88
+
89
+ ## Temas
90
+
91
+ Tema via atributo `data-theme` no `<html>`. Utilitários em `@1doc/1ds-react`:
92
+ `setTheme` · `getTheme` · `initTheme` · `onThemeChange` · `registerTheme` ·
93
+ `getAvailableThemes`.
94
+
95
+ | Marca | Light (arquivo / setTheme) | Dark |
96
+ |---|---|---|
97
+ | 1Doc | `theme-1doc.css` / `"1doc"` | `theme-1doc-dark.css` / `"1doc-dark"` |
98
+ | Green | `theme-1ds-green.css` / `"1ds-green"` | `theme-1ds-green-dark.css` / `"1ds-green-dark"` |
99
+ | Orange | `theme-1ds-orange.css` / `"1ds-orange"` | `theme-1ds-orange-dark.css` / `"1ds-orange-dark"` |
100
+ | Purple | `theme-1ds-purple.css` / `"1ds-purple"` | `theme-1ds-purple-dark.css` / `"1ds-purple-dark"` |
101
+ | Red | `theme-1ds-red.css` / `"1ds-red"` | `theme-1ds-red-dark.css` / `"1ds-red-dark"` |
102
+ | Yellow | `theme-1ds-yellow.css` / `"1ds-yellow"` | `theme-1ds-yellow-dark.css` / `"1ds-yellow-dark"` |
103
+
104
+ Alto contraste (acessibilidade, fora da matriz de marcas): `theme-1doc-high-contrast.css` /
105
+ `"1doc-high-contrast"` — fundo preto, texto branco, interativo amarelo. O DS não o aplica
106
+ sozinho; ver a seção "Alto contraste" do README de `@1doc/1ds-tokens` para seguir
107
+ `prefers-contrast: more`.
108
+
109
+ > O CSS do tema escolhido precisa estar **importado** para o `setTheme` ter
110
+ > efeito. Todos os temas podem ser carregados juntos (cada um é escopado em
111
+ > `[data-theme="..."]`) — a troca em runtime é atômica.
112
+
113
+ ### Tema de marca própria (white-label)
114
+
115
+ Crie um CSS com `[data-theme="minha-marca"]` sobrescrevendo os 25 tokens
116
+ `--umds-theme-*` (contrato idêntico aos 12 temas oficiais — o mínimo para a
117
+ cor de marca são os 4 `primary-*`), carregue-o após o `semantic.css` e
118
+ registre o tema para o utilitário aceitá-lo (validação + persistência):
119
+
120
+ ```tsx
121
+ import { registerTheme, initTheme, setTheme } from "@1doc/1ds-react";
122
+
123
+ registerTheme("minha-marca");
124
+ registerTheme("minha-marca-dark");
125
+ initTheme(); // restaura tema persistido (inclusive custom, já registrado)
126
+ setTheme("minha-marca");
127
+ ```
128
+
129
+ ## Formulários
130
+
131
+ Todos os campos participam do submit nativo (`<form>` + `FormData`) via prop
132
+ `name` — Select/DatePicker/TimePicker/MultiSelect emitem hidden inputs;
133
+ PhoneInput emite também `{name}_ddi`; FileInput mantém os `File` no input.
134
+ Contrato de validação uniforme: `invalid`/`errorMessage`/`helperText`
135
+ (com `aria-describedby`).
136
+
137
+ Em viewports ≤640px os campos de texto sobem automaticamente para fonte 16px
138
+ (evita o zoom do Safari iOS ao focar).
139
+
140
+ ## Modo touch (densidade)
141
+
142
+ Para apps mobile/touch, ative o modo touch — os alvos de toque dos controles
143
+ interativos (botões, campos, checkboxes, paginação, calendário…) sobem para
144
+ ≥44px, aumentando controle e espaçamento juntos:
145
+
146
+ ```tsx
147
+ import { setDensity } from "@1doc/1ds-react";
148
+
149
+ setDensity("touch"); // <html data-density="touch">
150
+ setDensity("default"); // volta ao padrão desktop
151
+ ```
152
+
153
+ Elementos inline compactos (Tag, Chips) mantêm o tamanho — em contextos touch,
154
+ dê espaçamento entre eles no layout.
155
+
156
+ ## Toasts
157
+
158
+ ```tsx
159
+ import { ToastStack, toast } from "@1doc/1ds-react";
160
+
161
+ // 1× na raiz do app:
162
+ <ToastStack position="top-right" />;
163
+
164
+ // em qualquer handler:
165
+ toast({ message: "Salvo!", variant: "positive" });
166
+ ```
package/package.json CHANGED
@@ -1,26 +1,59 @@
1
1
  {
2
2
  "name": "@1doc/1ds-react",
3
- "version": "0.0.1",
3
+ "version": "2.7.1",
4
4
  "main": "dist/index.js",
5
+ "sideEffects": [
6
+ "**/*.css"
7
+ ],
5
8
  "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./utils/theme": {
15
+ "types": "./dist/utils/theme.d.ts",
16
+ "default": "./dist/utils/theme.js"
17
+ },
18
+ "./utils/toast": {
19
+ "types": "./dist/utils/toast.d.ts",
20
+ "default": "./dist/utils/toast.js"
21
+ },
22
+ "./dist/*": "./dist/*",
23
+ "./src/*": "./src/*",
24
+ "./package.json": "./package.json"
25
+ },
6
26
  "dependencies": {
7
- "iconoir": "^7.11.0",
8
- "@1doc/1ds-tokens": "0.0.1"
27
+ "@floating-ui/dom": "^1.7.6",
28
+ "@fortawesome/fontawesome-svg-core": "^6.7.2",
29
+ "@fortawesome/free-regular-svg-icons": "^6.7.2",
30
+ "@fortawesome/free-solid-svg-icons": "^6.5.0"
31
+ },
32
+ "peerDependencies": {
33
+ "@1doc/1ds-tokens": ">=0.1.0",
34
+ "react": ">=18",
35
+ "react-dom": ">=18"
9
36
  },
10
37
  "devDependencies": {
38
+ "@testing-library/jest-dom": "^6.9.1",
39
+ "@testing-library/react": "^16.3.3",
40
+ "@testing-library/user-event": "^14.6.1",
11
41
  "@types/react": "^18.0.0",
42
+ "@types/react-dom": "^18.0.0",
43
+ "jsdom": "^26.1.0",
12
44
  "react": "^18.0.0",
45
+ "react-dom": "^18.0.0",
13
46
  "typescript": "^5.0.0",
14
- "@1doc/1ds-core": "0.0.1"
15
- },
16
- "peerDependencies": {
17
- "react": ">=18"
47
+ "vitest": "^3.2.7",
48
+ "@1doc/1ds-core": "2.7.2",
49
+ "@1doc/1ds-tokens": "0.4.1"
18
50
  },
19
51
  "publishConfig": {
20
- "access": "public",
21
- "registry": "https://registry.npmjs.org/"
52
+ "access": "public"
22
53
  },
23
54
  "scripts": {
24
- "build": "tsc && node scripts/postbuild.js"
55
+ "build": "tsc && node scripts/postbuild.js",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest"
25
58
  }
26
59
  }
package/src/index.ts CHANGED
@@ -1,28 +1,173 @@
1
1
  // Theme utilities
2
2
  export * from "./utils/theme";
3
3
 
4
+ // Fila imperativa de toasts (toast/dismissToast/subscribeToasts) — host: <ToastStack/>
5
+ export * from "./utils/toast";
6
+ // Densidade global (modo touch ≥44px via data-density)
7
+ export * from "./utils/density";
8
+ // FontAwesome
9
+ export { library } from "@fortawesome/fontawesome-svg-core";
10
+
4
11
  // Components
5
- export { default as Button } from "./components/Button/Button";
6
- export { default as Icon } from "./components/Icon/Icon";
7
- export { default as Spinner } from "./components/Spinner/Spinner";
8
- export { default as Divider } from "./components/Divider/Divider";
9
- export { default as Badge } from "./components/Badge/Badge";
12
+ export { default as Accordion } from "./components/Accordion/Accordion";
13
+ export { default as AILabel } from "./components/AILabel/AILabel";
14
+ export { default as AIContent } from "./components/AIContent/AIContent";
15
+ export { default as ConfirmAIAction } from "./components/ConfirmAIAction/ConfirmAIAction";
16
+ export { default as Alert } from "./components/Alert/Alert";
17
+ export { default as AppHeader } from "./components/AppHeader/AppHeader";
18
+ export { default as AppHeaderAction } from "./components/AppHeaderAction/AppHeaderAction";
19
+ export { default as AppNavItem } from "./components/AppNavItem/AppNavItem";
20
+ export { default as AppShell } from "./components/AppShell/AppShell";
21
+ export { default as AppSidebar } from "./components/AppSidebar/AppSidebar";
22
+ export { default as AppSubheader } from "./components/AppSubheader/AppSubheader";
23
+ export { default as AttachmentCard } from "./components/AttachmentCard/AttachmentCard";
10
24
  export { default as Avatar } from "./components/Avatar/Avatar";
11
25
  export { default as AvatarGroup } from "./components/Avatar/AvatarGroup";
12
- export { default as Logo } from "./components/Logo/Logo";
13
- export { default as Link } from "./components/Link/Link";
14
- export { default as Switch } from "./components/Switch/Switch";
26
+ export { default as Badge } from "./components/Badge/Badge";
27
+ export { default as BarChart } from "./components/Chart/BarChart";
28
+ export { default as BottomNavigation } from "./components/BottomNavigation/BottomNavigation";
29
+ export { default as BottomSheet } from "./components/BottomSheet/BottomSheet";
30
+ export { default as Box } from "./components/Box/Box";
31
+ export { default as Breadcrumb } from "./components/Breadcrumb/Breadcrumb";
32
+ export { default as Button } from "./components/Button/Button";
33
+ export { default as Card } from "./components/Card/Card";
15
34
  export { default as Checkbox } from "./components/Checkbox/Checkbox";
16
- export { default as Radio } from "./components/Radio/Radio";
17
- export { default as Input } from "./components/Input/Input";
35
+ export { default as Chips } from "./components/Chips/Chips";
36
+ export { default as Combobox } from "./components/Combobox/Combobox";
37
+ export { default as ConfirmDialog } from "./components/ConfirmDialog/ConfirmDialog";
38
+ export { default as Container } from "./components/Container/Container";
39
+ export { default as DataTable } from "./components/DataTable/DataTable";
40
+ export { default as DatePicker } from "./components/DatePicker/DatePicker";
41
+ export { default as Divider } from "./components/Divider/Divider";
42
+ export { default as DonutChart } from "./components/Chart/DonutChart";
43
+ export { default as Drawer } from "./components/Drawer/Drawer";
44
+ export { default as Dropdown } from "./components/Dropdown/Dropdown";
45
+ export { default as EmptyState } from "./components/EmptyState/EmptyState";
46
+ export { default as FileInput } from "./components/FileInput/FileInput";
18
47
  export { default as Grid } from "./components/Grid/Grid";
19
- export { default as Textarea } from "./components/Textarea/Textarea";
48
+ export { default as Icon } from "./components/Icon/Icon";
49
+ export { default as Input } from "./components/Input/Input";
50
+ export { default as Kanban } from "./components/Kanban/Kanban";
51
+ export { default as KanbanCard } from "./components/Kanban/KanbanCard";
52
+ export { default as KanbanColumn } from "./components/Kanban/KanbanColumn";
53
+ export { default as Link } from "./components/Link/Link";
54
+ export { default as LineChart } from "./components/Chart/LineChart";
55
+ export { default as Logo } from "./components/Logo/Logo";
56
+ export { default as Modal } from "./components/Modal/Modal";
57
+ export { default as MultiSelect } from "./components/MultiSelect/MultiSelect";
58
+ export { default as PageHeader } from "./components/PageHeader/PageHeader";
59
+ export { default as Pagination } from "./components/Pagination/Pagination";
60
+ export { default as PhoneInput } from "./components/PhoneInput/PhoneInput";
61
+ export { default as Podium } from "./components/Podium/Podium";
62
+ export { default as ProgressBar } from "./components/ProgressBar/ProgressBar";
63
+ export { default as Radio } from "./components/Radio/Radio";
64
+ export { default as RadioGroup } from "./components/Radio/RadioGroup";
65
+ export { default as SegmentedControl } from "./components/SegmentedControl/SegmentedControl";
20
66
  export { default as Select } from "./components/Select/Select";
21
- export { default as Text } from "./components/Text/Text";
22
- export { default as Tabs } from "./components/Tabs/Tabs";
67
+ export { default as SidePanel } from "./components/SidePanel/SidePanel";
68
+ export { default as SidePanelItem } from "./components/SidePanelItem/SidePanelItem";
69
+ export { default as SidePanelSearch } from "./components/SidePanelSearch/SidePanelSearch";
70
+ export { default as SidePanelSection } from "./components/SidePanelSection/SidePanelSection";
71
+ export { default as Skeleton } from "./components/Skeleton/Skeleton";
72
+ export { default as Spinner } from "./components/Spinner/Spinner";
73
+ export { default as Stack } from "./components/Stack/Stack";
74
+ export { default as StatCard } from "./components/StatCard/StatCard";
75
+ export { default as Stepper } from "./components/Stepper/Stepper";
76
+ export { default as Switch } from "./components/Switch/Switch";
23
77
  export { default as Tab } from "./components/Tabs/Tab";
24
- export { default as TabsContent } from "./components/Tabs/TabsContent";
25
78
  export { default as TabContent } from "./components/Tabs/TabContent";
79
+ export { default as Tabs } from "./components/Tabs/Tabs";
80
+ export { default as TabsContent } from "./components/Tabs/TabsContent";
26
81
  export { default as Tag } from "./components/Tag/Tag";
27
- export { default as Modal } from "./components/Modal/Modal";
28
- export { default as Alert } from "./components/Alert/Alert";
82
+ export { default as Text } from "./components/Text/Text";
83
+ export { default as Textarea } from "./components/Textarea/Textarea";
84
+ export { default as TimePicker } from "./components/TimePicker/TimePicker";
85
+ export { default as Timeline } from "./components/Timeline/Timeline";
86
+ export { default as Toast } from "./components/Toast/Toast";
87
+ export { default as ToastStack } from "./components/Toast/ToastStack";
88
+ export { default as Tooltip } from "./components/Tooltip/Tooltip";
89
+ export { default as Popover } from "./components/Popover/Popover";
90
+ export { default as Portal } from "./components/Portal/Portal";
91
+ export { default as Tree } from "./components/Tree/Tree";
92
+
93
+ // Component Props Types
94
+ export type * from "./components/Accordion/Accordion";
95
+ export type * from "./components/AILabel/AILabel";
96
+ export type * from "./components/AIContent/AIContent";
97
+ export type * from "./components/ConfirmAIAction/ConfirmAIAction";
98
+ export type * from "./components/Alert/Alert";
99
+ export type * from "./components/AppHeader/AppHeader";
100
+ export type * from "./components/AppHeaderAction/AppHeaderAction";
101
+ export type * from "./components/AppNavItem/AppNavItem";
102
+ export type * from "./components/AppShell/AppShell";
103
+ export type * from "./components/AppSidebar/AppSidebar";
104
+ export type * from "./components/AppSubheader/AppSubheader";
105
+ export type * from "./components/AttachmentCard/AttachmentCard";
106
+ export type * from "./components/Avatar/Avatar";
107
+ export type * from "./components/Avatar/AvatarGroup";
108
+ export type * from "./components/Badge/Badge";
109
+ export type * from "./components/Chart/BarChart";
110
+ export type * from "./components/BottomNavigation/BottomNavigation";
111
+ export type * from "./components/BottomSheet/BottomSheet";
112
+ export type * from "./components/Box/Box";
113
+ export type * from "./components/Breadcrumb/Breadcrumb";
114
+ export type * from "./components/Button/Button";
115
+ export type * from "./components/Card/Card";
116
+ export type * from "./components/Checkbox/Checkbox";
117
+ export type * from "./components/Chips/Chips";
118
+ export type * from "./components/Combobox/Combobox";
119
+ export type * from "./components/ConfirmDialog/ConfirmDialog";
120
+ export type * from "./components/Container/Container";
121
+ export type * from "./components/DataTable/DataTable";
122
+ export type * from "./components/DatePicker/DatePicker";
123
+ export type * from "./components/Divider/Divider";
124
+ export type * from "./components/Chart/DonutChart";
125
+ export type * from "./components/Drawer/Drawer";
126
+ export type * from "./components/Dropdown/Dropdown";
127
+ export type * from "./components/EmptyState/EmptyState";
128
+ export type * from "./components/FileInput/FileInput";
129
+ export type * from "./components/Grid/Grid";
130
+ export type * from "./components/Icon/Icon";
131
+ export type * from "./components/Input/Input";
132
+ export type * from "./components/Kanban/Kanban";
133
+ export type * from "./components/Kanban/KanbanCard";
134
+ export type * from "./components/Kanban/KanbanColumn";
135
+ export type * from "./components/Link/Link";
136
+ export type * from "./components/Chart/LineChart";
137
+ export type * from "./components/Logo/Logo";
138
+ export type * from "./components/Modal/Modal";
139
+ export type * from "./components/MultiSelect/MultiSelect";
140
+ export type * from "./components/PageHeader/PageHeader";
141
+ export type * from "./components/Pagination/Pagination";
142
+ export type * from "./components/PhoneInput/PhoneInput";
143
+ export type * from "./components/Podium/Podium";
144
+ export type * from "./components/ProgressBar/ProgressBar";
145
+ export type * from "./components/Radio/Radio";
146
+ export type * from "./components/Radio/RadioGroup";
147
+ export type * from "./components/SegmentedControl/SegmentedControl";
148
+ export type * from "./components/Select/Select";
149
+ export type * from "./components/SidePanel/SidePanel";
150
+ export type * from "./components/SidePanelItem/SidePanelItem";
151
+ export type * from "./components/SidePanelSearch/SidePanelSearch";
152
+ export type * from "./components/SidePanelSection/SidePanelSection";
153
+ export type * from "./components/Skeleton/Skeleton";
154
+ export type * from "./components/Spinner/Spinner";
155
+ export type * from "./components/Stack/Stack";
156
+ export type * from "./components/StatCard/StatCard";
157
+ export type * from "./components/Stepper/Stepper";
158
+ export type * from "./components/Switch/Switch";
159
+ export type * from "./components/Tabs/Tab";
160
+ export type * from "./components/Tabs/TabContent";
161
+ export type * from "./components/Tabs/Tabs";
162
+ export type * from "./components/Tabs/TabsContent";
163
+ export type * from "./components/Tag/Tag";
164
+ export type * from "./components/Text/Text";
165
+ export type * from "./components/Textarea/Textarea";
166
+ export type * from "./components/TimePicker/TimePicker";
167
+ export type * from "./components/Timeline/Timeline";
168
+ export type * from "./components/Toast/Toast";
169
+ export type * from "./components/Toast/ToastStack";
170
+ export type * from "./components/Tooltip/Tooltip";
171
+ export type * from "./components/Popover/Popover";
172
+ export type * from "./components/Portal/Portal";
173
+ export type * from "./components/Tree/Tree"
@@ -0,0 +1,161 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { render, screen, waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import AttachmentCard from "../components/AttachmentCard/AttachmentCard";
5
+
6
+ /**
7
+ * O que precisa ser provado:
8
+ * - com `onOpen` existe UM alvo de foco, com nome que diz o que abre;
9
+ * - sem `onOpen` o cartão é leitura — nenhum botão finge ação;
10
+ * - "obrigatório" chega ao leitor de tela como texto, não só como asterisco;
11
+ * - sem `status` ou sem `statusLabel`, o selo não aparece vazio.
12
+ */
13
+
14
+ describe("AttachmentCard · abrir", () => {
15
+ it("o nome do arquivo é o único botão, e o clique chama onOpen", async () => {
16
+ const usuario = userEvent.setup();
17
+ const aoAbrir = vi.fn();
18
+ render(
19
+ <AttachmentCard
20
+ fileName="cnh frente e verso.pdf"
21
+ documentType="CNH"
22
+ onOpen={aoAbrir}
23
+ />,
24
+ );
25
+
26
+ const botoes = screen.getAllByRole("button");
27
+ expect(botoes).toHaveLength(1);
28
+ expect(botoes[0]).toHaveAccessibleName("Abrir CNH: cnh frente e verso.pdf");
29
+
30
+ await usuario.click(botoes[0]);
31
+ expect(aoAbrir).toHaveBeenCalledTimes(1);
32
+ });
33
+
34
+ it("abre pelo teclado", async () => {
35
+ const usuario = userEvent.setup();
36
+ const aoAbrir = vi.fn();
37
+ render(<AttachmentCard fileName="parecer.pdf" onOpen={aoAbrir} />);
38
+
39
+ await usuario.tab();
40
+ expect(screen.getByRole("button", { name: "Abrir parecer.pdf" })).toHaveFocus();
41
+ await usuario.keyboard("{Enter}");
42
+ expect(aoAbrir).toHaveBeenCalledTimes(1);
43
+ });
44
+
45
+ it("sem onOpen não há botão", () => {
46
+ render(<AttachmentCard fileName="parecer.pdf" documentType="Parecer" />);
47
+ expect(screen.queryByRole("button")).toBeNull();
48
+ expect(screen.getByText("parecer.pdf")).toBeInTheDocument();
49
+ });
50
+ });
51
+
52
+ describe("AttachmentCard · conteúdo", () => {
53
+ it("obrigatório vira texto para leitor de tela", () => {
54
+ render(<AttachmentCard fileName="rg.pdf" documentType="RG" required />);
55
+ expect(screen.getByText("(obrigatório)")).toBeInTheDocument();
56
+ });
57
+
58
+ it("mostra o selo de situação só com status e rótulo", () => {
59
+ const { rerender } = render(
60
+ <AttachmentCard fileName="t.pdf" status="negative" statusLabel="Rejeitado" />,
61
+ );
62
+ expect(screen.getByText("Rejeitado").closest(".umds-attachment-card__status"))
63
+ .toHaveClass("umds-attachment-card__status--negative");
64
+
65
+ rerender(<AttachmentCard fileName="t.pdf" status="negative" />);
66
+ expect(document.querySelector(".umds-attachment-card__status")).toBeNull();
67
+ });
68
+ });
69
+
70
+ const CONFERENCIA = {
71
+ description: "Dados pessoais confirmados com sucesso.",
72
+ fields: [
73
+ { label: "Nº do CPF", value: "384.221.760-05", copyable: true },
74
+ { label: "Situação cadastral", value: "Regular" },
75
+ ],
76
+ };
77
+
78
+ function renderComPainel(extra: Record<string, any> = {}) {
79
+ return render(
80
+ <AttachmentCard
81
+ fileName="comprovante_cpf.pdf"
82
+ documentType="CPF"
83
+ status="positive"
84
+ statusLabel="Pré-aprovado"
85
+ statusDetails={CONFERENCIA}
86
+ onOpen={() => {}}
87
+ {...extra}
88
+ />,
89
+ );
90
+ }
91
+
92
+ describe("AttachmentCard · painel de conferência", () => {
93
+ it("hover abre sem tirar o foco de onde está, e sair fecha", async () => {
94
+ const usuario = userEvent.setup();
95
+ renderComPainel();
96
+ const selo = screen.getByRole("button", { name: /^Pré-aprovado/ });
97
+
98
+ await usuario.hover(selo);
99
+ const painel = await screen.findByRole("dialog", { name: "comprovante_cpf.pdf" });
100
+ expect(painel).toHaveTextContent("Dados pessoais confirmados com sucesso.");
101
+ expect(painel).toHaveTextContent("384.221.760-05");
102
+ expect(document.activeElement).toBe(document.body);
103
+
104
+ await usuario.unhover(selo);
105
+ await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
106
+ });
107
+
108
+ it("clique fixa o painel, leva o foco para dentro e Esc devolve ao selo", async () => {
109
+ const usuario = userEvent.setup();
110
+ const aoAbrir = vi.fn();
111
+ renderComPainel({ onOpen: aoAbrir });
112
+ const selo = screen.getByRole("button", { name: /^Pré-aprovado/ });
113
+
114
+ await usuario.click(selo);
115
+ expect(selo).toHaveAttribute("aria-expanded", "true");
116
+ // Clicar no selo NÃO abre o arquivo — o selo fica acima do alvo esticado.
117
+ expect(aoAbrir).not.toHaveBeenCalled();
118
+ await waitFor(() =>
119
+ expect(screen.getByRole("button", { name: "Copiar Nº do CPF" })).toHaveFocus(),
120
+ );
121
+
122
+ // Fixo: tirar o ponteiro não fecha.
123
+ await usuario.unhover(selo);
124
+ await new Promise((r) => setTimeout(r, 200));
125
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
126
+
127
+ await usuario.keyboard("{Escape}");
128
+ await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
129
+ expect(selo).toHaveFocus();
130
+ expect(selo).toHaveAttribute("aria-expanded", "false");
131
+ });
132
+
133
+ it("copiar só aparece em campo copiável e avisa o campo", async () => {
134
+ const usuario = userEvent.setup();
135
+ const aoCopiar = vi.fn();
136
+ renderComPainel({ onCopyField: aoCopiar });
137
+
138
+ await usuario.click(screen.getByRole("button", { name: /^Pré-aprovado/ }));
139
+ expect(screen.queryByRole("button", { name: "Copiar Situação cadastral" })).toBeNull();
140
+
141
+ await usuario.click(screen.getByRole("button", { name: "Copiar Nº do CPF" }));
142
+ expect(aoCopiar).toHaveBeenCalledWith(CONFERENCIA.fields[0]);
143
+ expect(screen.getByRole("button", { name: "Nº do CPF copiado" })).toBeInTheDocument();
144
+ });
145
+
146
+ it("clique fora fecha", async () => {
147
+ const usuario = userEvent.setup();
148
+ renderComPainel();
149
+ await usuario.click(screen.getByRole("button", { name: /^Pré-aprovado/ }));
150
+ expect(await screen.findByRole("dialog")).toBeInTheDocument();
151
+
152
+ await usuario.click(document.body);
153
+ await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
154
+ });
155
+
156
+ it("sem statusDetails o selo é só leitura", () => {
157
+ renderComPainel({ statusDetails: undefined });
158
+ expect(screen.queryByRole("button", { name: /^Pré-aprovado/ })).toBeNull();
159
+ expect(screen.getByText("Pré-aprovado")).toBeInTheDocument();
160
+ });
161
+ });