xertica-ui 2.3.0 → 2.4.1

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 (105) hide show
  1. package/CHANGELOG.md +22 -2
  2. package/README.md +33 -22
  3. package/bin/cli.ts +136 -47
  4. package/bin/language-config.ts +5 -8
  5. package/components/assistant/modern-chat-input/ModernChatInput.tsx +17 -7
  6. package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +1 -3
  7. package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +13 -3
  8. package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +10 -6
  9. package/components/assistant/xertica-assistant/xertica-assistant.tsx +1 -3
  10. package/components/blocks/card-patterns/FeatureCardSkeleton.tsx +1 -6
  11. package/components/blocks/card-patterns/ProfileCard.tsx +1 -3
  12. package/components/blocks/card-patterns/ProjectCardSkeleton.tsx +1 -6
  13. package/components/brand/language-selector/language-selector.stories.tsx +1 -4
  14. package/components/brand/theme-toggle/ThemeToggle.tsx +5 -1
  15. package/components/brand/xertica-provider/XerticaProvider.tsx +1 -4
  16. package/components/index.ts +1 -5
  17. package/components/layout/sidebar/sidebar.tsx +9 -3
  18. package/components/media/audio-player/AudioPlayer.tsx +4 -2
  19. package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +188 -188
  20. package/components/pages/home-content/HomeContent.tsx +55 -55
  21. package/components/pages/home-page/HomePage.tsx +5 -1
  22. package/components/pages/login-page/LoginPage.tsx +4 -2
  23. package/components/pages/reset-password-page/ResetPasswordPage.tsx +7 -3
  24. package/components/pages/template-content/TemplateContent.tsx +268 -149
  25. package/components/pages/verify-email-page/VerifyEmailPage.tsx +9 -9
  26. package/components/shared/error-boundary.stories.tsx +114 -132
  27. package/components/shared/error-boundary.tsx +150 -154
  28. package/components/shared/error-fallbacks.tsx +222 -226
  29. package/components/ui/stats-card/stats-card-skeleton.tsx +1 -3
  30. package/components/ui/stats-card/stats-card.stories.tsx +18 -0
  31. package/components/ui/stats-card/stats-card.tsx +18 -2
  32. package/components.json +512 -892
  33. package/contexts/AuthContext.tsx +121 -118
  34. package/contexts/LanguageContext.tsx +1 -2
  35. package/dist/AssistantChart-BKVtGUKF.js +3383 -0
  36. package/dist/AssistantChart-WeycT5Pd.cjs +3551 -0
  37. package/dist/VerifyEmailPage-Bp1XXl3H.cjs +3305 -0
  38. package/dist/VerifyEmailPage-DGhuIqkb.js +3296 -0
  39. package/dist/XerticaProvider-BErr83Bg.js +42 -0
  40. package/dist/XerticaProvider-CwOkHxiT.cjs +44 -0
  41. package/dist/XerticaXLogo-BX3ueACh.js +255 -0
  42. package/dist/XerticaXLogo-qBPhwK3g.cjs +260 -0
  43. package/dist/assistant.cjs.js +1 -1
  44. package/dist/assistant.es.js +1 -1
  45. package/dist/brand.cjs.js +2 -2
  46. package/dist/brand.es.js +2 -2
  47. package/dist/cli.js +90 -37
  48. package/dist/components/brand/theme-toggle/ThemeToggle.d.ts +1 -1
  49. package/dist/components/index.d.ts +1 -1
  50. package/dist/components/ui/stats-card/stats-card.d.ts +10 -0
  51. package/dist/index.cjs.js +6 -6
  52. package/dist/index.es.js +6 -6
  53. package/dist/layout.cjs.js +1 -1
  54. package/dist/layout.es.js +1 -1
  55. package/dist/pages.cjs.js +1 -1
  56. package/dist/pages.es.js +1 -1
  57. package/dist/sidebar-B4ZWaMrE.js +792 -0
  58. package/dist/sidebar-BS1p2V7t.cjs +795 -0
  59. package/dist/ui.cjs.js +1 -1
  60. package/dist/ui.es.js +1 -1
  61. package/dist/xertica-assistant-B1NaSFFj.js +2173 -0
  62. package/dist/xertica-assistant-CIaUlbIt.cjs +2180 -0
  63. package/dist/xertica-ui.css +1 -1
  64. package/docs/architecture-improvements.md +5 -5
  65. package/docs/architecture.md +16 -10
  66. package/docs/components/card-patterns.md +19 -17
  67. package/docs/components/error-boundary.md +201 -191
  68. package/docs/components/hooks.md +15 -13
  69. package/docs/components/language-selector.md +20 -16
  70. package/docs/components/pages.md +323 -309
  71. package/docs/components/stats-card.md +20 -2
  72. package/docs/doc-audit.md +12 -11
  73. package/docs/getting-started.md +41 -28
  74. package/docs/guidelines.md +14 -12
  75. package/docs/i18n.md +61 -57
  76. package/docs/installation.md +268 -267
  77. package/docs/llms.md +17 -17
  78. package/docs/state-management.md +17 -17
  79. package/guidelines/Guidelines.md +17 -14
  80. package/llms-compact.txt +1 -1
  81. package/llms-full.txt +11553 -7133
  82. package/llms.txt +1 -1
  83. package/package.json +1 -1
  84. package/styles/xertica/base.css +90 -84
  85. package/templates/CLAUDE.md +16 -1
  86. package/templates/guidelines/Guidelines.md +42 -18
  87. package/templates/package.json +3 -3
  88. package/templates/src/app/components/AuthGuard.tsx +131 -82
  89. package/templates/src/features/auth/ui/AuthPageShell.tsx +32 -32
  90. package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +1 -3
  91. package/templates/src/features/auth/ui/ResetPasswordContent.tsx +6 -2
  92. package/templates/src/features/auth/ui/VerifyEmailContent.tsx +2 -6
  93. package/templates/src/features/home/data/mock.ts +41 -35
  94. package/templates/src/features/home/ui/HomeContent.tsx +62 -64
  95. package/templates/src/features/template/ui/CrudTemplate.tsx +1 -4
  96. package/templates/src/features/template/ui/LoginTemplate.tsx +1 -1
  97. package/templates/src/features/template/ui/TemplateContent.tsx +28 -20
  98. package/templates/src/locales/en/pages/templates.json +17 -17
  99. package/templates/src/locales/es/pages/templates.json +17 -17
  100. package/templates/src/locales/pt-BR/pages/templates.json +17 -17
  101. package/templates/src/pages/AssistantPage.tsx +26 -20
  102. package/templates/src/pages/HomePage.tsx +5 -1
  103. package/templates/src/shared/error-boundary.tsx +150 -154
  104. package/templates/src/shared/error-fallbacks.tsx +222 -226
  105. package/templates/vite.config.ts +12 -9
