xertica-ui 2.1.2 → 2.1.4
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 +46 -0
- package/README.md +1 -1
- package/bin/cli.ts +1 -1
- package/bin/generate-tokens.ts +13 -7
- package/components/assistant/xertica-assistant/index.ts +2 -0
- package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +97 -0
- package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -0
- package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -0
- package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +86 -0
- package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +77 -0
- package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +573 -0
- package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +65 -0
- package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -0
- package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +98 -0
- package/components/assistant/xertica-assistant/parts/index.ts +16 -0
- package/components/assistant/xertica-assistant/types.ts +139 -0
- package/components/assistant/xertica-assistant/use-assistant.ts +559 -0
- package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +200 -0
- package/components/assistant/xertica-assistant/xertica-assistant.tsx +198 -1460
- package/components/brand/theme-toggle/ThemeToggle.tsx +8 -27
- package/components/hooks/index.ts +3 -0
- package/components/hooks/use-layout-shortcuts.ts +46 -0
- package/components/layout/sidebar/index.ts +2 -0
- package/components/layout/sidebar/sidebar.stories.tsx +160 -8
- package/components/layout/sidebar/sidebar.tsx +606 -497
- package/components/layout/sidebar/use-sidebar.ts +104 -0
- package/components/media/audio-player/AudioPlayer.tsx +131 -206
- package/components/media/audio-player/use-audio-player.ts +298 -0
- package/components/pages/home-page/HomePage.tsx +1 -1
- package/components/pages/template-content/TemplateContent.tsx +5 -5
- package/components/pages/template-page/TemplatePage.tsx +5 -5
- package/components/shared/CustomTooltipContent.tsx +52 -0
- package/components/shared/layout-constants.ts +1 -1
- package/components/ui/chart/chart.stories.tsx +966 -7
- package/components/ui/chart/chart.tsx +918 -45
- package/components/ui/file-upload/file-upload.stories.tsx +100 -0
- package/components/ui/file-upload/file-upload.tsx +14 -74
- package/components/ui/file-upload/index.ts +1 -0
- package/components/ui/file-upload/use-file-upload.ts +181 -0
- package/components/ui/pagination/index.ts +2 -0
- package/components/ui/pagination/pagination.stories.tsx +94 -0
- package/components/ui/pagination/use-pagination.ts +194 -0
- package/components/ui/rich-text-editor/index.ts +2 -0
- package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +129 -1
- package/components/ui/rich-text-editor/rich-text-editor.tsx +86 -305
- package/components/ui/rich-text-editor/use-rich-text-editor.ts +439 -0
- package/components/ui/stepper/index.ts +3 -1
- package/components/ui/stepper/stepper.stories.tsx +116 -0
- package/components/ui/stepper/stepper.tsx +4 -4
- package/components/ui/stepper/use-stepper.ts +137 -0
- package/components/ui/tree-view/index.ts +4 -1
- package/components/ui/tree-view/tree-view.stories.tsx +110 -4
- package/components/ui/tree-view/tree-view.tsx +17 -125
- package/components/ui/tree-view/use-tree-view.ts +229 -0
- package/contexts/AssistenteContext.tsx +17 -54
- package/contexts/BrandColorsContext.tsx +6 -17
- package/contexts/LayoutContext.tsx +5 -31
- package/dist/AssistantChart-BAudAfne.cjs +3591 -0
- package/dist/AssistantChart-BP8upjMk.js +3565 -0
- package/dist/AudioPlayer-1ypwE2Wh.cjs +936 -0
- package/dist/AudioPlayer-DuKXrCfy.js +937 -0
- package/dist/CustomTooltipContent-DHjkY0ww.js +40 -0
- package/dist/CustomTooltipContent-c_K-DWRr.cjs +56 -0
- package/dist/LanguageContext-BwhwC3G2.js +657 -0
- package/dist/LanguageContext-DvUt5jBg.cjs +656 -0
- package/dist/LayoutContext-BDmcZfMH.cjs +84 -0
- package/dist/LayoutContext-dbQvdC4O.js +85 -0
- package/dist/ThemeContext-RTy1m2Uq.js +82 -0
- package/dist/ThemeContext-bSzuOit2.cjs +81 -0
- package/dist/VerifyEmailPage-C_ihbcth.js +2828 -0
- package/dist/VerifyEmailPage-Dt7zgA4w.cjs +2827 -0
- package/dist/XerticaProvider-CW9hpCdF.cjs +39 -0
- package/dist/XerticaProvider-siSt9uG2.js +40 -0
- package/dist/XerticaXLogo-D8jf0SNv.cjs +214 -0
- package/dist/XerticaXLogo-fAJMy3H4.js +215 -0
- package/dist/assistant.cjs.js +2 -1
- package/dist/assistant.es.js +3 -2
- package/dist/brand.cjs.js +2 -2
- package/dist/brand.es.js +2 -2
- package/dist/cli.js +14 -8
- package/dist/components/assistant/xertica-assistant/index.d.ts +2 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantCollapsedView.d.ts +13 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantConversationList.d.ts +16 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.d.ts +17 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.d.ts +19 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantHeader.d.ts +11 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantMessageBubble.d.ts +29 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantTabBar.d.ts +13 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.d.ts +4 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.d.ts +17 -0
- package/dist/components/assistant/xertica-assistant/parts/index.d.ts +16 -0
- package/dist/components/assistant/xertica-assistant/types.d.ts +106 -0
- package/dist/components/assistant/xertica-assistant/use-assistant.d.ts +125 -0
- package/dist/components/assistant/xertica-assistant/xertica-assistant.d.ts +8 -97
- package/dist/components/hooks/index.d.ts +3 -0
- package/dist/components/hooks/use-layout-shortcuts.d.ts +22 -0
- package/dist/components/layout/sidebar/index.d.ts +2 -0
- package/dist/components/layout/sidebar/sidebar.d.ts +80 -0
- package/dist/components/layout/sidebar/use-sidebar.d.ts +22 -0
- package/dist/components/media/audio-player/AudioPlayer.d.ts +4 -1
- package/dist/components/media/audio-player/use-audio-player.d.ts +72 -0
- package/dist/components/shared/CustomTooltipContent.d.ts +20 -0
- package/dist/components/shared/layout-constants.d.ts +1 -1
- package/dist/components/ui/alert/alert.d.ts +1 -1
- package/dist/components/ui/badge/badge.d.ts +1 -1
- package/dist/components/ui/button/button.d.ts +2 -2
- package/dist/components/ui/chart/chart.d.ts +162 -5
- package/dist/components/ui/file-upload/file-upload.d.ts +2 -0
- package/dist/components/ui/file-upload/index.d.ts +1 -0
- package/dist/components/ui/file-upload/use-file-upload.d.ts +49 -0
- package/dist/components/ui/pagination/index.d.ts +2 -0
- package/dist/components/ui/pagination/use-pagination.d.ts +78 -0
- package/dist/components/ui/rich-text-editor/index.d.ts +2 -0
- package/dist/components/ui/rich-text-editor/use-rich-text-editor.d.ts +107 -0
- package/dist/components/ui/stepper/index.d.ts +3 -1
- package/dist/components/ui/stepper/stepper.d.ts +2 -2
- package/dist/components/ui/stepper/use-stepper.d.ts +60 -0
- package/dist/components/ui/tree-view/index.d.ts +4 -1
- package/dist/components/ui/tree-view/tree-view.d.ts +4 -6
- package/dist/components/ui/tree-view/use-tree-view.d.ts +60 -0
- package/dist/contexts/AssistenteContext.d.ts +10 -49
- package/dist/hooks.cjs.js +30 -10
- package/dist/hooks.es.js +25 -4
- package/dist/index.cjs.js +20 -9
- package/dist/index.es.js +38 -27
- package/dist/layout.cjs.js +82 -1
- package/dist/layout.es.js +83 -2
- package/dist/media.cjs.js +1 -1
- package/dist/media.es.js +1 -1
- package/dist/pages.cjs.js +1 -1
- package/dist/pages.es.js +1 -1
- package/dist/rich-text-editor-BmsjY03B.js +2949 -0
- package/dist/rich-text-editor-GS2kpTAK.cjs +2966 -0
- package/dist/sidebar-CVUGHOS_.cjs +756 -0
- package/dist/sidebar-CmvwjnVb.js +757 -0
- package/dist/ui.cjs.js +12 -2
- package/dist/ui.es.js +24 -14
- package/dist/use-audio-player-Bkh23vQ3.js +177 -0
- package/dist/use-audio-player-Dn1NR9xN.cjs +176 -0
- package/dist/utils/color-utils.d.ts +51 -0
- package/dist/xertica-assistant-BMqdyRVi.js +2082 -0
- package/dist/xertica-assistant-Bj3vBCq_.cjs +2081 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/ai-usage.md +28 -10
- package/docs/architecture-improvements.md +463 -0
- package/docs/architecture.md +77 -1
- package/docs/components/assistant-chart.md +1 -1
- package/docs/components/assistant.md +159 -0
- package/docs/components/audio-player.md +46 -0
- package/docs/components/branding.md +251 -0
- package/docs/components/chart.md +354 -39
- package/docs/components/code-block.md +108 -0
- package/docs/components/file-upload.md +119 -2
- package/docs/components/formatted-document.md +113 -0
- package/docs/components/hooks.md +430 -0
- package/docs/components/image-with-fallback.md +106 -0
- package/docs/components/map-layers.md +140 -0
- package/docs/components/modern-chat-input.md +163 -0
- package/docs/components/pages.md +351 -0
- package/docs/components/pagination.md +187 -0
- package/docs/components/rich-text-editor.md +164 -0
- package/docs/components/sidebar.md +153 -4
- package/docs/components/stepper.md +157 -12
- package/docs/components/tree-view.md +164 -6
- package/docs/doc-audit.md +223 -0
- package/docs/getting-started.md +155 -1
- package/docs/guidelines.md +14 -8
- package/docs/layout.md +2 -2
- package/docs/llms.md +29 -9
- package/docs/patterns/detail-page.md +276 -0
- package/docs/patterns/settings.md +346 -0
- package/docs/patterns/wizard.md +217 -0
- package/guidelines/Guidelines.md +5 -3
- package/llms.txt +1 -1
- package/package.json +10 -10
- package/styles/xertica/tokens.css +41 -12
- package/templates/CLAUDE.md +16 -6
- package/templates/guidelines/Guidelines.md +16 -4
- package/templates/package.json +3 -3
- package/templates/src/styles/xertica/tokens.css +39 -10
- package/utils/color-utils.ts +72 -0
package/docs/ai-usage.md
CHANGED
|
@@ -47,20 +47,36 @@ import { Settings, Home, LogOut } from 'lucide-react';
|
|
|
47
47
|
|
|
48
48
|
The standard icon size in UI controls is `w-4 h-4` (`16px`). In hero or empty states, `w-8 h-8` or `w-12 h-12` is acceptable.
|
|
49
49
|
|
|
50
|
-
### 2.3 Color Tokens
|
|
50
|
+
### 2.3 Color Tokens — Context-Dependent Rules
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Color usage depends on the **semantic context** of the element:
|
|
53
53
|
|
|
54
|
+
**Always forbidden (all contexts):**
|
|
54
55
|
```tsx
|
|
55
|
-
// ❌
|
|
56
|
-
<div className="bg-blue-500 text-white border-gray-200">
|
|
56
|
+
// ❌ Never use raw hex or rgb() values
|
|
57
57
|
<div style={{ backgroundColor: '#3B82F6' }}>
|
|
58
|
+
<div style={{ color: 'rgb(59, 130, 246)' }}>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Required for semantic/status contexts** (errors, warnings, success states, status badges, danger actions):
|
|
62
|
+
```tsx
|
|
63
|
+
// ❌ Wrong — non-semantic color for a semantic state
|
|
64
|
+
<div className="bg-red-500 text-white">Error</div>
|
|
65
|
+
|
|
66
|
+
// ✅ Required — semantic token responds to theme and dark mode
|
|
67
|
+
<div className="bg-destructive text-destructive-foreground">Error</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Acceptable for layout and general non-semantic UI** (custom components, Storybook stories, decorative elements where no semantic token applies):
|
|
71
|
+
```tsx
|
|
72
|
+
// ✅ Acceptable — non-semantic context, no theme dependency needed
|
|
73
|
+
<div className="bg-blue-500 text-white border-gray-200">Custom UI</div>
|
|
58
74
|
|
|
59
|
-
// ✅ Required
|
|
60
|
-
<div className="bg-primary text-primary-foreground border-border">
|
|
75
|
+
// ✅ Required for semantic contexts
|
|
76
|
+
<div className="bg-primary text-primary-foreground border-border">Action</div>
|
|
61
77
|
```
|
|
62
78
|
|
|
63
|
-
The only exception is within chart configurations (`recharts`) where literal colors may be required for data series styling.
|
|
79
|
+
The only additional exception is within chart configurations (`recharts`) where literal colors may be required for data series styling.
|
|
64
80
|
|
|
65
81
|
### 2.4 Prop Inference
|
|
66
82
|
|
|
@@ -97,7 +113,7 @@ The `sidebarWidth` value drives `padding-left` on the main content container:
|
|
|
97
113
|
<div style={{ paddingLeft: sidebarExpanded ? `${sidebarWidth}px` : '80px' }}>
|
|
98
114
|
```
|
|
99
115
|
|
|
100
|
-
Do not hardcode `pl-64` or `padding-left:
|
|
116
|
+
Do not hardcode `pl-64` or `padding-left: 280px`. Read `docs/layout.md` for details.
|
|
101
117
|
|
|
102
118
|
---
|
|
103
119
|
|
|
@@ -150,11 +166,13 @@ When mocking these in previews/tests, pass empty functions — never string lite
|
|
|
150
166
|
|---|---|---|
|
|
151
167
|
| `<button>` | Bypasses design system | Use `<Button>` |
|
|
152
168
|
| `<div className="bg-white rounded shadow">` | Duplicates Card logic | Use `<Card>` |
|
|
153
|
-
| `text-red-500` for
|
|
169
|
+
| `text-red-500` for error states | Non-semantic — won't adapt to theme/dark mode | Use `text-destructive` |
|
|
170
|
+
| `bg-green-500` for success states | Non-semantic — won't adapt to theme/dark mode | Use `bg-success` |
|
|
154
171
|
| Hardcoded `pl-64` for layout | Breaks dynamic width | Use `sidebarWidth` from `useLayout()` |
|
|
155
172
|
| Manually managing sidebar state | Defeats context | Use `useLayout()` |
|
|
156
173
|
| Custom SVG icons | Inconsistent style | Use `lucide-react` |
|
|
157
|
-
| `style={{ color: '#...' }}` | Non-themeable | Use token classes |
|
|
174
|
+
| `style={{ color: '#...' }}` | Non-themeable raw value | Use semantic token classes |
|
|
175
|
+
| `#3b82f6` or `rgb(...)` in any className/style | Non-themeable raw value | Use semantic tokens or Tailwind utilities |
|
|
158
176
|
| Inventing new component variants | Breaks design system | Use only documented variants |
|
|
159
177
|
| Wrapping `<XerticaProvider>` per component | Performance issue | Wrap once at app root |
|
|
160
178
|
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# Relatório de Melhorias Arquiteturais
|
|
2
|
+
|
|
3
|
+
> **Objetivo:** Identificar oportunidades para melhorar a separação entre UI e lógica de negócio, tornando o projeto mais moderno, testável e manutenível.
|
|
4
|
+
>
|
|
5
|
+
> **Data da análise:** Maio 2026
|
|
6
|
+
> **Escopo:** Todos os componentes, hooks e contextos do projeto
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Sumário Executivo
|
|
11
|
+
|
|
12
|
+
O projeto já adota boas práticas em vários pontos — o padrão **headless hook** está presente em `useRichTextEditor`, `useTreeView`, `useSidebar` e `useAssistant`. No entanto, existem **inconsistências críticas** onde a mesma lógica é duplicada em múltiplos lugares, componentes com mais de 600 linhas misturam JSX com lógica de estado, e um componente (`ThemeToggle`) contorna completamente o sistema de contexto existente.
|
|
13
|
+
|
|
14
|
+
### Severidade dos Problemas
|
|
15
|
+
|
|
16
|
+
| Severidade | Quantidade | Exemplos |
|
|
17
|
+
|------------|-----------|---------|
|
|
18
|
+
| 🔴 Crítico | 2 | `ThemeToggle` bypassa `ThemeContext`; tipos duplicados entre contextos |
|
|
19
|
+
| 🟠 Alto | 3 | `AudioPlayer` sem hook; detecção mobile duplicada 4×; `xertica-assistant.tsx` com 1573 linhas |
|
|
20
|
+
| 🟡 Médio | 4 | `sidebar.tsx` com 1089 linhas; atalhos de teclado no `LayoutContext`; `BrandColorsContext` com utilitário inline; `header.tsx` sem hook |
|
|
21
|
+
| 🟢 Baixo | 3 | `SidebarTooltipContent` duplicado; `AssistantTooltipContent` inline; cookie management inline |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🔴 Problemas Críticos
|
|
26
|
+
|
|
27
|
+
### C1 — `ThemeToggle` bypassa `ThemeContext` completamente
|
|
28
|
+
|
|
29
|
+
**Arquivo:** [`components/brand/theme-toggle/ThemeToggle.tsx`](../components/brand/theme-toggle/ThemeToggle.tsx)
|
|
30
|
+
|
|
31
|
+
**Problema:** O componente `ThemeToggle` manipula `document.documentElement.classList` diretamente e mantém seu próprio estado local com `useState`, ignorando completamente o `ThemeContext` e o hook `useTheme()`. Isso cria um **risco de dessincronização**: se o tema for alterado via `ThemeContext` (ex: por outro componente ou por `defaultTheme`), o `ThemeToggle` não refletirá a mudança.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// ❌ ATUAL — estado local + manipulação direta do DOM
|
|
35
|
+
const [isDark, setIsDark] = useState(false);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setIsDark(document.documentElement.classList.contains('dark'));
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const toggleTheme = () => {
|
|
42
|
+
document.documentElement.classList.toggle('dark');
|
|
43
|
+
setIsDark(!isDark);
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Solução:** Usar `useTheme()` do `ThemeContext`:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// ✅ CORRETO — usa o contexto existente
|
|
51
|
+
import { useTheme } from '@/contexts/ThemeContext';
|
|
52
|
+
|
|
53
|
+
export function ThemeToggle({ size = 'md', className }: ThemeToggleProps) {
|
|
54
|
+
const { theme, toggleTheme } = useTheme();
|
|
55
|
+
const isDark = theme === 'dark';
|
|
56
|
+
// ...
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Impacto:** Elimina dessincronização de estado. Sem breaking changes na API pública.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### C2 — Tipos duplicados entre `AssistenteContext` e `xertica-assistant.tsx`
|
|
65
|
+
|
|
66
|
+
**Arquivos:**
|
|
67
|
+
- [`contexts/AssistenteContext.tsx`](../contexts/AssistenteContext.tsx) — linhas 5–71
|
|
68
|
+
- [`components/assistant/xertica-assistant/xertica-assistant.tsx`](../components/assistant/xertica-assistant/xertica-assistant.tsx) — linhas 155–230
|
|
69
|
+
|
|
70
|
+
**Problema:** Os mesmos tipos são definidos em dois lugares com nomes e estruturas ligeiramente diferentes:
|
|
71
|
+
|
|
72
|
+
| `AssistenteContext.tsx` | `xertica-assistant.tsx` | Diferença |
|
|
73
|
+
|------------------------|------------------------|-----------|
|
|
74
|
+
| `Message` | `Message` | Campos diferentes (`role` vs `type`) |
|
|
75
|
+
| `Conversa` | `Conversation` | Nome diferente, campos em PT vs EN |
|
|
76
|
+
| `SearchResult` | `SearchResult` | Duplicado idêntico |
|
|
77
|
+
| `SearchSource` | `SearchSource` | Duplicado idêntico |
|
|
78
|
+
| `SearchCommand` | `SearchCommand` | Duplicado idêntico |
|
|
79
|
+
|
|
80
|
+
Isso significa que existem **dois contratos de tipo incompatíveis** para os mesmos dados, forçando conversões implícitas e tornando refatorações perigosas.
|
|
81
|
+
|
|
82
|
+
**Solução:** Criar um arquivo de tipos compartilhado:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
components/assistant/xertica-assistant/
|
|
86
|
+
├── types.ts ← NOVO: fonte única de verdade para todos os tipos
|
|
87
|
+
├── use-assistant.ts ← importa de types.ts
|
|
88
|
+
├── xertica-assistant.tsx ← importa de types.ts
|
|
89
|
+
contexts/
|
|
90
|
+
└── AssistenteContext.tsx ← importa de types.ts (re-exporta para compatibilidade)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// components/assistant/xertica-assistant/types.ts
|
|
95
|
+
export interface Message { ... }
|
|
96
|
+
export interface Conversation { ... }
|
|
97
|
+
export interface SearchResult { ... }
|
|
98
|
+
// ...
|
|
99
|
+
|
|
100
|
+
// contexts/AssistenteContext.tsx
|
|
101
|
+
export type { Message, Conversation, SearchResult } from '@/components/assistant/xertica-assistant/types';
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🟠 Problemas de Alta Prioridade
|
|
107
|
+
|
|
108
|
+
### A1 — Detecção de mobile duplicada em 4+ lugares
|
|
109
|
+
|
|
110
|
+
**Problema:** A lógica `window.innerWidth < 768` está duplicada em:
|
|
111
|
+
|
|
112
|
+
| Arquivo | Linha | Forma |
|
|
113
|
+
|---------|-------|-------|
|
|
114
|
+
| [`components/shared/use-mobile.ts`](../components/shared/use-mobile.ts) | 18 | `useIsMobile()` hook ✅ |
|
|
115
|
+
| [`components/assistant/xertica-assistant/use-assistant.ts`](../components/assistant/xertica-assistant/use-assistant.ts) | ~192 | `window.innerWidth < 768` inline |
|
|
116
|
+
| [`components/layout/sidebar/use-sidebar.ts`](../components/layout/sidebar/use-sidebar.ts) | ~46 | `window.innerWidth < 768` inline |
|
|
117
|
+
| [`components/media/audio-player/AudioPlayer.tsx`](../components/media/audio-player/AudioPlayer.tsx) | ~113 | `window.innerWidth < 768` inline |
|
|
118
|
+
| [`contexts/LayoutContext.tsx`](../contexts/LayoutContext.tsx) | ~60 | `window.innerWidth < 768` inline |
|
|
119
|
+
|
|
120
|
+
O hook `useIsMobile()` **já existe** em `components/shared/use-mobile.ts` e usa `matchMedia` (mais eficiente que `resize` events), mas não está sendo usado pelos outros arquivos.
|
|
121
|
+
|
|
122
|
+
**Solução:** Substituir todas as ocorrências pelo hook existente:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// ❌ ATUAL
|
|
126
|
+
const [isMobileViewport, setIsMobileViewport] = useState(window.innerWidth < 768);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const handleResize = () => setIsMobileViewport(window.innerWidth < 768);
|
|
129
|
+
window.addEventListener('resize', handleResize);
|
|
130
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
// ✅ CORRETO
|
|
134
|
+
import { useIsMobile } from '@/components/shared/use-mobile';
|
|
135
|
+
const isMobileViewport = useIsMobile();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Impacto:** Reduz código duplicado, garante breakpoint consistente (768px), usa `matchMedia` que é mais performático que `resize` events.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### A2 — `AudioPlayer.tsx` (663 linhas) sem hook headless
|
|
143
|
+
|
|
144
|
+
**Arquivo:** [`components/media/audio-player/AudioPlayer.tsx`](../components/media/audio-player/AudioPlayer.tsx)
|
|
145
|
+
|
|
146
|
+
**Problema:** Todo o estado e lógica do player de áudio está inline no componente, misturado com JSX. Inclui:
|
|
147
|
+
- Gerenciamento de estado (`isPlaying`, `currentTime`, `duration`, `volume`, `playbackRate`, `isFloating`)
|
|
148
|
+
- `IntersectionObserver` para auto-float
|
|
149
|
+
- Sincronização de áudio com `useEffect`
|
|
150
|
+
- Detecção de mobile (duplicada)
|
|
151
|
+
- Formatação de tempo (`formatTime`)
|
|
152
|
+
- Handlers de eventos (`togglePlay`, `handleSeek`, `handleVolumeChange`, `handleSetFloating`)
|
|
153
|
+
|
|
154
|
+
**Solução:** Extrair para `use-audio-player.ts`:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// components/media/audio-player/use-audio-player.ts
|
|
158
|
+
export interface UseAudioPlayerProps {
|
|
159
|
+
src: string;
|
|
160
|
+
autoFloat?: boolean;
|
|
161
|
+
onEnded?: () => void;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface UseAudioPlayerReturn {
|
|
165
|
+
audioRef: React.RefObject<HTMLAudioElement>;
|
|
166
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
167
|
+
isPlaying: boolean;
|
|
168
|
+
currentTime: number;
|
|
169
|
+
duration: number;
|
|
170
|
+
volume: number;
|
|
171
|
+
playbackRate: number;
|
|
172
|
+
isFloating: boolean;
|
|
173
|
+
isMobile: boolean;
|
|
174
|
+
togglePlay: () => void;
|
|
175
|
+
handleSeek: (value: number[]) => void;
|
|
176
|
+
handleVolumeChange: (value: number[]) => void;
|
|
177
|
+
handleSetFloating: (floating: boolean) => void;
|
|
178
|
+
setPlaybackRate: (rate: number) => void;
|
|
179
|
+
formatTime: (time: number) => string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function useAudioPlayer(props: UseAudioPlayerProps): UseAudioPlayerReturn { ... }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Benefícios:**
|
|
186
|
+
- `AudioPlayer.tsx` fica com ~200 linhas de JSX puro
|
|
187
|
+
- Lógica do player pode ser testada unitariamente sem renderizar DOM
|
|
188
|
+
- Permite criar variantes do player (mini, fullscreen) reusando o mesmo hook
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### A3 — `xertica-assistant.tsx` com 1573 linhas — decomposição necessária
|
|
193
|
+
|
|
194
|
+
**Arquivo:** [`components/assistant/xertica-assistant/xertica-assistant.tsx`](../components/assistant/xertica-assistant/xertica-assistant.tsx)
|
|
195
|
+
|
|
196
|
+
**Problema:** Mesmo com o `useAssistant` hook extraindo a lógica de estado, o componente ainda tem ~1000 linhas de JSX com sub-componentes definidos inline e seções de renderização muito longas.
|
|
197
|
+
|
|
198
|
+
**Estrutura atual:**
|
|
199
|
+
```
|
|
200
|
+
xertica-assistant.tsx (1573 linhas)
|
|
201
|
+
├── AssistantTooltipContent (componente inline, linhas 105-131)
|
|
202
|
+
├── Tipos exportados (linhas 155-230) ← deveria estar em types.ts
|
|
203
|
+
├── XerticaAssistantProps (linhas 246-417) ← 171 linhas de interface!
|
|
204
|
+
└── XerticaAssistant (linhas 467-1572)
|
|
205
|
+
├── Document editor overlay (linhas 598-694)
|
|
206
|
+
├── Collapsed view (linhas 695-827)
|
|
207
|
+
├── Chat messages list (linhas 888-1394) ← 506 linhas!
|
|
208
|
+
├── History/Favorites tabs (linhas 1439-1511)
|
|
209
|
+
├── Chat input area (linhas 1515-1531)
|
|
210
|
+
└── Feedback dialog (linhas 1539-1570)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Solução — decomposição em sub-componentes:**
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
components/assistant/xertica-assistant/
|
|
217
|
+
├── types.ts ← NOVO: todos os tipos
|
|
218
|
+
├── use-assistant.ts ← existente (manter)
|
|
219
|
+
├── xertica-assistant.tsx ← orquestrador (~200 linhas)
|
|
220
|
+
└── parts/
|
|
221
|
+
├── AssistantCollapsedView.tsx ← NOVO: view colapsada
|
|
222
|
+
├── AssistantDocumentEditor.tsx ← NOVO: overlay do editor
|
|
223
|
+
├── AssistantMessageList.tsx ← NOVO: lista de mensagens
|
|
224
|
+
├── AssistantMessage.tsx ← NOVO: mensagem individual
|
|
225
|
+
├── AssistantHistoryTab.tsx ← NOVO: aba de histórico
|
|
226
|
+
├── AssistantFeedbackDialog.tsx ← NOVO: dialog de feedback
|
|
227
|
+
└── AssistantTooltipContent.tsx ← NOVO: tooltip customizado
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Impacto:** `xertica-assistant.tsx` passa de 1573 para ~200 linhas. Cada sub-componente é testável e manutenível independentemente.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 🟡 Problemas de Média Prioridade
|
|
235
|
+
|
|
236
|
+
### M1 — `sidebar.tsx` com 1089 linhas — decomposição parcial necessária
|
|
237
|
+
|
|
238
|
+
**Arquivo:** [`components/layout/sidebar/sidebar.tsx`](../components/layout/sidebar/sidebar.tsx)
|
|
239
|
+
|
|
240
|
+
**Problema:** O arquivo já usa compound components (`SidebarRoot`, `SidebarHeader`, `SidebarNav`, `SidebarSearch`, `SidebarFooter`), mas todos estão no mesmo arquivo de 1089 linhas. Além disso, `SidebarTooltipContent` é definido inline (linhas 40-64) — idêntico ao `AssistantTooltipContent` em `xertica-assistant.tsx`.
|
|
241
|
+
|
|
242
|
+
**Solução:**
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
components/layout/sidebar/
|
|
246
|
+
├── sidebar.tsx ← orquestrador + export público (~100 linhas)
|
|
247
|
+
├── use-sidebar.ts ← existente (manter)
|
|
248
|
+
├── SidebarContext.tsx ← NOVO: contexto interno extraído
|
|
249
|
+
└── parts/
|
|
250
|
+
├── SidebarRoot.tsx ← NOVO
|
|
251
|
+
├── SidebarHeader.tsx ← NOVO
|
|
252
|
+
├── SidebarNav.tsx ← NOVO
|
|
253
|
+
├── SidebarSearch.tsx ← NOVO
|
|
254
|
+
└── SidebarFooter.tsx ← NOVO
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Oportunidade adicional:** `SidebarTooltipContent` e `AssistantTooltipContent` são idênticos — extrair para componente compartilhado:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
components/shared/
|
|
261
|
+
└── CustomTooltipContent.tsx ← NOVO: tooltip com portal e arrow
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### M2 — Atalhos de teclado e cookies misturados no `LayoutContext`
|
|
267
|
+
|
|
268
|
+
**Arquivo:** [`contexts/LayoutContext.tsx`](../contexts/LayoutContext.tsx)
|
|
269
|
+
|
|
270
|
+
**Problema:** O `LayoutContext` mistura três responsabilidades distintas:
|
|
271
|
+
1. **Estado de layout** (sidebar expandida, assistente expandido)
|
|
272
|
+
2. **Atalhos de teclado** (`Ctrl+B` para toggle sidebar, linhas 78-86)
|
|
273
|
+
3. **Persistência em cookies** (funções `getCookie`/`setCookie`, linhas 25-45)
|
|
274
|
+
|
|
275
|
+
**Solução — separação de responsabilidades:**
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
// contexts/LayoutContext.tsx — apenas estado
|
|
279
|
+
export function LayoutProvider({ children }) {
|
|
280
|
+
const [sidebarExpanded, setSidebarExpanded] = usePersistentState('sidebar', true);
|
|
281
|
+
// ...
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// hooks/use-layout-shortcuts.ts — NOVO: atalhos de teclado
|
|
285
|
+
export function useLayoutShortcuts() {
|
|
286
|
+
const { toggleSidebar } = useLayout();
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
const handler = (e: KeyboardEvent) => {
|
|
289
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
|
|
290
|
+
e.preventDefault();
|
|
291
|
+
toggleSidebar();
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
document.addEventListener('keydown', handler);
|
|
295
|
+
return () => document.removeEventListener('keydown', handler);
|
|
296
|
+
}, [toggleSidebar]);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// hooks/use-persistent-state.ts — NOVO: estado com persistência em cookie
|
|
300
|
+
export function usePersistentState<T>(key: string, defaultValue: T) { ... }
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
### M3 — `BrandColorsContext` com utilitário `hexToRgb` inline
|
|
306
|
+
|
|
307
|
+
**Arquivo:** [`contexts/BrandColorsContext.tsx`](../contexts/BrandColorsContext.tsx) — linhas 79-88
|
|
308
|
+
|
|
309
|
+
**Problema:** A função `hexToRgb` é um utilitário puro definido dentro do componente Provider. Não pode ser testada isoladamente e não pode ser reutilizada.
|
|
310
|
+
|
|
311
|
+
**Solução:**
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
// utils/color-utils.ts — NOVO
|
|
315
|
+
export function hexToRgb(hex: string): { r: number; g: number; b: number } | null { ... }
|
|
316
|
+
export function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } { ... }
|
|
317
|
+
export function generateColorScale(baseHex: string): Record<string, string> { ... }
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### M4 — `header.tsx` sem hook headless
|
|
323
|
+
|
|
324
|
+
**Arquivo:** [`components/layout/header/header.tsx`](../components/layout/header/header.tsx)
|
|
325
|
+
|
|
326
|
+
**Problema:** O `Header` não tem lógica de estado complexa, mas tem lógica de renderização condicional para breadcrumbs, ações e menu do usuário que poderia ser extraída para melhorar a testabilidade.
|
|
327
|
+
|
|
328
|
+
**Avaliação:** Prioridade baixa — o componente tem 337 linhas mas é principalmente JSX declarativo. Não há estado local significativo. A extração de um `useHeader` hook seria de valor limitado aqui.
|
|
329
|
+
|
|
330
|
+
**Recomendação:** Manter como está, mas considerar extrair `HeaderUserMenu` e `HeaderBreadcrumbs` como sub-componentes separados para melhorar a legibilidade.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 🟢 Problemas de Baixa Prioridade
|
|
335
|
+
|
|
336
|
+
### B1 — `SidebarTooltipContent` e `AssistantTooltipContent` são idênticos
|
|
337
|
+
|
|
338
|
+
**Arquivos:**
|
|
339
|
+
- [`components/layout/sidebar/sidebar.tsx`](../components/layout/sidebar/sidebar.tsx) — linhas 40-64
|
|
340
|
+
- [`components/assistant/xertica-assistant/xertica-assistant.tsx`](../components/assistant/xertica-assistant/xertica-assistant.tsx) — linhas 105-131
|
|
341
|
+
|
|
342
|
+
Ambos implementam um tooltip com `TooltipPrimitive.Portal` e `TooltipPrimitive.Arrow`. Extrair para componente compartilhado elimina duplicação.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
### B2 — `use-sidebar.ts` tem alturas hardcoded
|
|
347
|
+
|
|
348
|
+
**Arquivo:** [`components/layout/sidebar/use-sidebar.ts`](../components/layout/sidebar/use-sidebar.ts) — linhas 57-91
|
|
349
|
+
|
|
350
|
+
A função `checkOverflow` usa valores hardcoded (`40px`, `32px`) para calcular overflow de itens de navegação. Esses valores deveriam ser constantes nomeadas ou derivados do DOM.
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
// ❌ ATUAL
|
|
354
|
+
const itemHeight = 40;
|
|
355
|
+
const groupHeaderHeight = 32;
|
|
356
|
+
|
|
357
|
+
// ✅ MELHOR
|
|
358
|
+
const SIDEBAR_ITEM_HEIGHT_PX = 40;
|
|
359
|
+
const SIDEBAR_GROUP_HEADER_HEIGHT_PX = 32;
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
### B3 — `use-assistant.ts` tem `setTimeout` para simular async
|
|
365
|
+
|
|
366
|
+
**Arquivo:** [`components/assistant/xertica-assistant/use-assistant.ts`](../components/assistant/xertica-assistant/use-assistant.ts) — linhas 300-324
|
|
367
|
+
|
|
368
|
+
O hook usa `setTimeout` para simular respostas assíncronas. Isso é aceitável para desenvolvimento, mas deveria ser claramente marcado como mock e substituído por uma interface de serviço real:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
// Recomendação: abstrair a chamada de API
|
|
372
|
+
interface AssistantService {
|
|
373
|
+
sendMessage(message: string, context: Conversation[]): Promise<Message>;
|
|
374
|
+
generatePodcast(content: string): Promise<string>;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Padrões Positivos — Manter e Expandir
|
|
381
|
+
|
|
382
|
+
O projeto já tem excelentes exemplos do padrão headless hook que devem servir como referência:
|
|
383
|
+
|
|
384
|
+
### ✅ `useRichTextEditor` — Exemplo de referência
|
|
385
|
+
|
|
386
|
+
**Arquivo:** [`components/ui/rich-text-editor/use-rich-text-editor.ts`](../components/ui/rich-text-editor/use-rich-text-editor.ts)
|
|
387
|
+
|
|
388
|
+
- Interface `UseRichTextEditorProps` bem definida
|
|
389
|
+
- Interface `UseRichTextEditorReturn` completa com todos os retornos tipados
|
|
390
|
+
- Lógica completamente separada do JSX
|
|
391
|
+
- `rich-text-editor.tsx` é JSX puro consumindo o hook
|
|
392
|
+
|
|
393
|
+
### ✅ `useTreeView` — Exemplo de referência para acessibilidade
|
|
394
|
+
|
|
395
|
+
**Arquivo:** [`components/ui/tree-view/use-tree-view.ts`](../components/ui/tree-view/use-tree-view.ts)
|
|
396
|
+
|
|
397
|
+
- WAI-ARIA keyboard navigation implementada no hook
|
|
398
|
+
- Componente de UI não precisa conhecer lógica de navegação por teclado
|
|
399
|
+
|
|
400
|
+
### ✅ `useAssistant` — Boa extração, mas incompleta
|
|
401
|
+
|
|
402
|
+
**Arquivo:** [`components/assistant/xertica-assistant/use-assistant.ts`](../components/assistant/xertica-assistant/use-assistant.ts)
|
|
403
|
+
|
|
404
|
+
- Toda a lógica de estado está no hook
|
|
405
|
+
- O componente ainda tem 1000+ linhas de JSX que precisam de decomposição
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Plano de Implementação Recomendado
|
|
410
|
+
|
|
411
|
+
### Fase 1 — Correções Críticas (1-2 dias)
|
|
412
|
+
|
|
413
|
+
| # | Tarefa | Arquivo(s) | Esforço |
|
|
414
|
+
|---|--------|-----------|---------|
|
|
415
|
+
| 1.1 | Corrigir `ThemeToggle` para usar `useTheme()` | `ThemeToggle.tsx` | 30min |
|
|
416
|
+
| 1.2 | Criar `types.ts` compartilhado para o assistente | `types.ts` (novo) | 1h |
|
|
417
|
+
| 1.3 | Atualizar imports em `AssistenteContext` e `use-assistant.ts` | 2 arquivos | 30min |
|
|
418
|
+
|
|
419
|
+
### Fase 2 — Eliminação de Duplicações (2-3 dias)
|
|
420
|
+
|
|
421
|
+
| # | Tarefa | Arquivo(s) | Esforço |
|
|
422
|
+
|---|--------|-----------|---------|
|
|
423
|
+
| 2.1 | Substituir detecção mobile por `useIsMobile()` | 4 arquivos | 2h |
|
|
424
|
+
| 2.2 | Extrair `CustomTooltipContent` compartilhado | 1 arquivo novo | 1h |
|
|
425
|
+
| 2.3 | Mover `hexToRgb` para `utils/color-utils.ts` | 2 arquivos | 30min |
|
|
426
|
+
|
|
427
|
+
### Fase 3 — Extração de Hooks (3-5 dias)
|
|
428
|
+
|
|
429
|
+
| # | Tarefa | Arquivo(s) | Esforço |
|
|
430
|
+
|---|--------|-----------|---------|
|
|
431
|
+
| 3.1 | Criar `use-audio-player.ts` | 1 arquivo novo | 4h |
|
|
432
|
+
| 3.2 | Refatorar `AudioPlayer.tsx` para usar o hook | `AudioPlayer.tsx` | 2h |
|
|
433
|
+
| 3.3 | Extrair `use-layout-shortcuts.ts` | 1 arquivo novo | 1h |
|
|
434
|
+
| 3.4 | Extrair `use-persistent-state.ts` | 1 arquivo novo | 1h |
|
|
435
|
+
|
|
436
|
+
### Fase 4 — Decomposição de Componentes Grandes (5-8 dias)
|
|
437
|
+
|
|
438
|
+
| # | Tarefa | Arquivo(s) | Esforço |
|
|
439
|
+
|---|--------|-----------|---------|
|
|
440
|
+
| 4.1 | Decompor `xertica-assistant.tsx` em sub-componentes | 7 arquivos novos | 1 dia |
|
|
441
|
+
| 4.2 | Decompor `sidebar.tsx` em sub-componentes | 5 arquivos novos | 1 dia |
|
|
442
|
+
| 4.3 | Extrair `HeaderUserMenu` e `HeaderBreadcrumbs` | 2 arquivos novos | 3h |
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Métricas de Melhoria Esperadas
|
|
447
|
+
|
|
448
|
+
| Métrica | Atual | Após Fase 1-2 | Após Fase 3-4 |
|
|
449
|
+
|---------|-------|--------------|--------------|
|
|
450
|
+
| Maior arquivo (linhas) | 1573 | 1573 | ~200 |
|
|
451
|
+
| Duplicações de detecção mobile | 4 | 1 | 1 |
|
|
452
|
+
| Definições de tipo duplicadas | 2 conjuntos | 1 conjunto | 1 conjunto |
|
|
453
|
+
| Componentes sem hook headless | 2 (AudioPlayer, Header) | 2 | 0-1 |
|
|
454
|
+
| Risco de dessincronização de tema | Alto | Eliminado | Eliminado |
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Referências
|
|
459
|
+
|
|
460
|
+
- [Padrão Headless Hook — Kent C. Dodds](https://kentcdodds.com/blog/inversion-of-control)
|
|
461
|
+
- [Compound Components Pattern](https://kentcdodds.com/blog/compound-components-with-react-hooks)
|
|
462
|
+
- [`docs/architecture.md`](./architecture.md) — Arquitetura atual do projeto
|
|
463
|
+
- [`docs/doc-audit.md`](./doc-audit.md) — Auditoria de documentação
|
package/docs/architecture.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Xertica UI —
|
|
1
|
+
# Xertica UI — Architecture
|
|
2
2
|
|
|
3
3
|
## Audit Date
|
|
4
4
|
2026-04-23 (Post-Modularization)
|
|
@@ -61,3 +61,79 @@ Each subpath exposes three fields in `exports`: `types` (`.d.ts`), `import` (ES
|
|
|
61
61
|
> TypeScript consumers need `"moduleResolution": "bundler"` (or `"node16"` / `"nodenext"`) to resolve subpath exports.
|
|
62
62
|
|
|
63
63
|
---
|
|
64
|
+
|
|
65
|
+
## Headless UI Pattern (v3)
|
|
66
|
+
|
|
67
|
+
The library follows a **three-tier composability model** for its complex components. Each tier is a superset of the previous — consumers can choose the level of control they need.
|
|
68
|
+
|
|
69
|
+
### Tier 1 — Monolithic API (drop-in)
|
|
70
|
+
|
|
71
|
+
The default export. Zero configuration, full functionality. Ideal for rapid integration.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { Sidebar } from 'xertica-ui/layout';
|
|
75
|
+
import { XerticaAssistant } from 'xertica-ui/assistant';
|
|
76
|
+
import { RichTextEditor } from 'xertica-ui/ui';
|
|
77
|
+
|
|
78
|
+
<Sidebar navigationGroups={groups} />
|
|
79
|
+
<XerticaAssistant demoMode={true} />
|
|
80
|
+
<RichTextEditor value={html} onChange={setHtml} />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Tier 2 — Compound Components (structural control)
|
|
84
|
+
|
|
85
|
+
Sub-components exposed as static properties on the main component. Consumers control layout and composition while the shared React Context handles state propagation.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { Sidebar } from 'xertica-ui/layout';
|
|
89
|
+
|
|
90
|
+
<Sidebar.Root navigationGroups={groups}>
|
|
91
|
+
<Sidebar.Header logo={<MyLogo />} />
|
|
92
|
+
<Sidebar.Nav />
|
|
93
|
+
<Sidebar.Search search={searchConfig} />
|
|
94
|
+
<Sidebar.Footer showUser showSettings />
|
|
95
|
+
</Sidebar.Root>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Available compound components:
|
|
99
|
+
|
|
100
|
+
| Component | Sub-components |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `Sidebar` | `Sidebar.Root`, `Sidebar.Header`, `Sidebar.Nav`, `Sidebar.Search`, `Sidebar.Footer` |
|
|
103
|
+
|
|
104
|
+
### Tier 3 — Headless Hooks (full control)
|
|
105
|
+
|
|
106
|
+
Pure logic hooks with zero UI. Consumers bring their own markup, styling, and layout. The hook manages all state, refs, effects, and handlers.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { useSidebar } from 'xertica-ui/layout';
|
|
110
|
+
import { useAssistant } from 'xertica-ui/assistant';
|
|
111
|
+
import { useRichTextEditor } from 'xertica-ui/ui';
|
|
112
|
+
|
|
113
|
+
// Full custom sidebar
|
|
114
|
+
const { expanded, toggleExpanded, navigationGroups } = useSidebar({ defaultExpanded: true });
|
|
115
|
+
|
|
116
|
+
// Full custom assistant
|
|
117
|
+
const { mensagens, mensagem, setMensagem, handleEnviarMensagem } = useAssistant({ demoMode: true });
|
|
118
|
+
|
|
119
|
+
// Full custom editor
|
|
120
|
+
const { editorRef, activeFormats, execCommand, handleInput } = useRichTextEditor({ value, onChange });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Available Headless Hooks
|
|
124
|
+
|
|
125
|
+
| Hook | Import | Description |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `useSidebar` | `xertica-ui/layout` | Sidebar expansion, overflow detection, navigation state |
|
|
128
|
+
| `useAssistant` | `xertica-ui/assistant` | Full assistant state: messages, conversations, handlers |
|
|
129
|
+
| `useRichTextEditor` | `xertica-ui/ui` | Editor formatting state, search, link management |
|
|
130
|
+
|
|
131
|
+
### Design Principles
|
|
132
|
+
|
|
133
|
+
1. **Hooks own all state** — No state lives in the presentational component; it is always delegated to the hook.
|
|
134
|
+
2. **Backward compatible** — The Tier 1 monolithic API is unchanged. Existing consumers are unaffected.
|
|
135
|
+
3. **Single source of truth** — The hook is the canonical implementation. The default component is a thin wrapper around it.
|
|
136
|
+
4. **`useCallback` everywhere** — All handlers in hooks are memoized to prevent unnecessary re-renders in custom UIs.
|
|
137
|
+
5. **TypeScript-first** — Every hook exports a `Props` interface and a `Return` interface for full type safety.
|
|
138
|
+
|
|
139
|
+
---
|
|
@@ -42,5 +42,5 @@ const chartConfig = {
|
|
|
42
42
|
## AI Rules
|
|
43
43
|
|
|
44
44
|
> [!IMPORTANT]
|
|
45
|
-
> - **Data Format**:
|
|
45
|
+
> - **Data Format**: Your data objects can use **any key names** — pass them via `chartData` and reference them in `chartConfig`. The keys `month`, `desktop`, and `mobile` are only examples from the Storybook demo; they are not required.
|
|
46
46
|
> - **Container Size**: This component is optimized for the narrow width of the AI Assistant sidebar (`~400px`). It uses `min-h-[200px]` to maintain readability.
|