@tree-ia/design-system 1.3.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/README.md +789 -0
- package/dist/index.d.mts +1069 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2927 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
# @tree-ia/design-system
|
|
2
|
+
|
|
3
|
+
Biblioteca de componentes React compartilhada para os dashboards da Tree IA (EaíGarçom, EaíPrefeito, MeuConstrutor, etc.). Todos os componentes são temáveis via CSS custom properties, construídos com Tailwind CSS v4 e publicados no GitHub Packages.
|
|
4
|
+
|
|
5
|
+
## Instalação
|
|
6
|
+
|
|
7
|
+
### 1. Configurar o registry
|
|
8
|
+
|
|
9
|
+
Crie um `.npmrc` na raiz do projeto consumidor com o registry e o token de autenticação:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
@tree-ia:registry=https://npm.pkg.github.com
|
|
13
|
+
//npm.pkg.github.com/:_authToken=SEU_GITHUB_TOKEN
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Substitua `SEU_GITHUB_TOKEN` por um Personal Access Token (classic) com o scope `read:packages`.
|
|
17
|
+
|
|
18
|
+
### 2. Instalar
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @tree-ia/design-system
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Peer dependencies
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
29
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
30
|
+
"tailwindcss": "^4.0.0"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Setup
|
|
37
|
+
|
|
38
|
+
### 1. Configurar o Tailwind CSS (obrigatório)
|
|
39
|
+
|
|
40
|
+
Os componentes usam classes do Tailwind CSS. Como a lib não pré-compila o CSS do Tailwind (seguindo o [padrão oficial para libs Tailwind v4](https://github.com/tailwindlabs/tailwindcss/discussions/18545)), você precisa adicionar **duas linhas** no CSS principal do seu projeto (ex: `globals.css`):
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
@import "tailwindcss";
|
|
44
|
+
|
|
45
|
+
/* Aponta o Tailwind para o dist da lib, gerando as utility classes dos componentes */
|
|
46
|
+
@source "../../node_modules/@tree-ia/design-system/dist";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> **Por que `@source`?** O Tailwind v4 ignora `node_modules` por padrão. A diretiva `@source` faz ele escanear o JS compilado da lib e gerar apenas as classes usadas pelos componentes, usando o tema do **seu** projeto. Essa é a mesma abordagem usada por libs como [HeroUI](https://www.heroui.com/docs/guide/tailwind-v4).
|
|
50
|
+
|
|
51
|
+
### 2. Importar o CSS de animações
|
|
52
|
+
|
|
53
|
+
No layout raiz (ex: `layout.tsx` ou `_app.tsx`):
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import "@tree-ia/design-system/styles.css";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Este arquivo contém apenas keyframes e classes de animação (spinners, toasts, transições). Não inclui Tailwind, portanto não conflita com o seu tema.
|
|
60
|
+
|
|
61
|
+
### 3. Configurar o dark mode (Tailwind v4)
|
|
62
|
+
|
|
63
|
+
A lib usa a classe `.dark` no `<html>` para dark mode. Adicione ao seu CSS:
|
|
64
|
+
|
|
65
|
+
```css
|
|
66
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4. Envolver com o DashboardProvider
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { DashboardProvider } from "@tree-ia/design-system";
|
|
73
|
+
|
|
74
|
+
const config = {
|
|
75
|
+
name: "MeuProjeto",
|
|
76
|
+
colors: {
|
|
77
|
+
primary: "#ff521d",
|
|
78
|
+
surface: "#FFFFFF",
|
|
79
|
+
background: "#F2F2F2",
|
|
80
|
+
// demais cores usam os defaults se omitidas
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default function Layout({ children }) {
|
|
85
|
+
return (
|
|
86
|
+
<DashboardProvider config={config}>
|
|
87
|
+
{children}
|
|
88
|
+
</DashboardProvider>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Pronto! Todos os componentes agora respondem ao seu tema.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Tema e Cores
|
|
98
|
+
|
|
99
|
+
O `DashboardProvider` injeta CSS custom properties no `:root`. Todos os componentes referenciam essas variáveis. Você pode passar apenas as cores que quiser sobrescrever — o resto usa os defaults.
|
|
100
|
+
|
|
101
|
+
### ThemeColors
|
|
102
|
+
|
|
103
|
+
| Propriedade | CSS Variable | Default | Uso |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| `primary` | `--dashboard-primary` | `#37A501` | Botões, links ativos, switches, foco |
|
|
106
|
+
| `secondary` | `--dashboard-secondary` | `#f0f0f0` | Cor de apoio (disponível para o consumidor) |
|
|
107
|
+
| `background` | `--dashboard-background` | `#F2F2F2` | Fundo da página, sidebar |
|
|
108
|
+
| `surface` | `--dashboard-surface` | `#FFFFFF` | Fundo de cards, modais, inputs, tabelas |
|
|
109
|
+
| `textPrimary` | `--dashboard-text-primary` | `#2d2d2d` | Textos principais, títulos |
|
|
110
|
+
| `textSecondary` | `--dashboard-text-secondary` | `#6b7280` | Textos secundários, borders, placeholders |
|
|
111
|
+
| `statusSuccess` | `--dashboard-status-success` | `#10B981` | Toasts e badges de sucesso |
|
|
112
|
+
| `statusDanger` | `--dashboard-status-danger` | `#EF4444` | Erros, botão danger, validação |
|
|
113
|
+
| `statusWarning` | `--dashboard-status-warning` | `#F59E0B` | Toasts e badges de aviso |
|
|
114
|
+
| `statusInfo` | `--dashboard-status-info` | `#3B82F6` | Toasts e badges informativos |
|
|
115
|
+
| `statusNeutral` | `--dashboard-status-neutral` | `#6B7280` | Badges neutros |
|
|
116
|
+
|
|
117
|
+
### Exemplo de tema completo
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
const eaiGarcom = {
|
|
121
|
+
name: "EaiGarcom",
|
|
122
|
+
colors: {
|
|
123
|
+
primary: "#ff521d",
|
|
124
|
+
secondary: "#faf2e0",
|
|
125
|
+
background: "#F2F2F2",
|
|
126
|
+
surface: "#FFFFFF",
|
|
127
|
+
textPrimary: "#2d2d2d",
|
|
128
|
+
textSecondary: "#6b7280",
|
|
129
|
+
statusSuccess: "#10B981",
|
|
130
|
+
statusDanger: "#EF4444",
|
|
131
|
+
statusWarning: "#F59E0B",
|
|
132
|
+
statusInfo: "#3B82F6",
|
|
133
|
+
statusNeutral: "#6B7280",
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Componentes
|
|
141
|
+
|
|
142
|
+
### Button
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { Button } from "@tree-ia/design-system";
|
|
146
|
+
|
|
147
|
+
<Button variant="primary" size="md" onClick={handleClick}>
|
|
148
|
+
Salvar
|
|
149
|
+
</Button>
|
|
150
|
+
|
|
151
|
+
<Button variant="danger" isLoading>
|
|
152
|
+
Excluindo...
|
|
153
|
+
</Button>
|
|
154
|
+
|
|
155
|
+
<Button variant="ghost" icon={<Trash size={16} />} />
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| Prop | Tipo | Default | Descrição |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| `variant` | `"primary" \| "secondary" \| "danger" \| "ghost"` | `"primary"` | Estilo visual |
|
|
161
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tamanho |
|
|
162
|
+
| `isLoading` | `boolean` | `false` | Mostra spinner e desabilita |
|
|
163
|
+
| `icon` | `ReactNode` | - | Ícone. Se sem children, renderiza como botão ícone |
|
|
164
|
+
| `iconPosition` | `"left" \| "right"` | `"left"` | Posição do ícone |
|
|
165
|
+
|
|
166
|
+
Herda todos os atributos nativos de `<button>`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### Input
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { Input } from "@tree-ia/design-system";
|
|
174
|
+
|
|
175
|
+
<Input
|
|
176
|
+
label="Email"
|
|
177
|
+
type="email"
|
|
178
|
+
placeholder="seu@email.com"
|
|
179
|
+
error="Email inválido"
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
<Input placeholder="Buscar...">
|
|
183
|
+
<Search className="h-4 w-4" />
|
|
184
|
+
</Input>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
| Prop | Tipo | Default | Descrição |
|
|
188
|
+
|---|---|---|---|
|
|
189
|
+
| `label` | `string` | - | Label acima do input |
|
|
190
|
+
| `error` | `string` | - | Mensagem de erro abaixo (borda vermelha) |
|
|
191
|
+
| `children` | `ReactNode` | - | Elemento posicionado à direita dentro do input |
|
|
192
|
+
|
|
193
|
+
Herda todos os atributos nativos de `<input>`. Aceita `ref` via `forwardRef`.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### Dropdown
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { Dropdown } from "@tree-ia/design-system";
|
|
201
|
+
|
|
202
|
+
const options = [
|
|
203
|
+
{ value: "sp", label: "São Paulo" },
|
|
204
|
+
{ value: "rj", label: "Rio de Janeiro" },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
<Dropdown
|
|
208
|
+
label="Estado"
|
|
209
|
+
options={options}
|
|
210
|
+
value={selected}
|
|
211
|
+
onChange={setSelected}
|
|
212
|
+
placeholder="Selecione..."
|
|
213
|
+
/>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
| Prop | Tipo | Default | Descrição |
|
|
217
|
+
|---|---|---|---|
|
|
218
|
+
| `options` | `DropdownOption[]` | obrigatório | Opções do dropdown |
|
|
219
|
+
| `value` | `string` | - | Valor selecionado |
|
|
220
|
+
| `onChange` | `(value: string) => void` | obrigatório | Callback de seleção |
|
|
221
|
+
| `label` | `string` | - | Label acima |
|
|
222
|
+
| `placeholder` | `string` | `"Selecione uma opção"` | Texto quando vazio |
|
|
223
|
+
| `variant` | `"default" \| "underline" \| "simple" \| "compact"` | `"default"` | Estilo visual |
|
|
224
|
+
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | Tamanho |
|
|
225
|
+
| `error` | `string` | - | Mensagem de erro |
|
|
226
|
+
| `disabled` | `boolean` | `false` | Desabilita |
|
|
227
|
+
| `fullWidth` | `boolean` | `false` | Ocupa 100% da largura |
|
|
228
|
+
| `icon` | `ReactNode` | - | Ícone à esquerda |
|
|
229
|
+
| `isActive` | `boolean` | `false` | Destaca com borda primary |
|
|
230
|
+
|
|
231
|
+
O menu abre via portal no `document.body` (z-index 9999) e fecha ao clicar fora, scroll ou resize.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Table
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
import { Table } from "@tree-ia/design-system";
|
|
239
|
+
|
|
240
|
+
const columns = [
|
|
241
|
+
{ key: "name", header: "Nome", render: (item) => item.name },
|
|
242
|
+
{ key: "email", header: "Email", render: (item) => item.email },
|
|
243
|
+
{
|
|
244
|
+
key: "status",
|
|
245
|
+
header: "Status",
|
|
246
|
+
align: "center",
|
|
247
|
+
render: (item) => <BadgeStatus label={item.status} variant="success" />,
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
<Table
|
|
252
|
+
columns={columns}
|
|
253
|
+
data={users}
|
|
254
|
+
keyExtractor={(user) => user.id}
|
|
255
|
+
onRowClick={(user) => router.push(`/users/${user.id}`)}
|
|
256
|
+
isLoading={loading}
|
|
257
|
+
emptyMessage="Nenhum usuário encontrado"
|
|
258
|
+
/>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| Prop | Tipo | Default | Descrição |
|
|
262
|
+
|---|---|---|---|
|
|
263
|
+
| `columns` | `TableColumn<T>[]` | obrigatório | Definição das colunas |
|
|
264
|
+
| `data` | `T[]` | obrigatório | Array de dados |
|
|
265
|
+
| `keyExtractor` | `(item: T) => string` | obrigatório | Chave única por linha |
|
|
266
|
+
| `onRowClick` | `(item: T) => void` | - | Callback ao clicar na linha |
|
|
267
|
+
| `isLoading` | `boolean` | `false` | Mostra skeleton de loading |
|
|
268
|
+
| `emptyMessage` | `string` | `"Nenhum registro encontrado"` | Mensagem quando vazio |
|
|
269
|
+
| `loadingComponent` | `ReactNode` | - | Substitui o skeleton padrão |
|
|
270
|
+
| `emptyComponent` | `ReactNode` | - | Substitui o empty state padrão |
|
|
271
|
+
|
|
272
|
+
Exporta também: `TableHeader`, `TableBody`, `TableSkeleton`, `TableEmpty`.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### Modal
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
import { Modal } from "@tree-ia/design-system";
|
|
280
|
+
|
|
281
|
+
<Modal
|
|
282
|
+
isOpen={open}
|
|
283
|
+
onClose={() => setOpen(false)}
|
|
284
|
+
title="Confirmar exclusão"
|
|
285
|
+
showFooter
|
|
286
|
+
saveButtonText="Excluir"
|
|
287
|
+
saveButtonVariant="danger"
|
|
288
|
+
onSave={handleDelete}
|
|
289
|
+
>
|
|
290
|
+
<p>Tem certeza que deseja excluir este item?</p>
|
|
291
|
+
</Modal>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
| Prop | Tipo | Default | Descrição |
|
|
295
|
+
|---|---|---|---|
|
|
296
|
+
| `isOpen` | `boolean` | obrigatório | Controle de visibilidade |
|
|
297
|
+
| `onClose` | `() => void` | obrigatório | Callback de fechamento |
|
|
298
|
+
| `title` | `string` | `""` | Título no header |
|
|
299
|
+
| `showFooter` | `boolean` | `false` | Mostra footer com botões |
|
|
300
|
+
| `saveButtonText` | `string` | `"Salvar"` | Texto do botão principal |
|
|
301
|
+
| `cancelButtonText` | `string` | `"Cancelar"` | Texto do botão cancelar |
|
|
302
|
+
| `size` | `"small" \| "medium" \| "large" \| "largeXl" \| "extraLarge"` | `"medium"` | Largura máxima |
|
|
303
|
+
| `saveButtonVariant` | `"primary" \| "secondary" \| "danger" \| "ghost"` | `"primary"` | Variante do botão salvar |
|
|
304
|
+
| `closeOnEscape` | `boolean` | `true` | Fecha com Escape |
|
|
305
|
+
| `closeOnOverlayClick` | `boolean` | `true` | Fecha ao clicar fora |
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### Card
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
import { Card } from "@tree-ia/design-system";
|
|
313
|
+
|
|
314
|
+
<Card
|
|
315
|
+
title="Vendas do Mês"
|
|
316
|
+
subtitle="Atualizado há 5 minutos"
|
|
317
|
+
icon={<BarChart size={20} />}
|
|
318
|
+
headerActions={<Button variant="ghost" size="sm">Ver mais</Button>}
|
|
319
|
+
showDivider
|
|
320
|
+
>
|
|
321
|
+
<p className="text-2xl font-bold">R$ 12.450,00</p>
|
|
322
|
+
</Card>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
| Prop | Tipo | Default | Descrição |
|
|
326
|
+
|---|---|---|---|
|
|
327
|
+
| `title` | `string` | - | Título no header |
|
|
328
|
+
| `subtitle` | `string` | - | Subtítulo abaixo do título |
|
|
329
|
+
| `icon` | `ReactNode` | - | Ícone ao lado do título |
|
|
330
|
+
| `headerActions` | `ReactNode` | - | Ações alinhadas à direita no header |
|
|
331
|
+
| `showDivider` | `boolean` | `false` | Linha divisória abaixo do header |
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### Tabs
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
import { Tabs } from "@tree-ia/design-system";
|
|
339
|
+
|
|
340
|
+
const tabs = [
|
|
341
|
+
{ id: "all", label: "Todos", count: 42 },
|
|
342
|
+
{ id: "active", label: "Ativos", count: 38 },
|
|
343
|
+
{ id: "inactive", label: "Inativos", count: 4, icon: <Archive size={14} /> },
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
<Tabs tabs={tabs} activeTab={tab} onChange={setTab} />
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
| Prop | Tipo | Default | Descrição |
|
|
350
|
+
|---|---|---|---|
|
|
351
|
+
| `tabs` | `Tab[]` | obrigatório | Array de tabs |
|
|
352
|
+
| `activeTab` | `string` | obrigatório | ID da tab ativa |
|
|
353
|
+
| `onChange` | `(tabId: string) => void` | obrigatório | Callback de troca |
|
|
354
|
+
|
|
355
|
+
Cada `Tab`: `{ id, label, count?, icon? }`.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
### DateRangePicker
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
import { DateRangePicker } from "@tree-ia/design-system";
|
|
363
|
+
|
|
364
|
+
<DateRangePicker
|
|
365
|
+
value={dateRange}
|
|
366
|
+
onChange={setDateRange}
|
|
367
|
+
locale="pt"
|
|
368
|
+
/>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
| Prop | Tipo | Default | Descrição |
|
|
372
|
+
|---|---|---|---|
|
|
373
|
+
| `value` | `{ start: Date \| null, end: Date \| null }` | obrigatório | Período selecionado |
|
|
374
|
+
| `onChange` | `(range: DateRange) => void` | obrigatório | Callback |
|
|
375
|
+
| `locale` | `"pt" \| "en"` | `"pt"` | Idioma |
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Title
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
import { Title } from "@tree-ia/design-system";
|
|
383
|
+
|
|
384
|
+
<Title level={1}>Dashboard</Title>
|
|
385
|
+
<Title level={3} size="sm" weight="medium" align="center">Subtítulo</Title>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
| Prop | Tipo | Default | Descrição |
|
|
389
|
+
|---|---|---|---|
|
|
390
|
+
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `1` | Tag HTML (h1-h6) |
|
|
391
|
+
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl" \| "2xl" \| "3xl"` | auto por level | Tamanho visual |
|
|
392
|
+
| `weight` | `"normal" \| "medium" \| "semibold" \| "bold" \| "extrabold"` | `"bold"` | Peso da fonte |
|
|
393
|
+
| `align` | `"left" \| "center" \| "right"` | `"left"` | Alinhamento |
|
|
394
|
+
| `color` | `string` | `textPrimary` | Classe Tailwind ou cor CSS |
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
### ToggleSwitch
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
import { ToggleSwitch } from "@tree-ia/design-system";
|
|
402
|
+
|
|
403
|
+
<ToggleSwitch
|
|
404
|
+
enabled={notifications}
|
|
405
|
+
onChange={setNotifications}
|
|
406
|
+
label="Notificações"
|
|
407
|
+
size="md"
|
|
408
|
+
/>
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
| Prop | Tipo | Default | Descrição |
|
|
412
|
+
|---|---|---|---|
|
|
413
|
+
| `enabled` | `boolean` | obrigatório | Estado on/off |
|
|
414
|
+
| `onChange` | `(enabled: boolean) => void` | obrigatório | Callback |
|
|
415
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tamanho |
|
|
416
|
+
| `label` | `string` | - | Label e aria-label |
|
|
417
|
+
| `disabled` | `boolean` | `false` | Desabilita |
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
### BadgeStatus
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
import { BadgeStatus } from "@tree-ia/design-system";
|
|
425
|
+
|
|
426
|
+
<BadgeStatus label="Ativo" variant="success" />
|
|
427
|
+
<BadgeStatus label="Pendente" variant="warning" size="sm" />
|
|
428
|
+
<BadgeStatus label="Custom" color="#7c3aed" bgColor="#7c3aed20" />
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
| Prop | Tipo | Default | Descrição |
|
|
432
|
+
|---|---|---|---|
|
|
433
|
+
| `label` | `string` | obrigatório | Texto do badge |
|
|
434
|
+
| `variant` | `"success" \| "warning" \| "danger" \| "info" \| "neutral"` | `"neutral"` | Variante de cor |
|
|
435
|
+
| `color` | `string` | - | Cor do texto (sobrescreve variante) |
|
|
436
|
+
| `bgColor` | `string` | - | Cor de fundo (sobrescreve variante) |
|
|
437
|
+
| `size` | `"sm" \| "md"` | `"md"` | Tamanho |
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### Toast
|
|
442
|
+
|
|
443
|
+
```tsx
|
|
444
|
+
import { Toast } from "@tree-ia/design-system";
|
|
445
|
+
|
|
446
|
+
<Toast
|
|
447
|
+
title="Salvo com sucesso"
|
|
448
|
+
subtitle="As alterações foram aplicadas"
|
|
449
|
+
type="success"
|
|
450
|
+
duration={4000}
|
|
451
|
+
onClose={() => {}}
|
|
452
|
+
/>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
| Prop | Tipo | Default | Descrição |
|
|
456
|
+
|---|---|---|---|
|
|
457
|
+
| `title` | `string` | obrigatório | Título |
|
|
458
|
+
| `type` | `"success" \| "error" \| "warning" \| "info"` | `"success"` | Tipo (cor + ícone) |
|
|
459
|
+
| `subtitle` | `string` | - | Subtítulo |
|
|
460
|
+
| `duration` | `number` | `4000` | Duração em ms (0 = sem auto-close) |
|
|
461
|
+
| `showProgress` | `boolean` | `true` | Barra de progresso |
|
|
462
|
+
| `onClose` | `() => void` | obrigatório | Callback de fechamento |
|
|
463
|
+
|
|
464
|
+
Geralmente não se usa `Toast` diretamente — use `useNotifications()`.
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
### Loading
|
|
469
|
+
|
|
470
|
+
```tsx
|
|
471
|
+
import { Loading } from "@tree-ia/design-system";
|
|
472
|
+
|
|
473
|
+
<Loading size="lg" text="Carregando..." />
|
|
474
|
+
<Loading variant="border" color="#ff521d" />
|
|
475
|
+
<Loading fullscreen text="Processando..." />
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
| Prop | Tipo | Default | Descrição |
|
|
479
|
+
|---|---|---|---|
|
|
480
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tamanho (16/32/48px) |
|
|
481
|
+
| `variant` | `"spinner" \| "border"` | `"spinner"` | Estilo do spinner |
|
|
482
|
+
| `text` | `string` | - | Texto abaixo do spinner |
|
|
483
|
+
| `color` | `string` | `primary` | Cor do spinner e texto |
|
|
484
|
+
| `textColor` | `string` | - | Cor independente do texto |
|
|
485
|
+
| `fullscreen` | `boolean` | `false` | Overlay fullscreen |
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
### Pagination
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
import { Pagination } from "@tree-ia/design-system";
|
|
493
|
+
|
|
494
|
+
<Pagination
|
|
495
|
+
currentPage={page}
|
|
496
|
+
totalPages={10}
|
|
497
|
+
totalItems={95}
|
|
498
|
+
itemsPerPage={10}
|
|
499
|
+
onPageChange={setPage}
|
|
500
|
+
onItemsPerPageChange={setPerPage}
|
|
501
|
+
/>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
| Prop | Tipo | Default | Descrição |
|
|
505
|
+
|---|---|---|---|
|
|
506
|
+
| `currentPage` | `number` | obrigatório | Página atual (1-indexed) |
|
|
507
|
+
| `totalPages` | `number` | obrigatório | Total de páginas |
|
|
508
|
+
| `totalItems` | `number` | obrigatório | Total de itens |
|
|
509
|
+
| `itemsPerPage` | `number` | obrigatório | Itens por página |
|
|
510
|
+
| `onPageChange` | `(page: number) => void` | obrigatório | Callback de navegação |
|
|
511
|
+
| `onItemsPerPageChange` | `(n: number) => void` | - | Callback de itens/página (omitir oculta) |
|
|
512
|
+
| `compact` | `boolean` | `false` | Modo compacto |
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
### FormField
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
import { FormField } from "@tree-ia/design-system";
|
|
520
|
+
|
|
521
|
+
<FormField
|
|
522
|
+
label="Nome"
|
|
523
|
+
name="name"
|
|
524
|
+
value={name}
|
|
525
|
+
onChange={setName}
|
|
526
|
+
required
|
|
527
|
+
error={errors.name}
|
|
528
|
+
/>
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
| Prop | Tipo | Default | Descrição |
|
|
532
|
+
|---|---|---|---|
|
|
533
|
+
| `label` | `string` | obrigatório | Label do campo |
|
|
534
|
+
| `name` | `string` | obrigatório | name/id do input |
|
|
535
|
+
| `value` | `string` | obrigatório | Valor controlado |
|
|
536
|
+
| `onChange` | `(value: string) => void` | obrigatório | Callback (já extrai o valor) |
|
|
537
|
+
| `type` | `"text" \| "email" \| "password" \| "number" \| "tel"` | `"text"` | Tipo do input |
|
|
538
|
+
| `error` | `string` | - | Erro de validação |
|
|
539
|
+
| `required` | `boolean` | `false` | Mostra asterisco vermelho |
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
### Sidebar
|
|
544
|
+
|
|
545
|
+
```tsx
|
|
546
|
+
import { Sidebar } from "@tree-ia/design-system";
|
|
547
|
+
import { Home, Users, Settings } from "lucide-react";
|
|
548
|
+
import Link from "next/link"; // ou qualquer framework
|
|
549
|
+
|
|
550
|
+
const menuItems = [
|
|
551
|
+
{ id: "home", label: "Início", href: "/", icon: Home },
|
|
552
|
+
{ id: "users", label: "Usuários", href: "/users", icon: Users },
|
|
553
|
+
{ id: "settings", label: "Config", href: "/settings", icon: Settings },
|
|
554
|
+
];
|
|
555
|
+
|
|
556
|
+
<Sidebar
|
|
557
|
+
menuItems={menuItems}
|
|
558
|
+
logo={<img src="/logo.svg" alt="Logo" />}
|
|
559
|
+
collapsedLogo={<img src="/icon.svg" alt="Logo" />}
|
|
560
|
+
currentPath={pathname}
|
|
561
|
+
linkComponent={Link}
|
|
562
|
+
isCollapsed={collapsed}
|
|
563
|
+
onToggleCollapse={() => setCollapsed(!collapsed)}
|
|
564
|
+
user={{ name: "João", email: "joao@email.com", subtitle: "Admin" }}
|
|
565
|
+
onLogout={handleLogout}
|
|
566
|
+
/>
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
| Prop | Tipo | Default | Descrição |
|
|
570
|
+
|---|---|---|---|
|
|
571
|
+
| `menuItems` | `SidebarMenuItem[]` | obrigatório | Itens de navegação |
|
|
572
|
+
| `logo` | `ReactNode` | obrigatório | Logo expandido |
|
|
573
|
+
| `currentPath` | `string` | obrigatório | Path atual para destaque |
|
|
574
|
+
| `collapsedLogo` | `ReactNode` | - | Logo modo colapsado |
|
|
575
|
+
| `linkComponent` | `ComponentType` | `<a>` | Componente de link (ex: Next.js Link) |
|
|
576
|
+
| `isCollapsed` | `boolean` | `false` | Modo colapsado |
|
|
577
|
+
| `onToggleCollapse` | `() => void` | - | Callback toggle (omitir oculta botão) |
|
|
578
|
+
| `user` | `SidebarUser` | - | Info do usuário no footer |
|
|
579
|
+
| `onUserClick` | `() => void` | - | Callback ao clicar no usuário |
|
|
580
|
+
| `onLogout` | `() => void` | - | Callback logout (omitir oculta botão) |
|
|
581
|
+
| `logoutLabel` | `string` | `"Sair"` | Texto do botão logout |
|
|
582
|
+
|
|
583
|
+
Desktop: sidebar fixa à esquerda (280px / 109px colapsada). Mobile: header fixo no topo com menu dropdown.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
### ThemeSwitcher
|
|
588
|
+
|
|
589
|
+
```tsx
|
|
590
|
+
import { ThemeSwitcher } from "@tree-ia/design-system";
|
|
591
|
+
|
|
592
|
+
<ThemeSwitcher />
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Botão que alterna entre light e dark mode. Usa `useTheme()` internamente. Deve estar dentro do `DashboardProvider`.
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
## Hooks
|
|
600
|
+
|
|
601
|
+
### useTheme
|
|
602
|
+
|
|
603
|
+
```tsx
|
|
604
|
+
import { useTheme } from "@tree-ia/design-system";
|
|
605
|
+
|
|
606
|
+
function MyComponent() {
|
|
607
|
+
const { theme, setTheme, resolvedTheme } = useTheme();
|
|
608
|
+
|
|
609
|
+
return (
|
|
610
|
+
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
|
|
611
|
+
<option value="light">Claro</option>
|
|
612
|
+
<option value="dark">Escuro</option>
|
|
613
|
+
<option value="system">Sistema</option>
|
|
614
|
+
</select>
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
| Retorno | Tipo | Descrição |
|
|
620
|
+
|---|---|---|
|
|
621
|
+
| `theme` | `"light" \| "dark" \| "system"` | Preferência do usuário |
|
|
622
|
+
| `setTheme` | `(theme: Theme) => void` | Altera e persiste no localStorage |
|
|
623
|
+
| `resolvedTheme` | `"light" \| "dark"` | Tema efetivo resolvido |
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
### useNotifications
|
|
628
|
+
|
|
629
|
+
```tsx
|
|
630
|
+
import { useNotifications } from "@tree-ia/design-system";
|
|
631
|
+
|
|
632
|
+
function MyComponent() {
|
|
633
|
+
const { addNotification } = useNotifications();
|
|
634
|
+
|
|
635
|
+
const handleSave = async () => {
|
|
636
|
+
await save();
|
|
637
|
+
addNotification({
|
|
638
|
+
title: "Salvo!",
|
|
639
|
+
subtitle: "Registro atualizado com sucesso",
|
|
640
|
+
type: "success",
|
|
641
|
+
});
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
| Retorno | Tipo | Descrição |
|
|
647
|
+
|---|---|---|
|
|
648
|
+
| `notifications` | `Notification[]` | Lista atual |
|
|
649
|
+
| `addNotification` | `(n: Omit<Notification, "id">) => void` | Adiciona toast |
|
|
650
|
+
| `removeNotification` | `(id: string) => void` | Remove por ID |
|
|
651
|
+
| `clearNotifications` | `() => void` | Remove todos |
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
### useLoading
|
|
656
|
+
|
|
657
|
+
```tsx
|
|
658
|
+
import { useLoading } from "@tree-ia/design-system";
|
|
659
|
+
|
|
660
|
+
function MyComponent() {
|
|
661
|
+
const { showLoading, hideLoading } = useLoading();
|
|
662
|
+
|
|
663
|
+
const handleSubmit = async () => {
|
|
664
|
+
showLoading();
|
|
665
|
+
await submitForm();
|
|
666
|
+
hideLoading();
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Mostra um overlay fullscreen com spinner. Útil para operações que bloqueiam toda a interface.
|
|
672
|
+
|
|
673
|
+
---
|
|
674
|
+
|
|
675
|
+
### useConfig
|
|
676
|
+
|
|
677
|
+
```tsx
|
|
678
|
+
import { useConfig } from "@tree-ia/design-system";
|
|
679
|
+
|
|
680
|
+
function MyComponent() {
|
|
681
|
+
const config = useConfig();
|
|
682
|
+
console.log(config.name); // "EaiGarcom"
|
|
683
|
+
console.log(config.colors.primary); // "#ff521d"
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Retorna o `DashboardConfig` completo (com deep merge dos defaults).
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## CSS Utilities
|
|
692
|
+
|
|
693
|
+
O `styles.css` contém apenas keyframes e classes de animação — **nenhum CSS do Tailwind**. As utility classes do Tailwind são geradas pelo seu projeto via `@source` (veja [Setup](#setup)).
|
|
694
|
+
|
|
695
|
+
```tsx
|
|
696
|
+
import "@tree-ia/design-system/styles.css";
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
| Classe | Descrição |
|
|
700
|
+
|---|---|
|
|
701
|
+
| `dashboard-animate-fade-in` | Fade in (0.3s) |
|
|
702
|
+
| `dashboard-animate-fade-out` | Fade out (0.3s) |
|
|
703
|
+
| `dashboard-animate-slide-down` | Slide de cima (0.2s) |
|
|
704
|
+
| `dashboard-animate-slide-up` | Slide para cima (0.2s) |
|
|
705
|
+
| `dashboard-animate-slide-left` | Slide da direita (0.3s) |
|
|
706
|
+
| `dashboard-animate-slide-right` | Slide para direita (0.3s) |
|
|
707
|
+
| `dashboard-animate-expand` | Expandir (max-height 0 -> 1000px) |
|
|
708
|
+
| `dashboard-animate-collapse` | Colapsar (max-height 1000px -> 0) |
|
|
709
|
+
| `dashboard-animate-shimmer` | Shimmer para skeletons |
|
|
710
|
+
| `dashboard-animate-bounce-dot` | Bounce pulsante |
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Configuração Avançada
|
|
715
|
+
|
|
716
|
+
### ComponentsConfig
|
|
717
|
+
|
|
718
|
+
Além de cores, você pode configurar comportamentos padrão dos componentes:
|
|
719
|
+
|
|
720
|
+
```tsx
|
|
721
|
+
const config = {
|
|
722
|
+
colors: { ... },
|
|
723
|
+
components: {
|
|
724
|
+
modal: {
|
|
725
|
+
closeOnEscape: true,
|
|
726
|
+
closeOnOverlayClick: true,
|
|
727
|
+
},
|
|
728
|
+
toast: {
|
|
729
|
+
duration: 3000,
|
|
730
|
+
position: "top-right", // "top-left" | "bottom-right" | "bottom-left"
|
|
731
|
+
},
|
|
732
|
+
notification: {
|
|
733
|
+
duration: 5000,
|
|
734
|
+
},
|
|
735
|
+
pagination: {
|
|
736
|
+
itemsPerPageOptions: [10, 20, 30, 50],
|
|
737
|
+
defaultItemsPerPage: 10,
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### createConfig
|
|
744
|
+
|
|
745
|
+
Para criar configs programaticamente com type safety:
|
|
746
|
+
|
|
747
|
+
```tsx
|
|
748
|
+
import { createConfig } from "@tree-ia/design-system";
|
|
749
|
+
|
|
750
|
+
const config = createConfig({
|
|
751
|
+
name: "MeuApp",
|
|
752
|
+
colors: { primary: "#ff521d" },
|
|
753
|
+
// tudo que não for passado usa os defaults
|
|
754
|
+
});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
---
|
|
758
|
+
|
|
759
|
+
## Desenvolvimento
|
|
760
|
+
|
|
761
|
+
```bash
|
|
762
|
+
# Instalar dependências
|
|
763
|
+
npm install
|
|
764
|
+
|
|
765
|
+
# Rodar Storybook
|
|
766
|
+
npm run storybook
|
|
767
|
+
|
|
768
|
+
# Build da lib
|
|
769
|
+
npm run build
|
|
770
|
+
|
|
771
|
+
# Type check
|
|
772
|
+
npm run typecheck
|
|
773
|
+
|
|
774
|
+
# Formatar código
|
|
775
|
+
npm run format
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Storybook
|
|
779
|
+
|
|
780
|
+
O Storybook inclui um seletor de temas na toolbar (EaíGarçom, EaíPrefeito, MeuConstrutor) para visualizar componentes em diferentes marcas.
|
|
781
|
+
|
|
782
|
+
### Publicação
|
|
783
|
+
|
|
784
|
+
A publicação é automática via GitHub Actions. Ao criar uma Release no GitHub:
|
|
785
|
+
|
|
786
|
+
1. Bump a versão: `npm version patch|minor|major`
|
|
787
|
+
2. Push com tag: `git push origin main --tags`
|
|
788
|
+
3. Crie a Release no GitHub a partir da tag
|
|
789
|
+
4. O workflow publica automaticamente no GitHub Packages
|