@@ -29,7 +29,9 @@ Built on top of `<Card>`. Use it for dashboard stat widgets where a consistent m
29
29
  | `description` | `string` | No | Additional supplementary text below the value |
30
30
  | `trend` | `{ value: number; label?: string }` | No | Trend object. `value` is a number (positive = up, negative = down). `label` overrides `description` text. |
31
31
  | `icon` | `ReactNode` | No | Icon from `lucide-react` |
32
- | `className` | `string` | No | Additional CSS classes |
32
+ | `iconColor` | `string` | No | Tailwind class for the icon text/foreground color (defaults to `text-muted-foreground`) |
33
+ | `iconBg` | `string` | No | Tailwind class for the icon background color (defaults to `bg-muted`) |
34
+ | `className` | `string` | No | Additional CSS classes |
33
35
 
34
36
  ### trend
35
37
 
@@ -59,6 +61,22 @@ import { DollarSign } from 'lucide-react';
59
61
  />;
60
62
  ```
61
63
 
64
+ ### Custom Icon Colors
65
+
66
+ ```tsx
67
+ import { StatsCard } from 'xertica-ui/ui';
68
+ import { Users } from 'lucide-react';
69
+
70
+ <StatsCard
71
+ title="Active Users"
72
+ value="2,350"
73
+ trend={{ value: 8.2, label: 'new this week' }}
74
+ icon={<Users className="size-5" />}
75
+ iconBg="bg-primary/10"
76
+ iconColor="text-primary"
77
+ />;
78
+ ```
79
+
62
80
  ### Negative Trend
63
81
 
64
82
  ```tsx
@@ -108,7 +126,7 @@ import { DollarSign } from 'lucide-react';
108
126
  - `trend.value` is a **numeric percentage** (e.g., `20.1` for +20.1%) — the component displays `Math.abs(value)%` automatically.
109
127
  - Do NOT pre-format the trend value as a string — pass the raw number.
