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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Pattern: Settings Page
|
|
2
|
+
|
|
3
|
+
A structured settings page with categorized sections, form fields, and save/cancel actions. Suitable for user preferences, application configuration, and account management.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Use the Settings pattern when:
|
|
10
|
+
- Users need to configure personal preferences (theme, language, notifications)
|
|
11
|
+
- Administrators need to manage application-level configuration
|
|
12
|
+
- Account management (profile, password, API keys) is required
|
|
13
|
+
- Settings are grouped into logical categories (General, Security, Notifications, etc.)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Components Used
|
|
18
|
+
|
|
19
|
+
- [`PageHeader`](../components/page-header.md) — page title and breadcrumb
|
|
20
|
+
- [`Card`](../components/card.md) — settings section container
|
|
21
|
+
- [`Tabs`](../components/tabs.md) — category navigation (sidebar or top tabs)
|
|
22
|
+
- [`Form`](../components/form.md) + form elements — settings fields
|
|
23
|
+
- [`Switch`](../components/switch.md) — boolean toggles
|
|
24
|
+
- [`Select`](../components/select.md) — option dropdowns
|
|
25
|
+
- [`Input`](../components/input.md) — text settings
|
|
26
|
+
- [`Button`](../components/button.md) — Save and Cancel actions
|
|
27
|
+
- [`Separator`](../components/separator.md) — section dividers
|
|
28
|
+
- [`Sonner`](../components/sonner.md) — success/error toast after save
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Basic Structure
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import {
|
|
36
|
+
Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
|
|
37
|
+
Tabs, TabsContent, TabsList, TabsTrigger,
|
|
38
|
+
Button, Input, Label, Switch, Select, SelectContent, SelectItem,
|
|
39
|
+
SelectTrigger, SelectValue, Separator,
|
|
40
|
+
} from 'xertica-ui/ui';
|
|
41
|
+
import { PageHeader, PageHeaderHeading, PageHeaderDescription } from 'xertica-ui/ui';
|
|
42
|
+
import { toast } from 'sonner';
|
|
43
|
+
|
|
44
|
+
export function SettingsPage() {
|
|
45
|
+
return (
|
|
46
|
+
<div className="flex flex-col h-full overflow-hidden">
|
|
47
|
+
<PageHeader>
|
|
48
|
+
<div>
|
|
49
|
+
<PageHeaderHeading>Configurações</PageHeaderHeading>
|
|
50
|
+
<PageHeaderDescription>
|
|
51
|
+
Gerencie suas preferências e configurações da conta.
|
|
52
|
+
</PageHeaderDescription>
|
|
53
|
+
</div>
|
|
54
|
+
</PageHeader>
|
|
55
|
+
|
|
56
|
+
<div className="flex-1 overflow-y-auto p-6">
|
|
57
|
+
<Tabs defaultValue="general" className="space-y-6">
|
|
58
|
+
<TabsList>
|
|
59
|
+
<TabsTrigger value="general">Geral</TabsTrigger>
|
|
60
|
+
<TabsTrigger value="security">Segurança</TabsTrigger>
|
|
61
|
+
<TabsTrigger value="notifications">Notificações</TabsTrigger>
|
|
62
|
+
<TabsTrigger value="api">API Keys</TabsTrigger>
|
|
63
|
+
</TabsList>
|
|
64
|
+
|
|
65
|
+
<TabsContent value="general">
|
|
66
|
+
<GeneralSettings />
|
|
67
|
+
</TabsContent>
|
|
68
|
+
|
|
69
|
+
<TabsContent value="security">
|
|
70
|
+
<SecuritySettings />
|
|
71
|
+
</TabsContent>
|
|
72
|
+
|
|
73
|
+
<TabsContent value="notifications">
|
|
74
|
+
<NotificationSettings />
|
|
75
|
+
</TabsContent>
|
|
76
|
+
|
|
77
|
+
<TabsContent value="api">
|
|
78
|
+
<ApiKeySettings />
|
|
79
|
+
</TabsContent>
|
|
80
|
+
</Tabs>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## General Settings Section
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
function GeneralSettings() {
|
|
93
|
+
const [name, setName] = useState('João Silva');
|
|
94
|
+
const [language, setLanguage] = useState('pt-BR');
|
|
95
|
+
const [theme, setTheme] = useState('system');
|
|
96
|
+
|
|
97
|
+
const handleSave = () => {
|
|
98
|
+
// persist settings
|
|
99
|
+
toast.success('Configurações salvas com sucesso.');
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className="space-y-6 max-w-2xl">
|
|
104
|
+
{/* Profile */}
|
|
105
|
+
<Card>
|
|
106
|
+
<CardHeader>
|
|
107
|
+
<CardTitle>Perfil</CardTitle>
|
|
108
|
+
<CardDescription>Informações básicas da sua conta.</CardDescription>
|
|
109
|
+
</CardHeader>
|
|
110
|
+
<CardContent className="space-y-4">
|
|
111
|
+
<div className="space-y-2">
|
|
112
|
+
<Label htmlFor="name">Nome completo</Label>
|
|
113
|
+
<Input
|
|
114
|
+
id="name"
|
|
115
|
+
value={name}
|
|
116
|
+
onChange={e => setName(e.target.value)}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</CardContent>
|
|
120
|
+
</Card>
|
|
121
|
+
|
|
122
|
+
{/* Preferences */}
|
|
123
|
+
<Card>
|
|
124
|
+
<CardHeader>
|
|
125
|
+
<CardTitle>Preferências</CardTitle>
|
|
126
|
+
<CardDescription>Idioma e aparência da interface.</CardDescription>
|
|
127
|
+
</CardHeader>
|
|
128
|
+
<CardContent className="space-y-4">
|
|
129
|
+
<div className="space-y-2">
|
|
130
|
+
<Label>Idioma</Label>
|
|
131
|
+
<Select value={language} onValueChange={setLanguage}>
|
|
132
|
+
<SelectTrigger>
|
|
133
|
+
<SelectValue />
|
|
134
|
+
</SelectTrigger>
|
|
135
|
+
<SelectContent>
|
|
136
|
+
<SelectItem value="pt-BR">Português (Brasil)</SelectItem>
|
|
137
|
+
<SelectItem value="en">English</SelectItem>
|
|
138
|
+
<SelectItem value="es">Español</SelectItem>
|
|
139
|
+
</SelectContent>
|
|
140
|
+
</Select>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div className="space-y-2">
|
|
144
|
+
<Label>Tema</Label>
|
|
145
|
+
<Select value={theme} onValueChange={setTheme}>
|
|
146
|
+
<SelectTrigger>
|
|
147
|
+
<SelectValue />
|
|
148
|
+
</SelectTrigger>
|
|
149
|
+
<SelectContent>
|
|
150
|
+
<SelectItem value="light">Claro</SelectItem>
|
|
151
|
+
<SelectItem value="dark">Escuro</SelectItem>
|
|
152
|
+
<SelectItem value="system">Sistema</SelectItem>
|
|
153
|
+
</SelectContent>
|
|
154
|
+
</Select>
|
|
155
|
+
</div>
|
|
156
|
+
</CardContent>
|
|
157
|
+
<CardFooter className="flex justify-end gap-2">
|
|
158
|
+
<Button variant="outline">Cancelar</Button>
|
|
159
|
+
<Button onClick={handleSave}>Salvar alterações</Button>
|
|
160
|
+
</CardFooter>
|
|
161
|
+
</Card>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Notification Settings Section
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
function NotificationSettings() {
|
|
173
|
+
const [notifications, setNotifications] = useState({
|
|
174
|
+
email: true,
|
|
175
|
+
push: false,
|
|
176
|
+
weeklyReport: true,
|
|
177
|
+
securityAlerts: true,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const toggle = (key: keyof typeof notifications) => {
|
|
181
|
+
setNotifications(prev => ({ ...prev, [key]: !prev[key] }));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const items = [
|
|
185
|
+
{ key: 'email', label: 'Notificações por e-mail', description: 'Receba atualizações no seu e-mail' },
|
|
186
|
+
{ key: 'push', label: 'Notificações push', description: 'Notificações no navegador' },
|
|
187
|
+
{ key: 'weeklyReport', label: 'Relatório semanal', description: 'Resumo de atividades toda segunda-feira' },
|
|
188
|
+
{ key: 'securityAlerts', label: 'Alertas de segurança', description: 'Notificações sobre acessos suspeitos' },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<Card className="max-w-2xl">
|
|
193
|
+
<CardHeader>
|
|
194
|
+
<CardTitle>Notificações</CardTitle>
|
|
195
|
+
<CardDescription>Escolha quais notificações deseja receber.</CardDescription>
|
|
196
|
+
</CardHeader>
|
|
197
|
+
<CardContent className="space-y-0">
|
|
198
|
+
{items.map(({ key, label, description }, index) => (
|
|
199
|
+
<div key={key}>
|
|
200
|
+
<div className="flex items-center justify-between py-4">
|
|
201
|
+
<div className="space-y-0.5">
|
|
202
|
+
<Label className="text-base">{label}</Label>
|
|
203
|
+
<p className="text-sm text-muted-foreground">{description}</p>
|
|
204
|
+
</div>
|
|
205
|
+
<Switch
|
|
206
|
+
checked={notifications[key as keyof typeof notifications]}
|
|
207
|
+
onCheckedChange={() => toggle(key as keyof typeof notifications)}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
{index < items.length - 1 && <Separator />}
|
|
211
|
+
</div>
|
|
212
|
+
))}
|
|
213
|
+
</CardContent>
|
|
214
|
+
<CardFooter className="flex justify-end">
|
|
215
|
+
<Button onClick={() => toast.success('Preferências de notificação salvas.')}>
|
|
216
|
+
Salvar
|
|
217
|
+
</Button>
|
|
218
|
+
</CardFooter>
|
|
219
|
+
</Card>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Security Settings Section
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
function SecuritySettings() {
|
|
230
|
+
const [currentPassword, setCurrentPassword] = useState('');
|
|
231
|
+
const [newPassword, setNewPassword] = useState('');
|
|
232
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
233
|
+
|
|
234
|
+
const handleChangePassword = () => {
|
|
235
|
+
if (newPassword !== confirmPassword) {
|
|
236
|
+
toast.error('As senhas não coincidem.');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// call API
|
|
240
|
+
toast.success('Senha alterada com sucesso.');
|
|
241
|
+
setCurrentPassword('');
|
|
242
|
+
setNewPassword('');
|
|
243
|
+
setConfirmPassword('');
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<Card className="max-w-2xl">
|
|
248
|
+
<CardHeader>
|
|
249
|
+
<CardTitle>Alterar Senha</CardTitle>
|
|
250
|
+
<CardDescription>Use uma senha forte com pelo menos 8 caracteres.</CardDescription>
|
|
251
|
+
</CardHeader>
|
|
252
|
+
<CardContent className="space-y-4">
|
|
253
|
+
<div className="space-y-2">
|
|
254
|
+
<Label htmlFor="current-password">Senha atual</Label>
|
|
255
|
+
<Input
|
|
256
|
+
id="current-password"
|
|
257
|
+
type="password"
|
|
258
|
+
value={currentPassword}
|
|
259
|
+
onChange={e => setCurrentPassword(e.target.value)}
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="space-y-2">
|
|
263
|
+
<Label htmlFor="new-password">Nova senha</Label>
|
|
264
|
+
<Input
|
|
265
|
+
id="new-password"
|
|
266
|
+
type="password"
|
|
267
|
+
value={newPassword}
|
|
268
|
+
onChange={e => setNewPassword(e.target.value)}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
<div className="space-y-2">
|
|
272
|
+
<Label htmlFor="confirm-password">Confirmar nova senha</Label>
|
|
273
|
+
<Input
|
|
274
|
+
id="confirm-password"
|
|
275
|
+
type="password"
|
|
276
|
+
value={confirmPassword}
|
|
277
|
+
onChange={e => setConfirmPassword(e.target.value)}
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
</CardContent>
|
|
281
|
+
<CardFooter className="flex justify-end">
|
|
282
|
+
<Button onClick={handleChangePassword}>Alterar senha</Button>
|
|
283
|
+
</CardFooter>
|
|
284
|
+
</Card>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## API Key Settings Section
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { useApiKey } from 'xertica-ui/hooks';
|
|
295
|
+
|
|
296
|
+
function ApiKeySettings() {
|
|
297
|
+
const { geminiApiKey, setGeminiApiKey } = useApiKey();
|
|
298
|
+
const [localKey, setLocalKey] = useState(geminiApiKey);
|
|
299
|
+
|
|
300
|
+
const handleSave = () => {
|
|
301
|
+
setGeminiApiKey(localKey);
|
|
302
|
+
toast.success('API key salva com sucesso.');
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<Card className="max-w-2xl">
|
|
307
|
+
<CardHeader>
|
|
308
|
+
<CardTitle>Chaves de API</CardTitle>
|
|
309
|
+
<CardDescription>
|
|
310
|
+
Configure as chaves de API para os serviços integrados.
|
|
311
|
+
</CardDescription>
|
|
312
|
+
</CardHeader>
|
|
313
|
+
<CardContent className="space-y-4">
|
|
314
|
+
<div className="space-y-2">
|
|
315
|
+
<Label htmlFor="gemini-key">Google Gemini API Key</Label>
|
|
316
|
+
<Input
|
|
317
|
+
id="gemini-key"
|
|
318
|
+
type="password"
|
|
319
|
+
value={localKey}
|
|
320
|
+
onChange={e => setLocalKey(e.target.value)}
|
|
321
|
+
placeholder="AIza..."
|
|
322
|
+
/>
|
|
323
|
+
<p className="text-xs text-muted-foreground">
|
|
324
|
+
Necessária para o assistente de IA em modo real.
|
|
325
|
+
</p>
|
|
326
|
+
</div>
|
|
327
|
+
</CardContent>
|
|
328
|
+
<CardFooter className="flex justify-end">
|
|
329
|
+
<Button onClick={handleSave}>Salvar chave</Button>
|
|
330
|
+
</CardFooter>
|
|
331
|
+
</Card>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## AI Rules
|
|
339
|
+
|
|
340
|
+
> [!IMPORTANT]
|
|
341
|
+
> - **One `Card` per settings group**: Group related settings into a single `Card` with a `CardHeader` (title + description), `CardContent` (fields), and `CardFooter` (Save/Cancel buttons).
|
|
342
|
+
> - **Use `Switch` for boolean settings**: Never use `Checkbox` for on/off toggles in settings. `Switch` is the correct component for this context.
|
|
343
|
+
> - **Always show a toast after save**: Use `toast.success()` from `sonner` to confirm that settings were saved. Use `toast.error()` for validation failures.
|
|
344
|
+
> - **`Tabs` for categories**: When there are more than 2 settings categories, use `Tabs` to organize them. Do not use a custom sidebar navigation.
|
|
345
|
+
> - **Password fields use `type="password"`**: Never render password inputs as plain text. Always use `<Input type="password">`.
|
|
346
|
+
> - **API keys via `useApiKey`**: Use the `useApiKey()` hook from `xertica-ui/hooks` to read and write API keys. Do not store them in component state or `localStorage` directly.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Pattern: Wizard (Multi-Step Form)
|
|
2
|
+
|
|
3
|
+
A multi-step form pattern using the `Stepper` component to guide users through a sequential process with validation at each step.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Use the Wizard pattern when:
|
|
10
|
+
- A form has more than 4–5 fields and benefits from being broken into logical sections
|
|
11
|
+
- Each step has its own validation before proceeding
|
|
12
|
+
- The user needs to review a summary before final submission
|
|
13
|
+
- The process has a clear linear sequence (e.g., onboarding, checkout, configuration)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Components Used
|
|
18
|
+
|
|
19
|
+
- [`Stepper`](../components/stepper.md) — step indicator and navigation
|
|
20
|
+
- [`Card`](../components/card.md) — content container for each step
|
|
21
|
+
- [`Button`](../components/button.md) — Next, Back, and Submit actions
|
|
22
|
+
- [`Form`](../components/form.md) + [`Input`](../components/input.md) — form fields with validation
|
|
23
|
+
- [`PageHeader`](../components/page-header.md) — page title and breadcrumb
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Basic Structure
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { useState } from 'react';
|
|
31
|
+
import { Stepper } from 'xertica-ui/ui';
|
|
32
|
+
import { Button } from 'xertica-ui/ui';
|
|
33
|
+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from 'xertica-ui/ui';
|
|
34
|
+
|
|
35
|
+
const STEPS = [
|
|
36
|
+
{ id: 'info', label: 'Informações Básicas' },
|
|
37
|
+
{ id: 'address', label: 'Endereço' },
|
|
38
|
+
{ id: 'review', label: 'Revisão' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export function WizardExample() {
|
|
42
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
43
|
+
const [formData, setFormData] = useState({
|
|
44
|
+
name: '', email: '',
|
|
45
|
+
street: '', city: '',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const isLastStep = currentStep === STEPS.length - 1;
|
|
49
|
+
|
|
50
|
+
const handleNext = () => {
|
|
51
|
+
if (!isLastStep) setCurrentStep(s => s + 1);
|
|
52
|
+
else handleSubmit();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleBack = () => {
|
|
56
|
+
if (currentStep > 0) setCurrentStep(s => s - 1);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleSubmit = () => {
|
|
60
|
+
console.log('Submitted:', formData);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="max-w-2xl mx-auto p-6 space-y-6">
|
|
65
|
+
<Stepper
|
|
66
|
+
steps={STEPS}
|
|
67
|
+
currentStep={currentStep}
|
|
68
|
+
onStepClick={setCurrentStep}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<Card>
|
|
72
|
+
<CardHeader>
|
|
73
|
+
<CardTitle>{STEPS[currentStep].label}</CardTitle>
|
|
74
|
+
</CardHeader>
|
|
75
|
+
|
|
76
|
+
<CardContent>
|
|
77
|
+
{currentStep === 0 && <StepBasicInfo data={formData} onChange={setFormData} />}
|
|
78
|
+
{currentStep === 1 && <StepAddress data={formData} onChange={setFormData} />}
|
|
79
|
+
{currentStep === 2 && <StepReview data={formData} />}
|
|
80
|
+
</CardContent>
|
|
81
|
+
|
|
82
|
+
<CardFooter className="flex justify-between">
|
|
83
|
+
<Button
|
|
84
|
+
variant="outline"
|
|
85
|
+
onClick={handleBack}
|
|
86
|
+
disabled={currentStep === 0}
|
|
87
|
+
>
|
|
88
|
+
Voltar
|
|
89
|
+
</Button>
|
|
90
|
+
<Button onClick={handleNext}>
|
|
91
|
+
{isLastStep ? 'Confirmar' : 'Próximo'}
|
|
92
|
+
</Button>
|
|
93
|
+
</CardFooter>
|
|
94
|
+
</Card>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Step Components
|
|
103
|
+
|
|
104
|
+
Each step is a focused form section:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
function StepBasicInfo({ data, onChange }) {
|
|
108
|
+
return (
|
|
109
|
+
<div className="space-y-4">
|
|
110
|
+
<div className="space-y-2">
|
|
111
|
+
<Label htmlFor="name">Nome completo</Label>
|
|
112
|
+
<Input
|
|
113
|
+
id="name"
|
|
114
|
+
value={data.name}
|
|
115
|
+
onChange={e => onChange(prev => ({ ...prev, name: e.target.value }))}
|
|
116
|
+
placeholder="João Silva"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
<div className="space-y-2">
|
|
120
|
+
<Label htmlFor="email">E-mail</Label>
|
|
121
|
+
<Input
|
|
122
|
+
id="email"
|
|
123
|
+
type="email"
|
|
124
|
+
value={data.email}
|
|
125
|
+
onChange={e => onChange(prev => ({ ...prev, email: e.target.value }))}
|
|
126
|
+
placeholder="joao@empresa.com"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function StepReview({ data }) {
|
|
134
|
+
return (
|
|
135
|
+
<div className="space-y-3">
|
|
136
|
+
<div className="flex justify-between py-2 border-b border-border">
|
|
137
|
+
<span className="text-muted-foreground">Nome</span>
|
|
138
|
+
<span className="font-medium">{data.name}</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div className="flex justify-between py-2 border-b border-border">
|
|
141
|
+
<span className="text-muted-foreground">E-mail</span>
|
|
142
|
+
<span className="font-medium">{data.email}</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div className="flex justify-between py-2 border-b border-border">
|
|
145
|
+
<span className="text-muted-foreground">Endereço</span>
|
|
146
|
+
<span className="font-medium">{data.street}, {data.city}</span>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## With React Hook Form Validation
|
|
156
|
+
|
|
157
|
+
For production forms, integrate `react-hook-form` with per-step validation:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { useForm } from 'react-hook-form';
|
|
161
|
+
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from 'xertica-ui/ui';
|
|
162
|
+
|
|
163
|
+
function StepBasicInfoValidated({ onValid }) {
|
|
164
|
+
const form = useForm({
|
|
165
|
+
defaultValues: { name: '', email: '' },
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<Form {...form}>
|
|
170
|
+
<form onSubmit={form.handleSubmit(onValid)} className="space-y-4">
|
|
171
|
+
<FormField
|
|
172
|
+
control={form.control}
|
|
173
|
+
name="name"
|
|
174
|
+
rules={{ required: 'Nome é obrigatório' }}
|
|
175
|
+
render={({ field }) => (
|
|
176
|
+
<FormItem>
|
|
177
|
+
<FormLabel>Nome</FormLabel>
|
|
178
|
+
<FormControl>
|
|
179
|
+
<Input {...field} placeholder="João Silva" />
|
|
180
|
+
</FormControl>
|
|
181
|
+
<FormMessage />
|
|
182
|
+
</FormItem>
|
|
183
|
+
)}
|
|
184
|
+
/>
|
|
185
|
+
<Button type="submit">Próximo</Button>
|
|
186
|
+
</form>
|
|
187
|
+
</Form>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Progress Persistence
|
|
195
|
+
|
|
196
|
+
For long wizards, persist progress in `localStorage` or a URL query parameter:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// Save step to URL
|
|
200
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
201
|
+
const currentStep = parseInt(searchParams.get('step') || '0');
|
|
202
|
+
|
|
203
|
+
const handleNext = () => {
|
|
204
|
+
setSearchParams({ step: String(currentStep + 1) });
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## AI Rules
|
|
211
|
+
|
|
212
|
+
> [!IMPORTANT]
|
|
213
|
+
> - **Use `Stepper` for the indicator**: Never build a custom step indicator with raw `div` elements. The `Stepper` component handles accessibility, active state, and completed state automatically.
|
|
214
|
+
> - **One `Card` per step**: Each step's content should be inside a single `Card`. Do not render multiple cards or use `Tabs` as a substitute for `Stepper`.
|
|
215
|
+
> - **Validate before advancing**: Always validate the current step's fields before calling `setCurrentStep(s => s + 1)`. Use `react-hook-form`'s `trigger()` method for partial validation.
|
|
216
|
+
> - **Review step is read-only**: The final review step should display a summary of all collected data. It must not contain editable fields.
|
|
217
|
+
> - **Back button is never disabled on step > 0**: Users must always be able to go back. Only disable the Back button on the first step.
|
package/guidelines/Guidelines.md
CHANGED
|
@@ -95,17 +95,19 @@ The root `from 'xertica-ui'` exports everything and remains supported for backwa
|
|
|
95
95
|
|
|
96
96
|
## 5. Design Tokens — Rules for Component Authors
|
|
97
97
|
|
|
98
|
-
All components must use semantic CSS tokens exclusively. Never hardcode colors, radii, or shadows:
|
|
98
|
+
All **library components** (`components/ui/`, `components/layout/`, etc.) must use semantic CSS tokens exclusively. Never hardcode colors, radii, or shadows in library source:
|
|
99
99
|
|
|
100
100
|
```tsx
|
|
101
|
-
// ✅ Correct
|
|
101
|
+
// ✅ Correct — library component
|
|
102
102
|
className="bg-primary text-primary-foreground rounded-[var(--radius)]"
|
|
103
103
|
|
|
104
|
-
// ❌ Wrong
|
|
104
|
+
// ❌ Wrong — library component
|
|
105
105
|
className="bg-blue-600 text-white rounded-lg"
|
|
106
106
|
style={{ backgroundColor: '#3b82f6' }}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
> **Note for Storybook stories and consumer applications:** The strict "semantic tokens only" rule applies to **library component internals** and to **semantic/status contexts** (error states, warning banners, success indicators, status badges) in all code. For layout, spacing, and general non-semantic UI in stories or consumer apps, standard Tailwind color utilities (`bg-blue-500`, `text-gray-700`) are acceptable when no semantic token maps to the intent. Raw hex values and `rgb()`/`hsl()` literals are **never** acceptable in any context.
|
|
110
|
+
|
|
109
111
|
### Token reference
|
|
110
112
|
|
|
111
113
|
```
|
package/llms.txt
CHANGED
|
@@ -51,7 +51,7 @@ import 'xertica-ui/style.css';
|
|
|
51
51
|
- [Calendar](docs/components/calendar.md): Date picker. Almost always wrapped in `Popover`.
|
|
52
52
|
- [Card](docs/components/card.md): Primary content container. Never recreate with raw `<div>`.
|
|
53
53
|
- [Carousel](docs/components/carousel.md): Horizontal content slider. Built on embla-carousel.
|
|
54
|
-
- [Chart](docs/components/chart.md): Token-aware charts built on Recharts. Use `ChartContainer`; dashboard-ready wrappers
|
|
54
|
+
- [Chart](docs/components/chart.md): Token-aware charts built on Recharts. Use `ChartContainer`; 11 dashboard-ready wrappers: `ChartCard`, `DashboardBarChart`, `DashboardLineChart`, `HorizontalBarChart`, `InteractiveTimeSeriesChart`, `ComboMetricChart`, `DonutBreakdownChart`, `SparklineChart`, `RadarMetricChart`, `PieMetricChart`, `RadialBarMetricChart`, and `GaugeChart` (pure SVG semicircle gauge with thresholds).
|
|
55
55
|
- [Checkbox](docs/components/checkbox.md): Boolean toggle or multi-select option.
|
|
56
56
|
- [Collapsible](docs/components/collapsible.md): Single expandable section — no sibling management.
|
|
57
57
|
- [Command](docs/components/command.md): Command palette (⌘K). Searchable action list.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xertica-ui",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "Xertica UI — Enterprise-grade React design system with Tailwind CSS v4, Radix UI, and AI-first documentation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs.js",
|
|
@@ -154,12 +154,12 @@
|
|
|
154
154
|
"react-router-dom": "^7.1.3"
|
|
155
155
|
},
|
|
156
156
|
"devDependencies": {
|
|
157
|
-
"@chromatic-com/storybook": "^5.1
|
|
158
|
-
"@storybook/addon-a11y": "10.
|
|
159
|
-
"@storybook/addon-docs": "10.
|
|
160
|
-
"@storybook/addon-onboarding": "10.
|
|
161
|
-
"@storybook/addon-vitest": "^10.
|
|
162
|
-
"@storybook/react-vite": "10.
|
|
157
|
+
"@chromatic-com/storybook": "^5.2.1",
|
|
158
|
+
"@storybook/addon-a11y": "10.4.0",
|
|
159
|
+
"@storybook/addon-docs": "10.4.0",
|
|
160
|
+
"@storybook/addon-onboarding": "10.4.0",
|
|
161
|
+
"@storybook/addon-vitest": "^10.4.0",
|
|
162
|
+
"@storybook/react-vite": "10.4.0",
|
|
163
163
|
"@tailwindcss/postcss": "^4.0.0",
|
|
164
164
|
"@testing-library/jest-dom": "^6.9.1",
|
|
165
165
|
"@testing-library/react": "^16.3.2",
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
"eslint": "^9.18.0",
|
|
183
183
|
"eslint-plugin-react-hooks": "^5.1.0",
|
|
184
184
|
"eslint-plugin-react-refresh": "^0.4.16",
|
|
185
|
-
"eslint-plugin-storybook": "^10.
|
|
185
|
+
"eslint-plugin-storybook": "^10.4.0",
|
|
186
186
|
"globals": "^15.14.0",
|
|
187
187
|
"jsdom": "^29.0.2",
|
|
188
188
|
"playwright": "^1.59.1",
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
"react-dom": "^18.3.1",
|
|
192
192
|
"react-router-dom": "^7.1.3",
|
|
193
193
|
"set-cookie-parser": "^3.1.0",
|
|
194
|
-
"storybook": "^10.
|
|
194
|
+
"storybook": "^10.4.0",
|
|
195
195
|
"tailwindcss": "^4.0.0",
|
|
196
196
|
"ts-morph": "^28.0.0",
|
|
197
197
|
"ts-node": "^10.9.2",
|
|
@@ -200,4 +200,4 @@
|
|
|
200
200
|
"vite": "^6.0.5",
|
|
201
201
|
"vitest": "^4.1.5"
|
|
202
202
|
}
|
|
203
|
-
}
|
|
203
|
+
}
|