@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 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