110
128
  - `value` IS pre-formatted as a display string: `"$45,231"`, `"3.6%"`, `"2,350"`.
111
- - Icons render inside a muted rounded background use `size-5` for the icon.
129
+ - Icons render inside a rounded background. By default, background is `bg-muted` and color is `text-muted-foreground`, but they can be customized using `iconBg` and `iconColor` props. Use `size-5` for the icon.
112
130
  - Always use in a 4-column responsive grid for dashboard overviews.
113
131
  - `trend.label` takes precedence over `description` for below-value text.
114
132
 
package/docs/doc-audit.md CHANGED
@@ -4,19 +4,20 @@
4
4
  >
5
5
  > Antes de seguir qualquer recomendação aqui, verifique se a documentação correspondente já existe em `docs/components/`. A inventário real abaixo foi atualizado para refletir o estado em 2026-05-21:
6
6
  >
7
- > | Categoria | Total | Com Doc | Sem Doc |
8
- > |---|---|---|---|
9
- > | Componentes UI (`components/ui/`) | 65 | 65 | 0 |
10
- > | Componentes Assistant (`components/assistant/`) | 5 | 5 | 0 |
11
- > | Componentes Brand (`components/brand/`) | 7 | 7 | 0 |
12
- > | Componentes Layout (`components/layout/`) | 2 | 2 | 0 |
13
- > | Componentes Media (`components/media/`) | 3 | 3 | 0 |
14
- > | Componentes Blocks (`components/blocks/`) | 6 cards + 6 skeletons | card-patterns.md (cobre cards + skeletons) | 0 |
15
- > | Páginas (`components/pages/`) | 8 | pages.md (cobertura agregada) | 0 |
16
- > | Hooks/Contextos | 9 hooks | hooks.md (cobertura agregada) | 0 |
17
- > | Utilitários (`components/figma/`) | 1 | image-with-fallback.md | 0 |
7
+ > | Categoria | Total | Com Doc | Sem Doc |
8
+ > | ----------------------------------------------- | --------------------- | ------------------------------------------ | ------- |
9
+ > | Componentes UI (`components/ui/`) | 65 | 65 | 0 |
10
+ > | Componentes Assistant (`components/assistant/`) | 5 | 5 | 0 |
11
+ > | Componentes Brand (`components/brand/`) | 7 | 7 | 0 |
12
+ > | Componentes Layout (`components/layout/`) | 2 | 2 | 0 |
13
+ > | Componentes Media (`components/media/`) | 3 | 3 | 0 |
14
+ > | Componentes Blocks (`components/blocks/`) | 6 cards + 6 skeletons | card-patterns.md (cobre cards + skeletons) | 0 |
15
+ > | Páginas (`components/pages/`) | 8 | pages.md (cobertura agregada) | 0 |
16
+ > | Hooks/Contextos | 9 hooks | hooks.md (cobertura agregada) | 0 |
17
+ > | Utilitários (`components/figma/`) | 1 | image-with-fallback.md | 0 |
18
18
  >
19
19
  > **Lacunas ainda em aberto (verificar antes de fechar):**
20
+ >
20
21
  > - Storybook `.mdx` para os 6 card skeletons (existe `.md`, mas não `.mdx`)
21
22
  > - `lib/query-client.ts` — singleton não documentado externamente (intencionalmente interno)
22
23
  >
@@ -34,13 +34,14 @@ npx xertica-ui@latest init
34
34
 
35
35
  The CLI walks you through an interactive setup:
36
36
 
37
- | Prompt | Choices |
38
- |---|---|
39
- | Pages to include | Login, Home, Template (multi-select) |
40
- | Languages to support | pt-BR, English, Español (multi-select, min 1) |
41
- | Default color theme | Xertica, Slate, Blue, Violet, Rose, Emerald, … |
42
- | **Include AI Assistant** | yes (default) / no |
43
- | Install dependencies | yes / no |
37
+ | Prompt | Choices |
38
+ | ----------------------------- | ---------------------------------------------- |
39
+ | Pages to include | Login, Home, Template (multi-select) |
40
+ | Languages to support | pt-BR, English, Español (multi-select, min 1) |
41
+ | Default color theme | Xertica, Slate, Blue, Violet, Rose, Emerald, … |
42
+ | **Enable dark mode support?** | yes (default) / no |
43
+ | **Include AI Assistant** | yes (default) / no |
44
+ | Install dependencies | yes / no |
44
45
 
45
46
  This command:
46
47
 
