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
@@ -1,32 +1,32 @@
1
- import React from 'react';
2
- import { ImageWithFallback } from 'xertica-ui/ui';
3
- import { LanguageSelector } from 'xertica-ui/brand';
4
-
5
- interface AuthPageShellProps {
6
- imageSrc: string;
7
- imageAlt: string;
8
- children: React.ReactNode;
9
- }
10
-
11
- export function AuthPageShell({ imageSrc, imageAlt, children }: AuthPageShellProps) {
12
- return (
13
- <div className="min-h-screen flex">
14
- <div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
15
- <ImageWithFallback
16
- src={imageSrc}
17
- alt={imageAlt}
18
- className="absolute inset-0 w-full h-full object-cover"
19
- />
20
- <div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
21
- </div>
22
-
23
- <div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative bg-muted">
24
- <div className="absolute top-4 right-4 z-20">
25
- <LanguageSelector variant="minimal" showIcon={false} />
26
- </div>
27
- <div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
28
- <div className="w-full max-w-sm space-y-6 relative z-10">{children}</div>
29
- </div>
30
- </div>
31
- );
32
- }
1
+ import React from 'react';
2
+ import { ImageWithFallback } from 'xertica-ui/ui';
3
+ import { LanguageSelector } from 'xertica-ui/brand';
4
+
5
+ interface AuthPageShellProps {
6
+ imageSrc: string;
7
+ imageAlt: string;
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ export function AuthPageShell({ imageSrc, imageAlt, children }: AuthPageShellProps) {
12
+ return (
13
+ <div className="h-screen w-full flex overflow-y-auto">
14
+ <div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
15
+ <ImageWithFallback
16
+ src={imageSrc}
17
+ alt={imageAlt}
18
+ className="absolute inset-0 w-full h-full object-cover"
19
+ />
20
+ <div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
21
+ </div>
22
+
23
+ <div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative bg-muted">
24
+ <div className="absolute top-4 right-4 z-20">
25
+ <LanguageSelector variant="minimal" showIcon={false} />
26
+ </div>
27
+ <div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
28
+ <div className="w-full max-w-sm space-y-6 relative z-10">{children}</div>
29
+ </div>
30
+ </div>
31
+ );
32
+ }
@@ -31,9 +31,7 @@ export function ForgotPasswordContent() {
31
31
  <XerticaLogo className="h-12 w-auto text-primary dark:text-foreground" variant="theme" />
32
32
  </div>
33
33
  <h2 className="text-sm text-muted-foreground">{t('forgotPassword.heading')}</h2>
34
- <p className="mt-2 text-muted-foreground">
35
- {t('forgotPassword.subheading')}
36
- </p>
34
+ <p className="mt-2 text-muted-foreground">{t('forgotPassword.subheading')}</p>
37
35
  </div>
38
36
 
39
37
  <form className="space-y-6" onSubmit={handleSubmit}>
@@ -152,13 +152,17 @@ export function ResetPasswordContent() {
152
152
  <CheckCircle2
153
153
  className={`w-4 h-4 ${password.length >= 6 ? 'text-[var(--chart-2)]' : 'text-muted-foreground'}`}
154
154
  />
155
- <span className="text-sm text-muted-foreground">{t('resetPassword.requirementMinChars')}</span>
155
+ <span className="text-sm text-muted-foreground">
156
+ {t('resetPassword.requirementMinChars')}
157
+ </span>
156
158
  </div>
157
159
  <div className="flex items-center gap-2">
158
160
  <CheckCircle2
159
161
  className={`w-4 h-4 ${password === confirmPassword && password.length > 0 ? 'text-[var(--chart-2)]' : 'text-muted-foreground'}`}
160
162
  />
161
- <span className="text-sm text-muted-foreground">{t('resetPassword.requirementMatch')}</span>
163
+ <span className="text-sm text-muted-foreground">
164
+ {t('resetPassword.requirementMatch')}
165
+ </span>
162
166
  </div>
163
167
  </div>
164
168
  </div>
@@ -39,16 +39,12 @@ export function VerifyEmailContent() {
39
39
  </div>
40
40
  </div>
41
41
  <h2 className="text-sm text-muted-foreground">{t('verifyEmail.heading')}</h2>
42
- <p className="mt-2 text-muted-foreground">
43
- {t('verifyEmail.instructionsSent')}
44
- </p>
42
+ <p className="mt-2 text-muted-foreground">{t('verifyEmail.instructionsSent')}</p>
45
43
  <p className="mt-1 text-primary">{email}</p>
46
44
  </div>
47
45
 
48
46
  <div className="bg-accent rounded-[var(--radius)] p-4 space-y-3">
49
- <p className="text-muted-foreground">
50
- {t('verifyEmail.instructions')}
51
- </p>
47
+ <p className="text-muted-foreground">{t('verifyEmail.instructions')}</p>
52
48
  <div className="flex items-start gap-2 text-muted-foreground">
53
49
  <CheckCircle2 className="w-4 h-4 mt-0.5 flex-shrink-0 text-[var(--chart-2)]" />
54
50
  <span className="text-sm">{t('verifyEmail.checkSpam')}</span>
@@ -1,35 +1,41 @@
1
- // ─────────────────────────────────────────────────────────────────────────────
2
- // features/home/data/mock.ts
3
- //
4
- // All mock data for the Home feature lives here.
5
- // To connect to a real API, replace the fetch functions below with actual
6
- // HTTP calls — the hooks and components stay unchanged.
7
- // ─────────────────────────────────────────────────────────────────────────────
8
-
9
- import i18n from '../../../i18n';
10
-
11
- // ── Types ─────────────────────────────────────────────────────────────────────
12
-
13
- export interface FeatureCard {
14
- id: string;
15
- title: string;
16
- description: string;
17
- badge?: string;
18
- href: string;
19
- }
20
-
21
- // ── Mock API function ─────────────────────────────────────────────────────────
22
-
23
- export async function fetchFeatureCards(): Promise<FeatureCard[]> {
24
- // Replace with: return fetch('/api/home/features').then(r => r.json());
25
- await new Promise(resolve => setTimeout(resolve, 100));
26
- return [
27
- {
28
- id: 'template-cli',
29
- title: i18n.t('home.templateCliTitle'),
30
- description: i18n.t('home.templateCliDescription'),
31
- badge: i18n.t('home.templateClibadge'),
32
- href: '/template',
33
- },
34
- ];
35
- }
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // features/home/data/mock.ts
3
+ //
4
+ // All mock data for the Home feature lives here.
5
+ // To connect to a real API, replace the fetch functions below with actual
6
+ // HTTP calls — the hooks and components stay unchanged.
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+
9
+ import i18n from '../../../i18n';
10
+ import type { LucideIcon } from 'lucide-react';
11
+ import { FileText } from 'lucide-react';
12
+
13
+ // ── Types ─────────────────────────────────────────────────────────────────────
14
+
15
+ export interface FeatureCard {
16
+ id: string;
17
+ title: string;
18
+ description: string;
19
+ badge?: string;
20
+ href: string;
21
+ Icon: LucideIcon;
22
+ chartColor: string; // CSS variable: '--chart-1' | '--chart-2' | '--chart-3' | '--chart-4' | '--chart-5'
23
+ }
24
+
25
+ // ── Mock API function ─────────────────────────────────────────────────────────
26
+
27
+ export async function fetchFeatureCards(): Promise<FeatureCard[]> {
28
+ // Replace with: return fetch('/api/home/features').then(r => r.json());
29
+ await new Promise(resolve => setTimeout(resolve, 100));
30
+ return [
31
+ {
32
+ id: 'template-cli',
33
+ title: i18n.t('home.templateCliTitle'),
34
+ description: i18n.t('home.templateCliDescription'),
35
+ badge: i18n.t('home.templateClibadge'),
36
+ href: '/template',
37
+ Icon: FileText,
38
+ chartColor: '--chart-1',
39
+ },
40
+ ];
41
+ }
@@ -11,20 +11,11 @@ import {
11
11
  } from 'xertica-ui/ui';
12
12
  import { Header } from 'xertica-ui/layout';
13
13
  import { useLayout } from 'xertica-ui/hooks';
14
- import { FileText } from 'lucide-react';
15
14
  import { useNavigate, Link } from 'react-router-dom';
16
15
  import { useTranslation } from 'react-i18next';
17
16
  import { useFeatureCards } from '../hooks/useFeatureCards';
18
17
  import { SectionErrorBoundary } from '../../../shared/error-boundary';
19
18
 
20
- /**
21
- * Home dashboard content area.
22
- *
23
- * All UI strings are translated via `useTranslation()`.
24
- * Feature cards are fetched via `useFeatureCards` (React Query).
25
- * To connect to a real API, replace `fetchFeatureCards` in
26
- * `features/home/data/mock.ts`.
27
- */
28
19
  export function HomeContent() {
29
20
  const { t } = useTranslation();
30
21
  const { sidebarExpanded, sidebarWidth } = useLayout();
@@ -42,11 +33,10 @@ export function HomeContent() {
42
33
  <Header
43
34
  showThemeToggle={true}
44
35
  showLanguageSelector={true}
45
- breadcrumbs={[
46
- { label: t('nav.designSystem'), href: '/home' },
47
- { label: t('nav.home') },
48
- ]}
49
- renderLink={(href: string, props: any) => <Link to={href} {...props} />}
36
+ breadcrumbs={[{ label: t('nav.designSystem'), href: '/home' }, { label: t('nav.home') }]}
37
+ renderLink={(href: string, props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
38
+ <Link to={href} {...props} />
39
+ )}
50
40
  />
51
41
 
52
42
  <main className="flex-1 overflow-hidden bg-muted">
@@ -54,61 +44,69 @@ export function HomeContent() {
54
44
  <div className="p-5 sm:p-4 md:p-6">
55
45
  <div className="max-w-6xl mx-auto space-y-8">
56
46
  <div className="space-y-2">
57
- <h2>{t('home.welcome')}</h2>
47
+ <h2 className="text-3xl font-bold tracking-tight">{t('home.welcome')}</h2>
58
48
  <p className="text-muted-foreground">{t('home.subtitle')}</p>
59
49
  </div>
60
50
 
61
51
  <SectionErrorBoundary>
62
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
63
- {isLoading ? (
64
- <Card className="flex flex-col h-full animate-pulse">
65
- <CardHeader>
66
- <div className="h-4 bg-muted-foreground/20 rounded w-2/3" />
67
- </CardHeader>
68
- <CardContent className="flex-1">
69
- <div className="h-3 bg-muted-foreground/10 rounded w-full" />
70
- </CardContent>
71
- <CardFooter>
72
- <div className="h-9 bg-muted-foreground/10 rounded w-full" />
73
- </CardFooter>
74
- </Card>
75
- ) : (
76
- featureCards.map(card => (
77
- <Card
78
- key={card.id}
79
- className="hover:shadow-xl transition-shadow duration-200 flex flex-col h-full"
80
- >
81
- <CardHeader>
82
- <div className="flex items-center gap-3">
83
- <div className="p-2 bg-[var(--chart-2)]/20 rounded-[var(--radius)]">
84
- <FileText className="w-6 h-6 text-[var(--chart-2)]" />
85
- </div>
86
- <div className="flex items-center gap-2">
87
- <CardTitle className="text-sm">{card.title}</CardTitle>
88
- {card.badge && (
89
- <Badge variant="default" className="text-xs">
90
- {card.badge}
91
- </Badge>
92
- )}
93
- </div>
94
- </div>
95
- </CardHeader>
96
- <CardContent className="flex-1">
97
- <p className="text-muted-foreground">{card.description}</p>
98
- </CardContent>
99
- <CardFooter>
100
- <Button
101
- variant="outline"
102
- className="w-full"
103
- onClick={() => navigate(card.href)}
52
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
53
+ {isLoading
54
+ ? Array.from({ length: 3 }).map((_, i) => (
55
+ <Card key={i} className="flex flex-col h-full animate-pulse">
56
+ <CardHeader>
57
+ <div className="h-4 bg-muted-foreground/20 rounded w-2/3" />
58
+ </CardHeader>
59
+ <CardContent className="flex-1">
60
+ <div className="h-3 bg-muted-foreground/10 rounded w-full" />
61
+ </CardContent>
62
+ <CardFooter>
63
+ <div className="h-9 bg-muted-foreground/10 rounded w-full" />
64
+ </CardFooter>
65
+ </Card>
66
+ ))
67
+ : featureCards.map(card => (
68
+ <Card
69
+ key={card.id}
70
+ className="hover:shadow-xl transition-shadow duration-200 flex flex-col h-full"
104
71
  >
105
- {t('common.view')}
106
- </Button>
107
- </CardFooter>
108
- </Card>
109
- ))
110
- )}
111
- </div>
72
+ <CardHeader>
73
+ <div className="flex items-center gap-3">
74
+ <div
75
+ className="p-2 rounded-[var(--radius)]"
76
+ style={{
77
+ background: `color-mix(in srgb, var(${card.chartColor}) 20%, transparent)`,
78
+ }}
79
+ >
80
+ <card.Icon
81
+ className="w-6 h-6"
82
+ style={{ color: `var(${card.chartColor})` }}
83
+ />
84
+ </div>
85
+ <div className="flex items-center gap-2">
86
+ <CardTitle className="text-sm">{card.title}</CardTitle>
87
+ {card.badge && (
88
+ <Badge variant="default" className="text-xs">
89
+ {card.badge}
90
+ </Badge>
91
+ )}
92
+ </div>
93
+ </div>
94
+ </CardHeader>
95
+ <CardContent className="flex-1">
96
+ <p className="text-muted-foreground">{card.description}</p>
97
+ </CardContent>
98
+ <CardFooter>
99
+ <Button
100
+ variant="outline"
101
+ className="w-full"
102
+ onClick={() => navigate(card.href)}
103
+ >
104
+ {t('common.view')}
105
+ </Button>
106
+ </CardFooter>
107
+ </Card>
108
+ ))}
109
+ </div>
112
110
  </SectionErrorBoundary>
113
111
  </div>
114
112
  </div>
@@ -37,10 +37,7 @@ export function CrudTemplate() {
37
37
  <div className="flex p-4 border-b gap-2">
38
38
  <div className="relative w-full max-w-sm">
39
39
  <Search className="absolute left-2.5 top-2.5 size-4 text-muted-foreground" />
40
- <Input
41
- placeholder={t('crudTemplate.searchPlaceholder')}
42
- className="w-full h-9 pl-8"
43
- />
40
+ <Input placeholder={t('crudTemplate.searchPlaceholder')} className="w-full h-9 pl-8" />
44
41
  </div>
45
42
  </div>
46
43
 
@@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
16
16
  export function LoginTemplate() {
17
17
  const { t } = useTranslation();
18
18
  return (
19
- <div className="flex min-h-screen w-full items-center justify-center bg-muted/30 p-4">
19
+ <div className="flex h-full min-h-0 w-full items-center justify-center bg-muted/30 p-4 overflow-y-auto">
20
20
  <Card className="w-full max-w-sm">
21
21
  <CardHeader className="text-center">
22
22
  <div className="flex justify-center mb-4">
@@ -68,7 +68,7 @@ import {
68
68
  useStepper,
69
69
  } from 'xertica-ui/ui';
70
70
  import { Header, Sidebar } from 'xertica-ui/layout';
71
- import { useLayout } from 'xertica-ui/hooks';
71
+ import { useLayout, useTheme } from 'xertica-ui/hooks';
72
72
  import { toast } from 'sonner';
73
73
  import { Link } from 'react-router-dom';
74
74
  import { Trans, useTranslation } from 'react-i18next';
@@ -131,6 +131,7 @@ export function TemplateContent() {
131
131
  },
132
132
  ];
133
133
  const { sidebarExpanded, sidebarWidth } = useLayout();
134
+ const { disableDarkMode } = useTheme();
134
135
  const [progress, setProgress] = useState(45);
135
136
  const [sliderValue, setSliderValue] = useState([50]);
136
137
  const [switchEnabled, setSwitchEnabled] = useState(false);
@@ -611,17 +612,21 @@ export function TemplateContent() {
611
612
  <CardDescription>{t('templates.settings.description')}</CardDescription>
612
613
  </CardHeader>
613
614
  <CardContent className="space-y-6">
614
- <div className="flex items-center justify-between">
615
- <div className="space-y-1">
616
- <Label>{t('templates.settings.darkMode')}</Label>
617
- <p className="text-muted-foreground">
618
- {t('templates.settings.darkModeDescription')}
619
- </p>
620
- </div>
621
- <Switch checked={switchEnabled} onCheckedChange={setSwitchEnabled} />
622
- </div>
615
+ {!disableDarkMode && (
616
+ <>
617
+ <div className="flex items-center justify-between">
618
+ <div className="space-y-1">
619
+ <Label>{t('templates.settings.darkMode')}</Label>
620
+ <p className="text-muted-foreground">
621
+ {t('templates.settings.darkModeDescription')}
622
+ </p>
623
+ </div>
624
+ <Switch checked={switchEnabled} onCheckedChange={setSwitchEnabled} />
625
+ </div>
623
626
 
624
- <Separator />
627
+ <Separator />
628
+ </>
629
+ )}
625
630
 
626
631
  <div className="flex items-center justify-between">
627
632
  <div className="space-y-1">
@@ -883,9 +888,7 @@ export function TemplateContent() {
883
888
  </AlertDialogTrigger>
884
889
  <AlertDialogContent className="sm:max-w-[425px]">
885
890
  <AlertDialogHeader>
886
- <AlertDialogTitle>
887
- {t('templates.dialogs.areYouSure')}
888
- </AlertDialogTitle>
891
+ <AlertDialogTitle>{t('templates.dialogs.areYouSure')}</AlertDialogTitle>
889
892
  <AlertDialogDescription>
890
893
  {t('templates.dialogs.deleteWarning')}
891
894
  </AlertDialogDescription>
@@ -978,9 +981,7 @@ export function TemplateContent() {
978
981
  {
979
982
  label: t('templates.sidebar.actions.moveActive'),
980
983
  onClick: () =>
981
- toast(
982
- t('templates.sidebar.actions.moveActiveToast')
983
- ),
984
+ toast(t('templates.sidebar.actions.moveActiveToast')),
984
985
  },
985
986
  {
986
987
  label: t('templates.sidebar.actions.moveMonitoring'),
@@ -1084,7 +1085,11 @@ export function TemplateContent() {
1084
1085
  navigate={() => {}}
1085
1086
  variant="default"
1086
1087
  routes={[
1087
- { path: '/home', label: t('templates.sidebar.routes.home'), icon: Home },
1088
+ {
1089
+ path: '/home',
1090
+ label: t('templates.sidebar.routes.home'),
1091
+ icon: Home,
1092
+ },
1088
1093
  {
1089
1094
  path: '/dashboard',
1090
1095
  label: t('templates.sidebar.routes.dashboard'),
@@ -1150,7 +1155,7 @@ export function TemplateContent() {
1150
1155
  disabled={!canGoPrev}
1151
1156
  />
1152
1157
  </PaginationItem>
1153
- {items.map((item) =>
1158
+ {items.map(item =>
1154
1159
  item.type === 'ellipsis' ? (
1155
1160
  <PaginationItem key={item.key}>
1156
1161
  <PaginationEllipsis />
@@ -1249,7 +1254,10 @@ export function TemplateContent() {
1249
1254
  aria-label={t('templates.enhanced.treeView.ariaLabel')}
1250
1255
  defaultExpanded={['components', 'ui']}
1251
1256
  onNodeSelect={node =>
1252
- console.log(t('templates.enhanced.treeView.selectedConsoleLog'), node.label)
1257
+ console.log(
1258
+ t('templates.enhanced.treeView.selectedConsoleLog'),
1259
+ node.label
1260
+ )
1253
1261
  }
1254
1262
  className="max-w-xs border border-border rounded-[var(--radius-lg)] p-2"
1255
1263
  />
@@ -239,23 +239,23 @@
239
239
  "next": "Next",
240
240
  "finish": "Finish"
241
241
  },
242
- "treeView": {
243
- "title": "TreeView",
244
- "description": "Hierarchical navigation with <code>ariaLabel</code>, roving tabindex and WAI-ARIA keyboard (Space expands/collapses, Enter selects).",
245
- "ariaLabel": "Component structure",
246
- "selectedConsoleLog": "Selected:",
247
- "nodes": {
248
- "components": "Components",
249
- "ui": "UI",
250
- "button": "Button",
251
- "input": "Input",
252
- "pagination": "Pagination",
253
- "layout": "Layout",
254
- "sidebar": "Sidebar",
255
- "header": "Header",
256
- "hooks": "Hooks"
257
- }
258
- },
242
+ "treeView": {
243
+ "title": "TreeView",
244
+ "description": "Hierarchical navigation with <code>ariaLabel</code>, roving tabindex and WAI-ARIA keyboard (Space expands/collapses, Enter selects).",
245
+ "ariaLabel": "Component structure",
246
+ "selectedConsoleLog": "Selected:",
247
+ "nodes": {
248
+ "components": "Components",
249
+ "ui": "UI",
250
+ "button": "Button",
251
+ "input": "Input",
252
+ "pagination": "Pagination",
253
+ "layout": "Layout",
254
+ "sidebar": "Sidebar",
255
+ "header": "Header",
256
+ "hooks": "Hooks"
257
+ }
258
+ },
259
259
  "richText": {
260
260
  "title": "RichTextEditor",
261
261
  "description": "Rich text editor with <code>role=\"textbox\"</code>, <code>aria-multiline</code> and counters via <code>useState</code> (without IIFE).",
@@ -239,23 +239,23 @@
239
239
  "next": "Siguiente",
240
240
  "finish": "Finalizar"
241
241
  },
242
- "treeView": {
243
- "title": "TreeView",
244
- "description": "Navegación jerárquica con <code>ariaLabel</code>, roving tabindex y teclado WAI-ARIA (Espacio expande/colapsa, Enter selecciona).",
245
- "ariaLabel": "Estructura de componentes",
246
- "selectedConsoleLog": "Seleccionado:",
247
- "nodes": {
248
- "components": "Componentes",
249
- "ui": "UI",
250
- "button": "Botón",
251
- "input": "Input",
252
- "pagination": "Paginación",
253
- "layout": "Layout",
254
- "sidebar": "Sidebar",
255
- "header": "Header",
256
- "hooks": "Hooks"
257
- }
258
- },
242
+ "treeView": {
243
+ "title": "TreeView",
244
+ "description": "Navegación jerárquica con <code>ariaLabel</code>, roving tabindex y teclado WAI-ARIA (Espacio expande/colapsa, Enter selecciona).",
245
+ "ariaLabel": "Estructura de componentes",
246
+ "selectedConsoleLog": "Seleccionado:",
247
+ "nodes": {
248
+ "components": "Componentes",
249
+ "ui": "UI",
250
+ "button": "Botón",
251
+ "input": "Input",
252
+ "pagination": "Paginación",
253
+ "layout": "Layout",
254
+ "sidebar": "Sidebar",
255
+ "header": "Header",
256
+ "hooks": "Hooks"
257
+ }
258
+ },
259
259
  "richText": {
260
260
  "title": "RichTextEditor",
261
261
  "description": "Editor rich text con <code>role=\"textbox\"</code>, <code>aria-multiline</code> y contadores via <code>useState</code> (sin IIFE).",
@@ -239,23 +239,23 @@
239
239
  "next": "Próximo",
240
240
  "finish": "Concluir"
241
241
  },
242
- "treeView": {
243
- "title": "TreeView",
244
- "description": "Navegação hierárquica com <code>ariaLabel</code>, roving tabindex e teclado WAI-ARIA (Espaço expande/colapsa, Enter seleciona).",
245
- "ariaLabel": "Estrutura de componentes",
246
- "selectedConsoleLog": "Selecionado:",
247
- "nodes": {
248
- "components": "Componentes",
249
- "ui": "UI",
250
- "button": "Botão",
251
- "input": "Input",
252
- "pagination": "Paginação",
253
- "layout": "Layout",
254
- "sidebar": "Sidebar",
255
- "header": "Header",
256
- "hooks": "Hooks"
257
- }
258
- },
242
+ "treeView": {
243
+ "title": "TreeView",
244
+ "description": "Navegação hierárquica com <code>ariaLabel</code>, roving tabindex e teclado WAI-ARIA (Espaço expande/colapsa, Enter seleciona).",
245
+ "ariaLabel": "Estrutura de componentes",
246
+ "selectedConsoleLog": "Selecionado:",
247
+ "nodes": {
248
+ "components": "Componentes",
249
+ "ui": "UI",
250
+ "button": "Botão",
251
+ "input": "Input",
252
+ "pagination": "Paginação",
253
+ "layout": "Layout",
254
+ "sidebar": "Sidebar",
255
+ "header": "Header",
256
+ "hooks": "Hooks"
257
+ }
258
+ },
259
259
  "richText": {
260
260
  "title": "RichTextEditor",
261
261
  "description": "Editor rich text com <code>role=\"textbox\"</code>, <code>aria-multiline</code> e contadores via <code>useState</code> (sem IIFE).",