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.
Files changed (181) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/bin/cli.ts +1 -1
  4. package/bin/generate-tokens.ts +13 -7
  5. package/components/assistant/xertica-assistant/index.ts +2 -0
  6. package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +97 -0
  7. package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -0
  8. package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -0
  9. package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +86 -0
  10. package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +77 -0
  11. package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +573 -0
  12. package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +65 -0
  13. package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -0
  14. package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +98 -0
  15. package/components/assistant/xertica-assistant/parts/index.ts +16 -0
  16. package/components/assistant/xertica-assistant/types.ts +139 -0
  17. package/components/assistant/xertica-assistant/use-assistant.ts +559 -0
  18. package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +200 -0
  19. package/components/assistant/xertica-assistant/xertica-assistant.tsx +198 -1460
  20. package/components/brand/theme-toggle/ThemeToggle.tsx +8 -27
  21. package/components/hooks/index.ts +3 -0
  22. package/components/hooks/use-layout-shortcuts.ts +46 -0
  23. package/components/layout/sidebar/index.ts +2 -0
  24. package/components/layout/sidebar/sidebar.stories.tsx +160 -8
  25. package/components/layout/sidebar/sidebar.tsx +606 -497
  26. package/components/layout/sidebar/use-sidebar.ts +104 -0
  27. package/components/media/audio-player/AudioPlayer.tsx +131 -206
  28. package/components/media/audio-player/use-audio-player.ts +298 -0
  29. package/components/pages/home-page/HomePage.tsx +1 -1
  30. package/components/pages/template-content/TemplateContent.tsx +5 -5
  31. package/components/pages/template-page/TemplatePage.tsx +5 -5
  32. package/components/shared/CustomTooltipContent.tsx +52 -0
  33. package/components/shared/layout-constants.ts +1 -1
  34. package/components/ui/chart/chart.stories.tsx +966 -7
  35. package/components/ui/chart/chart.tsx +918 -45
  36. package/components/ui/file-upload/file-upload.stories.tsx +100 -0
  37. package/components/ui/file-upload/file-upload.tsx +14 -74
  38. package/components/ui/file-upload/index.ts +1 -0
  39. package/components/ui/file-upload/use-file-upload.ts +181 -0
  40. package/components/ui/pagination/index.ts +2 -0
  41. package/components/ui/pagination/pagination.stories.tsx +94 -0
  42. package/components/ui/pagination/use-pagination.ts +194 -0
  43. package/components/ui/rich-text-editor/index.ts +2 -0
  44. package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +129 -1
  45. package/components/ui/rich-text-editor/rich-text-editor.tsx +86 -305
  46. package/components/ui/rich-text-editor/use-rich-text-editor.ts +439 -0
  47. package/components/ui/stepper/index.ts +3 -1
  48. package/components/ui/stepper/stepper.stories.tsx +116 -0
  49. package/components/ui/stepper/stepper.tsx +4 -4
  50. package/components/ui/stepper/use-stepper.ts +137 -0
  51. package/components/ui/tree-view/index.ts +4 -1
  52. package/components/ui/tree-view/tree-view.stories.tsx +110 -4
  53. package/components/ui/tree-view/tree-view.tsx +17 -125
  54. package/components/ui/tree-view/use-tree-view.ts +229 -0
  55. package/contexts/AssistenteContext.tsx +17 -54
  56. package/contexts/BrandColorsContext.tsx +6 -17
  57. package/contexts/LayoutContext.tsx +5 -31
  58. package/dist/AssistantChart-BAudAfne.cjs +3591 -0
  59. package/dist/AssistantChart-BP8upjMk.js +3565 -0
  60. package/dist/AudioPlayer-1ypwE2Wh.cjs +936 -0
  61. package/dist/AudioPlayer-DuKXrCfy.js +937 -0
  62. package/dist/CustomTooltipContent-DHjkY0ww.js +40 -0
  63. package/dist/CustomTooltipContent-c_K-DWRr.cjs +56 -0
  64. package/dist/LanguageContext-BwhwC3G2.js +657 -0
  65. package/dist/LanguageContext-DvUt5jBg.cjs +656 -0
  66. package/dist/LayoutContext-BDmcZfMH.cjs +84 -0
  67. package/dist/LayoutContext-dbQvdC4O.js +85 -0
  68. package/dist/ThemeContext-RTy1m2Uq.js +82 -0
  69. package/dist/ThemeContext-bSzuOit2.cjs +81 -0
  70. package/dist/VerifyEmailPage-C_ihbcth.js +2828 -0
  71. package/dist/VerifyEmailPage-Dt7zgA4w.cjs +2827 -0
  72. package/dist/XerticaProvider-CW9hpCdF.cjs +39 -0
  73. package/dist/XerticaProvider-siSt9uG2.js +40 -0
  74. package/dist/XerticaXLogo-D8jf0SNv.cjs +214 -0
  75. package/dist/XerticaXLogo-fAJMy3H4.js +215 -0
  76. package/dist/assistant.cjs.js +2 -1
  77. package/dist/assistant.es.js +3 -2
  78. package/dist/brand.cjs.js +2 -2
  79. package/dist/brand.es.js +2 -2
  80. package/dist/cli.js +14 -8
  81. package/dist/components/assistant/xertica-assistant/index.d.ts +2 -0
  82. package/dist/components/assistant/xertica-assistant/parts/AssistantCollapsedView.d.ts +13 -0
  83. package/dist/components/assistant/xertica-assistant/parts/AssistantConversationList.d.ts +16 -0
  84. package/dist/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.d.ts +17 -0
  85. package/dist/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.d.ts +19 -0
  86. package/dist/components/assistant/xertica-assistant/parts/AssistantHeader.d.ts +11 -0
  87. package/dist/components/assistant/xertica-assistant/parts/AssistantMessageBubble.d.ts +29 -0
  88. package/dist/components/assistant/xertica-assistant/parts/AssistantTabBar.d.ts +13 -0
  89. package/dist/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.d.ts +4 -0
  90. package/dist/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.d.ts +17 -0
  91. package/dist/components/assistant/xertica-assistant/parts/index.d.ts +16 -0
  92. package/dist/components/assistant/xertica-assistant/types.d.ts +106 -0
  93. package/dist/components/assistant/xertica-assistant/use-assistant.d.ts +125 -0
  94. package/dist/components/assistant/xertica-assistant/xertica-assistant.d.ts +8 -97
  95. package/dist/components/hooks/index.d.ts +3 -0
  96. package/dist/components/hooks/use-layout-shortcuts.d.ts +22 -0
  97. package/dist/components/layout/sidebar/index.d.ts +2 -0
  98. package/dist/components/layout/sidebar/sidebar.d.ts +80 -0
  99. package/dist/components/layout/sidebar/use-sidebar.d.ts +22 -0
  100. package/dist/components/media/audio-player/AudioPlayer.d.ts +4 -1
  101. package/dist/components/media/audio-player/use-audio-player.d.ts +72 -0
  102. package/dist/components/shared/CustomTooltipContent.d.ts +20 -0
  103. package/dist/components/shared/layout-constants.d.ts +1 -1
  104. package/dist/components/ui/alert/alert.d.ts +1 -1
  105. package/dist/components/ui/badge/badge.d.ts +1 -1
  106. package/dist/components/ui/button/button.d.ts +2 -2
  107. package/dist/components/ui/chart/chart.d.ts +162 -5
  108. package/dist/components/ui/file-upload/file-upload.d.ts +2 -0
  109. package/dist/components/ui/file-upload/index.d.ts +1 -0
  110. package/dist/components/ui/file-upload/use-file-upload.d.ts +49 -0
  111. package/dist/components/ui/pagination/index.d.ts +2 -0
  112. package/dist/components/ui/pagination/use-pagination.d.ts +78 -0
  113. package/dist/components/ui/rich-text-editor/index.d.ts +2 -0
  114. package/dist/components/ui/rich-text-editor/use-rich-text-editor.d.ts +107 -0
  115. package/dist/components/ui/stepper/index.d.ts +3 -1
  116. package/dist/components/ui/stepper/stepper.d.ts +2 -2
  117. package/dist/components/ui/stepper/use-stepper.d.ts +60 -0
  118. package/dist/components/ui/tree-view/index.d.ts +4 -1
  119. package/dist/components/ui/tree-view/tree-view.d.ts +4 -6
  120. package/dist/components/ui/tree-view/use-tree-view.d.ts +60 -0
  121. package/dist/contexts/AssistenteContext.d.ts +10 -49
  122. package/dist/hooks.cjs.js +30 -10
  123. package/dist/hooks.es.js +25 -4
  124. package/dist/index.cjs.js +20 -9
  125. package/dist/index.es.js +38 -27
  126. package/dist/layout.cjs.js +82 -1
  127. package/dist/layout.es.js +83 -2
  128. package/dist/media.cjs.js +1 -1
  129. package/dist/media.es.js +1 -1
  130. package/dist/pages.cjs.js +1 -1
  131. package/dist/pages.es.js +1 -1
  132. package/dist/rich-text-editor-BmsjY03B.js +2949 -0
  133. package/dist/rich-text-editor-GS2kpTAK.cjs +2966 -0
  134. package/dist/sidebar-CVUGHOS_.cjs +756 -0
  135. package/dist/sidebar-CmvwjnVb.js +757 -0
  136. package/dist/ui.cjs.js +12 -2
  137. package/dist/ui.es.js +24 -14
  138. package/dist/use-audio-player-Bkh23vQ3.js +177 -0
  139. package/dist/use-audio-player-Dn1NR9xN.cjs +176 -0
  140. package/dist/utils/color-utils.d.ts +51 -0
  141. package/dist/xertica-assistant-BMqdyRVi.js +2082 -0
  142. package/dist/xertica-assistant-Bj3vBCq_.cjs +2081 -0
  143. package/dist/xertica-ui.css +1 -1
  144. package/docs/ai-usage.md +28 -10
  145. package/docs/architecture-improvements.md +463 -0
  146. package/docs/architecture.md +77 -1
  147. package/docs/components/assistant-chart.md +1 -1
  148. package/docs/components/assistant.md +159 -0
  149. package/docs/components/audio-player.md +46 -0
  150. package/docs/components/branding.md +251 -0
  151. package/docs/components/chart.md +354 -39
  152. package/docs/components/code-block.md +108 -0
  153. package/docs/components/file-upload.md +119 -2
  154. package/docs/components/formatted-document.md +113 -0
  155. package/docs/components/hooks.md +430 -0
  156. package/docs/components/image-with-fallback.md +106 -0
  157. package/docs/components/map-layers.md +140 -0
  158. package/docs/components/modern-chat-input.md +163 -0
  159. package/docs/components/pages.md +351 -0
  160. package/docs/components/pagination.md +187 -0
  161. package/docs/components/rich-text-editor.md +164 -0
  162. package/docs/components/sidebar.md +153 -4
  163. package/docs/components/stepper.md +157 -12
  164. package/docs/components/tree-view.md +164 -6
  165. package/docs/doc-audit.md +223 -0
  166. package/docs/getting-started.md +155 -1
  167. package/docs/guidelines.md +14 -8
  168. package/docs/layout.md +2 -2
  169. package/docs/llms.md +29 -9
  170. package/docs/patterns/detail-page.md +276 -0
  171. package/docs/patterns/settings.md +346 -0
  172. package/docs/patterns/wizard.md +217 -0
  173. package/guidelines/Guidelines.md +5 -3
  174. package/llms.txt +1 -1
  175. package/package.json +10 -10
  176. package/styles/xertica/tokens.css +41 -12
  177. package/templates/CLAUDE.md +16 -6
  178. package/templates/guidelines/Guidelines.md +16 -4
  179. package/templates/package.json +3 -3
  180. package/templates/src/styles/xertica/tokens.css +39 -10
  181. 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 Are Non-Negotiable
50
+ ### 2.3 Color Tokens Context-Dependent Rules
51
51
 
52
- Never use raw hex values, RGB, or generic Tailwind color classes in application code:
52
+ Color usage depends on the **semantic context** of the element:
53
53
 
54
+ **Always forbidden (all contexts):**
54
55
  ```tsx
55
- // ❌ Forbidden
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: 256px`. Read `docs/layout.md` for details.
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 errors | Non-semantic | Use `text-destructive` |
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
@@ -1,4 +1,4 @@
1
- # Xertica UI — Documentation Audit Report
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**: Ensure your data objects contain keys that match the `dataKey` set in the internal implementation (currently `month`, `desktop`, and `mobile`).
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.