@@ -50,7 +51,7 @@ This command:
50
51
  - Injects the theme token system (`tokens.css`)
51
52
  - Copies only the locale files for the selected languages
52
53
  - Conditionally scaffolds the AI Assistant (`AssistantPage`, `features/assistant/`) based on your answer
53
- - Persists your choices in `src/locales/.languages.json` and `.xertica.json`
54
+ - Persists your choices in `src/locales/.languages.json` and `.xertica.json` (e.g. `hasAssistant`, `disableDarkMode`)
54
55
 
55
56
  ### Step 1b — Update an Existing Project (CLI)
56
57
 
@@ -62,12 +63,13 @@ npx xertica-ui@latest update
62
63
 
63
64
  The menu offers:
64
65
 
65
- | Option | What it does |
66
- |---|---|
67
- | **Theme only** | Re-prompts for color theme, overwrites `tokens.css` |
68
- | **Languages** | Add/remove language support, regenerates `i18n.ts` and `App.tsx` |
69
- | **Assistant** | Add or remove the AI Assistant (copies/deletes files, regenerates `AuthGuard.tsx`, `HomePage.tsx`, `TemplatePage.tsx`) |
70
- | **Project files** | Updates `app/`, `shared/`, `features/`, `pages/` to a newer version |
66
+ | Option | What it does |
67
+ | ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
68
+ | **Theme only** | Re-prompts for color theme, overwrites `tokens.css` |
69
+ | **Languages** | Add/remove language support, regenerates `i18n.ts` and `App.tsx` |
70
+ | **Dark Mode** | Enable or disable dark mode support (updates `.xertica.json` and regenerates `App.tsx` to set `disableDarkMode` flag) |
71
+ | **Assistant** | Add or remove the AI Assistant (copies/deletes files, regenerates `AuthGuard.tsx`, `HomePage.tsx`, `TemplatePage.tsx`) |
72
+ | **Project files** | Updates `app/`, `shared/`, `features/`, `pages/` to a newer version |
71
73
 
72
74
  > **Tip:** You can also manually edit `src/styles/xertica/tokens.css` to tweak individual color values — the file is fully commented and designed to be human-readable.
73
75
 
@@ -100,12 +102,18 @@ const queryClient = new QueryClient({
100
102
 
101
103
  function App() {
102
104
  return (
103
- <AppErrorBoundary> {/* catches provider/context crashes */}
105
+ <AppErrorBoundary>
106
+ {' '}
107
+ {/* catches provider/context crashes */}
104
108
  <QueryClientProvider client={queryClient}>
105
109
  <XerticaProvider>
106
110
  <Router>
107
- <AuthProvider> {/* must be inside Router */}
108
- <PageErrorBoundary> {/* catches lazy-chunk + page errors */}
111
+ <AuthProvider>
112
+ {' '}
113
+ {/* must be inside Router */}
114
+ <PageErrorBoundary>
115
+ {' '}
116
+ {/* catches lazy-chunk + page errors */}
109
117
  <YourRoutes />
110
118
  </PageErrorBoundary>
111
119
  </AuthProvider>
@@ -183,12 +191,12 @@ function MyPage() {
183
191
 
184
192
  `useAuth()` returns:
185
193
 
186
- | Property | Type | Description |
187
- | ----------- | ------------------------------------------ | -------------------------------------------- |
188
- | `user` | `AuthUser \| null` | Current user (`email`, `name?`, `avatar?`) |
189
- | `isLoading` | `boolean` | `true` while the session is being hydrated |
190
- | `login` | `(email, password) => boolean` | Attempt login; returns `true` on success |
191
- | `logout` | `() => void` | Clear session and redirect to `/login` |
194
+ | Property | Type | Description |
195
+ | ----------- | ------------------------------ | ------------------------------------------ |
196
+ | `user` | `AuthUser \| null` | Current user (`email`, `name?`, `avatar?`) |
197
+ | `isLoading` | `boolean` | `true` while the session is being hydrated |
198
+ | `login` | `(email, password) => boolean` | Attempt login; returns `true` on success |
199
+ | `logout` | `() => void` | Clear session and redirect to `/login` |
192
200
 
193
201
  ### Route Guards
194
202
 
@@ -198,7 +206,7 @@ Use `ProtectedRoute` and `GuestRoute` wrappers (provided in the CLI-generated `A
198
206
  // Redirect unauthenticated users to /login
199
207
  function ProtectedRoute({ children }) {
200
208
  const { user, isLoading } = useAuth();
201
- if (isLoading) return null; // wait for localStorage hydration
209
+ if (isLoading) return null; // wait for localStorage hydration
202
210
  if (!user) return <Navigate to="/login" replace />;
203
211
  return <>{children}</>;
204
212
  }
@@ -221,15 +229,20 @@ All routes should use `React.lazy()` + `<Suspense>` to split each page into a se
221
229
  ```tsx
222
230
  import React, { Suspense } from 'react';
223
231
 
224
- const HomePage = React.lazy(() =>
225
- import('./pages/HomePage').then(m => ({ default: m.HomePage }))
226
- );
232
+ const HomePage = React.lazy(() => import('./pages/HomePage').then(m => ({ default: m.HomePage })));
227
233
 
228
234
  function AppRoutes() {
229
235
  return (
230
236
  <Suspense fallback={null}>
231
237
  <Routes>
232
- <Route path="/home" element={<ProtectedRoute><HomePage /></ProtectedRoute>} />
238
+ <Route
239
+ path="/home"
240
+ element={
241
+ <ProtectedRoute>
242
+ <HomePage />
243
+ </ProtectedRoute>
244
+ }
245
+ />
233
246
  </Routes>
234
247
  </Suspense>
235
248
  );
@@ -201,9 +201,9 @@ Always render a skeleton placeholder — never a spinner — for data-bearing su
201
201
  ```tsx
202
202
  import { ActivityCard, ActivityCardSkeleton } from 'xertica-ui';
203
203
 
204
- {isLoading
205
- ? <ActivityCardSkeleton rows={5} />
206
- : <ActivityCard items={items} />}
204
+ {
205
+ isLoading ? <ActivityCardSkeleton rows={5} /> : <ActivityCard items={items} />;
206
+ }
207
207
  ```
208
208
 
209
209
  Available skeleton companions: `ActivityCardSkeleton`, `ProfileCardSkeleton`, `ProjectCardSkeleton`, `NotificationCardSkeleton`, `QuickActionCardSkeleton`, `FeatureCardSkeleton`, `StatsCardSkeleton`.
@@ -211,15 +211,17 @@ Available skeleton companions: `ActivityCardSkeleton`, `ProfileCardSkeleton`, `P
211
211
  **For grids**, render one skeleton per expected card so layout shift is minimized when data arrives:
212
212
 
213
213
  ```tsx
214
- {isLoading ? (
215
- <>
216
- <FeatureCardSkeleton showAction />
217
- <FeatureCardSkeleton showAction />
218
- <FeatureCardSkeleton showAction />
219
- </>
220
- ) : (
221
- data.map(item => <FeatureCard key={item.id} {...item} />)
222
- )}
214
+ {
215
+ isLoading ? (
216
+ <>
217
+ <FeatureCardSkeleton showAction />
218
+ <FeatureCardSkeleton showAction />
219
+ <FeatureCardSkeleton showAction />
220
+ </>
221
+ ) : (
222
+ data.map(item => <FeatureCard key={item.id} {...item} />)
223
+ );
224
+ }
223
225
  ```
224
226
 
225
227
  **For tables**, render skeleton `<TableRow>`s using the `<Skeleton>` primitive (`xertica-ui/ui`) sized to match each cell:
package/docs/i18n.md CHANGED
@@ -97,15 +97,13 @@ const es = bundleLang(
97
97
  );
98
98
 
99
99
  const savedLanguage =
100
- typeof window !== 'undefined'
101
- ? (localStorage.getItem('xertica_language') ?? 'pt-BR')
102
- : 'pt-BR';
100
+ typeof window !== 'undefined' ? (localStorage.getItem('xertica_language') ?? 'pt-BR') : 'pt-BR';
103
101
 
104
102
  i18n.use(initReactI18next).init({
105
103
  resources: {
106
104
  'pt-BR': { translation: ptBR },
107
- en: { translation: en },
108
- es: { translation: es },
105
+ en: { translation: en },
106
+ es: { translation: es },
109
107
  },
110
108
  lng: savedLanguage,
111
109
  fallbackLng: 'pt-BR',
@@ -150,7 +148,7 @@ function HomeContent() {
150
148
  ```
151
149
 
152
150
  ```tsx
153
- t('team.showing', { count: 5, total: 127 })
151
+ t('team.showing', { count: 5, total: 127 });
154
152
  // → "Exibindo 5 de 127 usuários"
155
153
  ```
156
154
 
@@ -176,11 +174,11 @@ setLanguage('en'); // persists + calls i18n.changeLanguage('en') + invalidates R
176
174
 
177
175
  ### Language codes
178
176
 
179
- | Code | Display | Stored as |
180
- |---|---|---|
177
+ | Code | Display | Stored as |
178
+ | --------- | -------------- | --------------------------- |
181
179
  | `'pt-BR'` | Português (BR) | `'pt-BR'` in `localStorage` |
182
- | `'en'` | English | `'en'` |
183
- | `'es'` | Español | `'es'` |
180
+ | `'en'` | English | `'en'` |
181
+ | `'es'` | Español | `'es'` |
184
182
 
185
183
  ---
186
184
 
@@ -190,41 +188,41 @@ The project uses a single `translation` namespace. Each top-level key maps to a
190
188
 
191
189
  **Root files** (`locales/<lang>/<key>.json`):
192
190
 
193
- | Namespace | File | Example keys |
194
- |---|---|---|
195
- | `common` | `common.json` | `common.view`, `common.edit`, `common.loading`, `common.close`, `common.copied` |
196
- | `nav` | `nav.json` | `nav.home`, `nav.designSystem`, `nav.settings` |
197
- | `errors` | `errors.json` | `errors.somethingWentWrong`, `errors.tryAgain`, `errors.pageLoadError` |
198
- | `languageSelector` | `languageSelector.json` | `languageSelector.label`, `languageSelector.ptBR` |
199
- | `themeToggle` | `themeToggle.json` | `themeToggle.switchToLight`, `themeToggle.darkMode` |
191
+ | Namespace | File | Example keys |
192
+ | ------------------ | ----------------------- | ------------------------------------------------------------------------------- |
193
+ | `common` | `common.json` | `common.view`, `common.edit`, `common.loading`, `common.close`, `common.copied` |
194
+ | `nav` | `nav.json` | `nav.home`, `nav.designSystem`, `nav.settings` |
195
+ | `errors` | `errors.json` | `errors.somethingWentWrong`, `errors.tryAgain`, `errors.pageLoadError` |
196
+ | `languageSelector` | `languageSelector.json` | `languageSelector.label`, `languageSelector.ptBR` |
197
+ | `themeToggle` | `themeToggle.json` | `themeToggle.switchToLight`, `themeToggle.darkMode` |
200
198
 
201
199
  **Page files** (`locales/<lang>/pages/<key>.json`):
202
200
 
203
- | Namespace | File | Example keys |
204
- |---|---|---|
205
- | `home` | `pages/home.json` | `home.welcome`, `home.subtitle`, `home.templateCliTitle` |
206
- | `templates` | `pages/templates.json` | `templates.title`, `templates.alerts.infoTitle`, `templates.forms.firstName` |
207
- | `login` | `pages/login.json` | `login.heading`, `login.submit`, `login.forgotPassword` |
208
- | `resetPassword` | `pages/resetPassword.json` | `resetPassword.heading`, `resetPassword.errorMismatch` |
209
- | `verifyEmail` | `pages/verifyEmail.json` | `verifyEmail.heading`, `verifyEmail.resend` |
210
- | `loginTemplate` | `pages/loginTemplate.json` | `loginTemplate.title`, `loginTemplate.submit` |
211
- | `formTemplate` | `pages/formTemplate.json` | `formTemplate.title`, `formTemplate.save`, `formTemplate.errors.fullNameRequired` |
212
- | `dashboardTemplate` | `pages/dashboardTemplate.json` | `dashboardTemplate.title`, `dashboardTemplate.stats.totalRevenue` |
213
- | `crudTemplate` | `pages/crudTemplate.json` | `crudTemplate.title`, `crudTemplate.actions.editProfile` |
201
+ | Namespace | File | Example keys |
202
+ | ------------------- | ------------------------------ | --------------------------------------------------------------------------------- |
203
+ | `home` | `pages/home.json` | `home.welcome`, `home.subtitle`, `home.templateCliTitle` |
204
+ | `templates` | `pages/templates.json` | `templates.title`, `templates.alerts.infoTitle`, `templates.forms.firstName` |
205
+ | `login` | `pages/login.json` | `login.heading`, `login.submit`, `login.forgotPassword` |
206
+ | `resetPassword` | `pages/resetPassword.json` | `resetPassword.heading`, `resetPassword.errorMismatch` |
207
+ | `verifyEmail` | `pages/verifyEmail.json` | `verifyEmail.heading`, `verifyEmail.resend` |
208
+ | `loginTemplate` | `pages/loginTemplate.json` | `loginTemplate.title`, `loginTemplate.submit` |
209
+ | `formTemplate` | `pages/formTemplate.json` | `formTemplate.title`, `formTemplate.save`, `formTemplate.errors.fullNameRequired` |
210
+ | `dashboardTemplate` | `pages/dashboardTemplate.json` | `dashboardTemplate.title`, `dashboardTemplate.stats.totalRevenue` |
211
+ | `crudTemplate` | `pages/crudTemplate.json` | `crudTemplate.title`, `crudTemplate.actions.editProfile` |
214
212
 
215
213
  **Component files** (`locales/<lang>/components/<key>.json`):
216
214
 
217
- | Namespace | File | Example keys |
218
- |---|---|---|
219
- | `assistant` | `components/assistant.json` | `assistant.title`, `assistant.inputPlaceholder`, `assistant.tabs.chat`, `assistant.feedbackDialog.title` |
220
- | `sidebar` | `components/sidebar.json` | `sidebar.collapse`, `sidebar.logout`, `sidebar.moreOptions` |
221
- | `media` | `components/media.json` | `media.play`, `media.pause`, `media.downloadAudio`, `media.floatingMode` |
222
- | `projectCard` | `components/projectCard.json` | `projectCard.progress`, `projectCard.status.active` |
223
- | `profileCard` | `components/profileCard.json` | `profileCard.status.online`, `profileCard.status.busy` |
224
- | `notificationCard` | `components/notificationCard.json` | `notificationCard.title`, `notificationCard.markAllRead` |
225
- | `activityCard` | `components/activityCard.json` | `activityCard.title`, `activityCard.type.create` |
226
- | `stats` | `components/stats.json` | `stats.totalUsers`, `stats.last30Days` |
227
- | `team` | `components/team.json` | `team.name`, `team.roles.Developer`, `team.showing` |
215
+ | Namespace | File | Example keys |
216
+ | ------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------- |
217
+ | `assistant` | `components/assistant.json` | `assistant.title`, `assistant.inputPlaceholder`, `assistant.tabs.chat`, `assistant.feedbackDialog.title` |
218
+ | `sidebar` | `components/sidebar.json` | `sidebar.collapse`, `sidebar.logout`, `sidebar.moreOptions` |
219
+ | `media` | `components/media.json` | `media.play`, `media.pause`, `media.downloadAudio`, `media.floatingMode` |
220
+ | `projectCard` | `components/projectCard.json` | `projectCard.progress`, `projectCard.status.active` |
221
+ | `profileCard` | `components/profileCard.json` | `profileCard.status.online`, `profileCard.status.busy` |
222
+ | `notificationCard` | `components/notificationCard.json` | `notificationCard.title`, `notificationCard.markAllRead` |
223
+ | `activityCard` | `components/activityCard.json` | `activityCard.title`, `activityCard.type.create` |
224
+ | `stats` | `components/stats.json` | `stats.totalUsers`, `stats.last30Days` |
225
+ | `team` | `components/team.json` | `team.name`, `team.roles.Developer`, `team.showing` |
228
226
 
229
227
  ---
230
228
 
@@ -237,11 +235,13 @@ Mock data fetch functions use `i18n.t()` (the instance, not the hook) so they re
237
235
  import i18n from '../../../i18n';
238
236
 
239
237
  export async function fetchFeatureCards(): Promise<FeatureCard[]> {
240
- return [{
241
- id: 'template-cli',
242
- title: i18n.t('home.templateCliTitle'), // ← translated at query time
243
- description: i18n.t('home.templateCliDescription'),
244
- }];
238
+ return [
239
+ {
240
+ id: 'template-cli',
241
+ title: i18n.t('home.templateCliTitle'), // ← translated at query time
242
+ description: i18n.t('home.templateCliDescription'),
243
+ },
244
+ ];
245
245
  }
246
246
  ```
247
247
 
@@ -264,6 +264,7 @@ export function useFeatureCards() {
264
264
  ```
265
265
 
266
266
  **Why this works:**
267
+
267
268
  - Switching from `pt-BR` → `en` changes the queryKey to `['home', 'feature-cards', 'en']`
268
269
  - React Query finds no cache entry for this key → triggers an immediate refetch
269
270
  - `fetchFeatureCards()` runs again → `i18n.t()` now returns English strings
@@ -278,9 +279,7 @@ When you need static fallback data while a query loads, use **factory functions*
278
279
  ```ts
279
280
  // ✅ Correct — evaluated at call time, always returns current language
280
281
  export function getMockRichSuggestions(): Suggestion[] {
281
- return [
282
- { id: 'rich-1', text: i18n.t('assistant.richSuggestions.viewPerformance') },
283
- ];
282
+ return [{ id: 'rich-1', text: i18n.t('assistant.richSuggestions.viewPerformance') }];
284
283
  }
285
284
 
286
285
  // ❌ Wrong — i18n.t() runs once at module load, frozen in initial language
@@ -312,11 +311,13 @@ $ npx xertica-ui init my-app
312
311
  ```
313
312
 
314
313
  Pick **all three** (default), **two**, or **just one**:
314
+
315
315
  - **All three** — the CLI omits the `availableLanguages` prop entirely (the library default already matches).
316
316
  - **Two or one** — the CLI injects the explicit `availableLanguages` array into `src/app/App.tsx`.
317
317
  - **Just one (monolingual)** — additionally, the `LanguageSelector` auto-hides because there is nothing to switch between. A header banner comment in `App.tsx` documents this.
318
318
 
319
319
  The CLI also:
320
+
320
321
  - Copies **only** the locale **folders** for the selected languages into `src/locales/` (no orphan files). Each language is a directory tree with split JSON files.
321
322
  - Generates `src/i18n.ts` with `import.meta.glob` calls for exactly those languages — Vite auto-discovers all JSON files in the folder at build time.
322
323
  - Persists the selection in `src/locales/.languages.json` so the `update` command can preserve it.
@@ -341,6 +342,7 @@ Current languages: Português (BR), English
341
342
  ```
342
343
 
343
344
  The command:
345
+
344
346
  1. Computes the add/remove diff and shows it to you.
345
347
  2. Copies the newly-added locale **folders** from `node_modules/xertica-ui/templates/src/locales/<lang>/`.
346
348
  3. Removes the **folders** of unselected languages from your project (also removes any legacy flat `<lang>.json` files if upgrading from pre-2.2.0).
@@ -358,7 +360,7 @@ import { XerticaProvider } from 'xertica-ui';
358
360
 
359
361
  <XerticaProvider>
360
362
  {/* pt-BR, en, es are all available — selector shows all three */}
361
- </XerticaProvider>
363
+ </XerticaProvider>;
362
364
  ```
363
365
 
364
366
  ### Monolingual — single language, no selector
@@ -390,7 +392,7 @@ import fr from './locales/fr.json';
390
392
  ]}
391
393
  >
392
394
  {/* French now appears in the selector; its strings are registered automatically */}
393
- </XerticaProvider>
395
+ </XerticaProvider>;
394
396
  ```
395
397
 
396
398
  The provider calls `i18n.addResourceBundle()` on mount for any entry that carries `resources`.
@@ -414,10 +416,12 @@ registerLanguageResource('fr', fr);
414
416
  Just omit them from `availableLanguages`. The translation bundles registered in `src/i18n.ts` remain loaded (they're cheap) but they're invisible to the UI:
415
417
 
416
418
  ```tsx
417
- <XerticaProvider availableLanguages={[
418
- { code: 'pt-BR', label: 'Português' },
419
- { code: 'en', label: 'English' },
420
- ]}>
419
+ <XerticaProvider
420
+ availableLanguages={[
421
+ { code: 'pt-BR', label: 'Português' },
422
+ { code: 'en', label: 'English' },
423
+ ]}
424
+ >
421
425
  {/* Only Portuguese and English appear — Spanish is hidden */}
422
426
  </XerticaProvider>
423
427
  ```
@@ -451,10 +455,10 @@ interface LanguageDefinition {
451
455
 
452
456
  ```ts
453
457
  const {
454
- language, // current locale code
455
- setLanguage, // change locale + persist + invalidate React Query
456
- availableLanguages, // LanguageDefinition[] currently configured
457
- isMonolingual, // true when availableLanguages.length === 1
458
+ language, // current locale code
459
+ setLanguage, // change locale + persist + invalidate React Query
460
+ availableLanguages, // LanguageDefinition[] currently configured
461
+ isMonolingual, // true when availableLanguages.length === 1
458
462
  } = useLanguage();
459
463
  ```
460
464