xertica-ui 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/bin/cli.ts +1 -1
  4. package/bin/generate-tokens.ts +13 -7
  5. package/components/assistant/xertica-assistant/index.ts +2 -0
  6. package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +97 -0
  7. package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -0
  8. package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -0
  9. package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +86 -0
  10. package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +77 -0
  11. package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +573 -0
  12. package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +65 -0
  13. package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -0
  14. package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +98 -0
  15. package/components/assistant/xertica-assistant/parts/index.ts +16 -0
  16. package/components/assistant/xertica-assistant/types.ts +139 -0
  17. package/components/assistant/xertica-assistant/use-assistant.ts +559 -0
  18. package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +200 -0
  19. package/components/assistant/xertica-assistant/xertica-assistant.tsx +198 -1460
  20. package/components/brand/theme-toggle/ThemeToggle.tsx +8 -27
  21. package/components/hooks/index.ts +3 -0
  22. package/components/hooks/use-layout-shortcuts.ts +46 -0
  23. package/components/layout/sidebar/index.ts +2 -0
  24. package/components/layout/sidebar/sidebar.stories.tsx +160 -8
  25. package/components/layout/sidebar/sidebar.tsx +606 -497
  26. package/components/layout/sidebar/use-sidebar.ts +104 -0
  27. package/components/media/audio-player/AudioPlayer.tsx +131 -206
  28. package/components/media/audio-player/use-audio-player.ts +298 -0
  29. package/components/pages/home-page/HomePage.tsx +1 -1
  30. package/components/pages/template-content/TemplateContent.tsx +5 -5
  31. package/components/pages/template-page/TemplatePage.tsx +5 -5
  32. package/components/shared/CustomTooltipContent.tsx +52 -0
  33. package/components/shared/layout-constants.ts +1 -1
  34. package/components/ui/chart/chart.stories.tsx +966 -7
  35. package/components/ui/chart/chart.tsx +918 -45
  36. package/components/ui/file-upload/file-upload.stories.tsx +100 -0
  37. package/components/ui/file-upload/file-upload.tsx +14 -74
  38. package/components/ui/file-upload/index.ts +1 -0
  39. package/components/ui/file-upload/use-file-upload.ts +181 -0
  40. package/components/ui/pagination/index.ts +2 -0
  41. package/components/ui/pagination/pagination.stories.tsx +94 -0
  42. package/components/ui/pagination/use-pagination.ts +194 -0
  43. package/components/ui/rich-text-editor/index.ts +2 -0
  44. package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +129 -1
  45. package/components/ui/rich-text-editor/rich-text-editor.tsx +86 -305
  46. package/components/ui/rich-text-editor/use-rich-text-editor.ts +439 -0
  47. package/components/ui/stepper/index.ts +3 -1
  48. package/components/ui/stepper/stepper.stories.tsx +116 -0
  49. package/components/ui/stepper/stepper.tsx +4 -4
  50. package/components/ui/stepper/use-stepper.ts +137 -0
  51. package/components/ui/tree-view/index.ts +4 -1
  52. package/components/ui/tree-view/tree-view.stories.tsx +110 -4
  53. package/components/ui/tree-view/tree-view.tsx +17 -125
  54. package/components/ui/tree-view/use-tree-view.ts +229 -0
  55. package/contexts/AssistenteContext.tsx +17 -54
  56. package/contexts/BrandColorsContext.tsx +6 -17
  57. package/contexts/LayoutContext.tsx +5 -31
  58. package/dist/AssistantChart-BAudAfne.cjs +3591 -0
  59. package/dist/AssistantChart-BP8upjMk.js +3565 -0
  60. package/dist/AudioPlayer-1ypwE2Wh.cjs +936 -0
  61. package/dist/AudioPlayer-DuKXrCfy.js +937 -0
  62. package/dist/CustomTooltipContent-DHjkY0ww.js +40 -0
  63. package/dist/CustomTooltipContent-c_K-DWRr.cjs +56 -0
  64. package/dist/LanguageContext-BwhwC3G2.js +657 -0
  65. package/dist/LanguageContext-DvUt5jBg.cjs +656 -0
  66. package/dist/LayoutContext-BDmcZfMH.cjs +84 -0
  67. package/dist/LayoutContext-dbQvdC4O.js +85 -0
  68. package/dist/ThemeContext-RTy1m2Uq.js +82 -0
  69. package/dist/ThemeContext-bSzuOit2.cjs +81 -0
  70. package/dist/VerifyEmailPage-C_ihbcth.js +2828 -0
  71. package/dist/VerifyEmailPage-Dt7zgA4w.cjs +2827 -0
  72. package/dist/XerticaProvider-CW9hpCdF.cjs +39 -0
  73. package/dist/XerticaProvider-siSt9uG2.js +40 -0
  74. package/dist/XerticaXLogo-D8jf0SNv.cjs +214 -0
  75. package/dist/XerticaXLogo-fAJMy3H4.js +215 -0
  76. package/dist/assistant.cjs.js +2 -1
  77. package/dist/assistant.es.js +3 -2
  78. package/dist/brand.cjs.js +2 -2
  79. package/dist/brand.es.js +2 -2
  80. package/dist/cli.js +14 -8
  81. package/dist/components/assistant/xertica-assistant/index.d.ts +2 -0
  82. package/dist/components/assistant/xertica-assistant/parts/AssistantCollapsedView.d.ts +13 -0
  83. package/dist/components/assistant/xertica-assistant/parts/AssistantConversationList.d.ts +16 -0
  84. package/dist/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.d.ts +17 -0
  85. package/dist/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.d.ts +19 -0
  86. package/dist/components/assistant/xertica-assistant/parts/AssistantHeader.d.ts +11 -0
  87. package/dist/components/assistant/xertica-assistant/parts/AssistantMessageBubble.d.ts +29 -0
  88. package/dist/components/assistant/xertica-assistant/parts/AssistantTabBar.d.ts +13 -0
  89. package/dist/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.d.ts +4 -0
  90. package/dist/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.d.ts +17 -0
  91. package/dist/components/assistant/xertica-assistant/parts/index.d.ts +16 -0
  92. package/dist/components/assistant/xertica-assistant/types.d.ts +106 -0
  93. package/dist/components/assistant/xertica-assistant/use-assistant.d.ts +125 -0
  94. package/dist/components/assistant/xertica-assistant/xertica-assistant.d.ts +8 -97
  95. package/dist/components/hooks/index.d.ts +3 -0
  96. package/dist/components/hooks/use-layout-shortcuts.d.ts +22 -0
  97. package/dist/components/layout/sidebar/index.d.ts +2 -0
  98. package/dist/components/layout/sidebar/sidebar.d.ts +80 -0
  99. package/dist/components/layout/sidebar/use-sidebar.d.ts +22 -0
  100. package/dist/components/media/audio-player/AudioPlayer.d.ts +4 -1
  101. package/dist/components/media/audio-player/use-audio-player.d.ts +72 -0
  102. package/dist/components/shared/CustomTooltipContent.d.ts +20 -0
  103. package/dist/components/shared/layout-constants.d.ts +1 -1
  104. package/dist/components/ui/alert/alert.d.ts +1 -1
  105. package/dist/components/ui/badge/badge.d.ts +1 -1
  106. package/dist/components/ui/button/button.d.ts +2 -2
  107. package/dist/components/ui/chart/chart.d.ts +162 -5
  108. package/dist/components/ui/file-upload/file-upload.d.ts +2 -0
  109. package/dist/components/ui/file-upload/index.d.ts +1 -0
  110. package/dist/components/ui/file-upload/use-file-upload.d.ts +49 -0
  111. package/dist/components/ui/pagination/index.d.ts +2 -0
  112. package/dist/components/ui/pagination/use-pagination.d.ts +78 -0
  113. package/dist/components/ui/rich-text-editor/index.d.ts +2 -0
  114. package/dist/components/ui/rich-text-editor/use-rich-text-editor.d.ts +107 -0
  115. package/dist/components/ui/stepper/index.d.ts +3 -1
  116. package/dist/components/ui/stepper/stepper.d.ts +2 -2
  117. package/dist/components/ui/stepper/use-stepper.d.ts +60 -0
  118. package/dist/components/ui/tree-view/index.d.ts +4 -1
  119. package/dist/components/ui/tree-view/tree-view.d.ts +4 -6
  120. package/dist/components/ui/tree-view/use-tree-view.d.ts +60 -0
  121. package/dist/contexts/AssistenteContext.d.ts +10 -49
  122. package/dist/hooks.cjs.js +30 -10
  123. package/dist/hooks.es.js +25 -4
  124. package/dist/index.cjs.js +20 -9
  125. package/dist/index.es.js +38 -27
  126. package/dist/layout.cjs.js +82 -1
  127. package/dist/layout.es.js +83 -2
  128. package/dist/media.cjs.js +1 -1
  129. package/dist/media.es.js +1 -1
  130. package/dist/pages.cjs.js +1 -1
  131. package/dist/pages.es.js +1 -1
  132. package/dist/rich-text-editor-BmsjY03B.js +2949 -0
  133. package/dist/rich-text-editor-GS2kpTAK.cjs +2966 -0
  134. package/dist/sidebar-CVUGHOS_.cjs +756 -0
  135. package/dist/sidebar-CmvwjnVb.js +757 -0
  136. package/dist/ui.cjs.js +12 -2
  137. package/dist/ui.es.js +24 -14
  138. package/dist/use-audio-player-Bkh23vQ3.js +177 -0
  139. package/dist/use-audio-player-Dn1NR9xN.cjs +176 -0
  140. package/dist/utils/color-utils.d.ts +51 -0
  141. package/dist/xertica-assistant-BMqdyRVi.js +2082 -0
  142. package/dist/xertica-assistant-Bj3vBCq_.cjs +2081 -0
  143. package/dist/xertica-ui.css +1 -1
  144. package/docs/ai-usage.md +28 -10
  145. package/docs/architecture-improvements.md +463 -0
  146. package/docs/architecture.md +77 -1
  147. package/docs/components/assistant-chart.md +1 -1
  148. package/docs/components/assistant.md +159 -0
  149. package/docs/components/audio-player.md +46 -0
  150. package/docs/components/branding.md +251 -0
  151. package/docs/components/chart.md +354 -39
  152. package/docs/components/code-block.md +108 -0
  153. package/docs/components/file-upload.md +119 -2
  154. package/docs/components/formatted-document.md +113 -0
  155. package/docs/components/hooks.md +430 -0
  156. package/docs/components/image-with-fallback.md +106 -0
  157. package/docs/components/map-layers.md +140 -0
  158. package/docs/components/modern-chat-input.md +163 -0
  159. package/docs/components/pages.md +351 -0
  160. package/docs/components/pagination.md +187 -0
  161. package/docs/components/rich-text-editor.md +164 -0
  162. package/docs/components/sidebar.md +153 -4
  163. package/docs/components/stepper.md +157 -12
  164. package/docs/components/tree-view.md +164 -6
  165. package/docs/doc-audit.md +223 -0
  166. package/docs/getting-started.md +155 -1
  167. package/docs/guidelines.md +14 -8
  168. package/docs/layout.md +2 -2
  169. package/docs/llms.md +29 -9
  170. package/docs/patterns/detail-page.md +276 -0
  171. package/docs/patterns/settings.md +346 -0
  172. package/docs/patterns/wizard.md +217 -0
  173. package/guidelines/Guidelines.md +5 -3
  174. package/llms.txt +1 -1
  175. package/package.json +10 -10
  176. package/styles/xertica/tokens.css +41 -12
  177. package/templates/CLAUDE.md +16 -6
  178. package/templates/guidelines/Guidelines.md +16 -4
  179. package/templates/package.json +3 -3
  180. package/templates/src/styles/xertica/tokens.css +39 -10
  181. package/utils/color-utils.ts +72 -0
@@ -1,6 +1,7 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React from 'react';
2
2
  import { Sun, Moon } from 'lucide-react';
3
3
  import { Button } from '../../ui/button';
4
+ import { useTheme } from '../../../contexts/ThemeContext';
4
5
 
5
6
  interface ThemeToggleProps {
6
7
  size?: 'sm' | 'md' | 'lg';
@@ -9,33 +10,13 @@ interface ThemeToggleProps {
9
10
  showLabel?: boolean;
10
11
  }
11
12
 
12
- export function ThemeToggle({
13
- size = 'md',
14
- variant = 'ghost',
13
+ export function ThemeToggle({
14
+ size = 'md',
15
+ variant = 'ghost',
15
16
  className = '',
16
- showLabel = false
17
+ showLabel = false
17
18
  }: ThemeToggleProps) {
18
- const [isDark, setIsDark] = useState(false);
19
-
20
- useEffect(() => {
21
- // Check initial state from HTML element
22
- if (typeof document !== 'undefined') {
23
- setIsDark(document.documentElement.classList.contains('dark'));
24
- }
25
- }, []);
26
-
27
- const toggleTheme = () => {
28
- if (typeof document !== 'undefined') {
29
- const isCurrentlyDark = document.documentElement.classList.contains('dark');
30
- if (isCurrentlyDark) {
31
- document.documentElement.classList.remove('dark');
32
- setIsDark(false);
33
- } else {
34
- document.documentElement.classList.add('dark');
35
- setIsDark(true);
36
- }
37
- }
38
- };
19
+ const { isDark, toggleTheme } = useTheme();
39
20
 
40
21
  const getSizeClasses = () => {
41
22
  switch (size) {
@@ -82,4 +63,4 @@ export function ThemeToggle({
82
63
  )}
83
64
  </Button>
84
65
  );
85
- }
66
+ }
@@ -5,3 +5,6 @@ export * from '../../contexts/BrandColorsContext';
5
5
  export * from '../../contexts/AssistenteContext';
6
6
  export * from '../../contexts/ApiKeyContext';
7
7
  export { useMobile, useIsMobile } from '../shared/use-mobile';
8
+ export { useLayoutShortcuts } from './use-layout-shortcuts';
9
+ export { useAudioPlayer } from '../media/audio-player/use-audio-player';
10
+ export type { UseAudioPlayerProps, UseAudioPlayerReturn } from '../media/audio-player/use-audio-player';
@@ -0,0 +1,46 @@
1
+ import { useEffect } from 'react';
2
+ import { useLayout } from '../../contexts/LayoutContext';
3
+
4
+ const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
5
+
6
+ /**
7
+ * `useLayoutShortcuts` — Registra atalhos de teclado globais para controle de layout.
8
+ *
9
+ * Deve ser usado uma única vez no componente raiz da aplicação (ex: `App.tsx` ou `Layout.tsx`).
10
+ * Extrai a responsabilidade de atalhos de teclado do `LayoutContext`, seguindo o
11
+ * princípio de responsabilidade única.
12
+ *
13
+ * **Atalhos registrados:**
14
+ * - `Ctrl+B` / `Cmd+B` — Alterna a sidebar
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * // Em App.tsx ou no componente de layout raiz:
19
+ * import { useLayoutShortcuts } from 'xertica-ui/hooks';
20
+ *
21
+ * function AppLayout({ children }: { children: React.ReactNode }) {
22
+ * useLayoutShortcuts(); // registra Ctrl+B para toggle da sidebar
23
+ * return <div>{children}</div>;
24
+ * }
25
+ * ```
26
+ */
27
+ export function useLayoutShortcuts() {
28
+ const { toggleSidebar } = useLayout();
29
+
30
+ useEffect(() => {
31
+ if (typeof window === 'undefined') return;
32
+
33
+ const handleKeyDown = (event: KeyboardEvent) => {
34
+ if (
35
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
36
+ (event.metaKey || event.ctrlKey)
37
+ ) {
38
+ event.preventDefault();
39
+ toggleSidebar();
40
+ }
41
+ };
42
+
43
+ window.addEventListener('keydown', handleKeyDown);
44
+ return () => window.removeEventListener('keydown', handleKeyDown);
45
+ }, [toggleSidebar]);
46
+ }
@@ -1 +1,3 @@
1
1
  export * from './sidebar';
2
+ export { useSidebar } from './use-sidebar';
3
+ export type { UseSidebarProps } from './use-sidebar';
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
- import { Sidebar } from './sidebar';
2
+ import { Sidebar, useSidebar } from './sidebar';
3
3
  import { useLayout } from '../../../contexts/LayoutContext';
4
4
  import { BrowserRouter } from 'react-router-dom';
5
5
  import {
@@ -12,6 +12,7 @@ import {
12
12
  FileEdit,
13
13
  ArrowRightLeft,
14
14
  PanelLeft,
15
+ Menu,
15
16
  Trash2,
16
17
  Map,
17
18
  Archive
@@ -63,8 +64,8 @@ export const Default: Story = {
63
64
  const { sidebarWidth: contextWidth } = useLayout();
64
65
  const [isExpanded, setIsExpanded] = useState(args.expanded);
65
66
 
66
- // In Storybook, we match the sidebar widths (256 expanded, 80 collapsed)
67
- const currentWidth = isExpanded ? 256 : 80;
67
+ // In Storybook, we match the sidebar widths (280 expanded, 80 collapsed)
68
+ const currentWidth = isExpanded ? 280 : 80;
68
69
 
69
70
  return (
70
71
  <div className="relative h-screen w-full border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
@@ -136,7 +137,7 @@ export const DefaultWithGroups: Story = {
136
137
  },
137
138
  render: (args) => {
138
139
  const [isExpanded, setIsExpanded] = useState(args.expanded);
139
- const currentWidth = isExpanded ? 256 : 80;
140
+ const currentWidth = isExpanded ? 280 : 80;
140
141
 
141
142
  return (
142
143
  <div className="relative h-screen w-full border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
@@ -249,7 +250,7 @@ export const Assistant: Story = {
249
250
  },
250
251
  render: (args) => {
251
252
  const [isExpanded, setIsExpanded] = useState(args.expanded);
252
- const currentWidth = isExpanded ? 256 : 80;
253
+ const currentWidth = isExpanded ? 280 : 80;
253
254
 
254
255
  return (
255
256
  <div className="relative h-screen w-full border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
@@ -278,7 +279,7 @@ export const Collapsed: Story = {
278
279
  },
279
280
  render: (args) => {
280
281
  const [isExpanded, setIsExpanded] = useState(args.expanded);
281
- const currentWidth = isExpanded ? 256 : 80;
282
+ const currentWidth = isExpanded ? 280 : 80;
282
283
 
283
284
  return (
284
285
  <div className="relative h-screen w-full border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
@@ -306,7 +307,7 @@ export const NoFooter: Story = {
306
307
  },
307
308
  render: (args) => {
308
309
  const [isExpanded, setIsExpanded] = useState(args.expanded);
309
- const currentWidth = isExpanded ? 256 : 80;
310
+ const currentWidth = isExpanded ? 280 : 80;
310
311
 
311
312
  return (
312
313
  <div className="relative h-screen w-full border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
@@ -344,7 +345,7 @@ export const Autonomous: Story = {
344
345
  <div className="text-center max-w-md">
345
346
  <p className="text-muted-foreground font-bold">Autonomous Sidebar</p>
346
347
  <p className="text-sm text-muted-foreground/80 mt-2">
347
- This instance is managing its own state internally.
348
+ This instance is managing its own state internally.
348
349
  No `expanded` or `onToggle` props are passed.
349
350
  </p>
350
351
  <p className="text-xs text-muted-foreground mt-4">
@@ -356,3 +357,154 @@ export const Autonomous: Story = {
356
357
  );
357
358
  },
358
359
  };
360
+
361
+ /**
362
+ * Compound Component API — demonstrates using Sidebar.Root + sub-components
363
+ * for full layout control. This is the recommended pattern for advanced
364
+ * customization without forking the component.
365
+ */
366
+ export const CompoundComponentAPI: Story = {
367
+ name: 'Compound Component API',
368
+ render: () => {
369
+ const [isExpanded, setIsExpanded] = React.useState(true);
370
+ const currentWidth = isExpanded ? 280 : 80;
371
+ const groups = [
372
+ {
373
+ id: 'main',
374
+ label: 'Principal',
375
+ items: [
376
+ { path: '/home', label: 'Início', icon: Home },
377
+ { path: '/dashboard', label: 'Dashboard', icon: BarChart },
378
+ ],
379
+ },
380
+ {
381
+ id: 'admin',
382
+ label: 'Administração',
383
+ items: [
384
+ { path: '/users', label: 'Usuários', icon: Users },
385
+ { path: '/settings', label: 'Configurações', icon: Settings },
386
+ ],
387
+ },
388
+ ];
389
+
390
+ return (
391
+ <div className="relative h-screen w-full border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
392
+ {/* Compound Component usage */}
393
+ <Sidebar.Root
394
+ expanded={isExpanded}
395
+ onToggle={() => setIsExpanded(prev => !prev)}
396
+ width={currentWidth}
397
+ location={{ pathname: '/home' }}
398
+ navigate={() => { }}
399
+ >
400
+ <Sidebar.Header />
401
+ <Sidebar.Nav navigationGroups={groups} />
402
+ <Sidebar.Footer
403
+ user={{ name: 'Admin', email: 'admin@example.com' }}
404
+ onLogout={() => console.log('logout')}
405
+ showUser
406
+ showSettings
407
+ showLogout
408
+ />
409
+ </Sidebar.Root>
410
+
411
+ <div
412
+ className="absolute inset-y-0 right-0 p-8 flex items-center justify-center transition-all duration-300"
413
+ style={{ left: `${currentWidth}px` }}
414
+ >
415
+ <div className="text-center max-w-md space-y-3">
416
+ <p className="text-muted-foreground font-bold">Compound Component API</p>
417
+ <p className="text-sm text-muted-foreground/80">
418
+ Built with <code className="bg-muted px-1 rounded text-xs">&lt;Sidebar.Root&gt;</code>,{' '}
419
+ <code className="bg-muted px-1 rounded text-xs">&lt;Sidebar.Header&gt;</code>,{' '}
420
+ <code className="bg-muted px-1 rounded text-xs">&lt;Sidebar.Nav&gt;</code>, and{' '}
421
+ <code className="bg-muted px-1 rounded text-xs">&lt;Sidebar.Footer&gt;</code>.
422
+ </p>
423
+ <p className="text-xs text-muted-foreground">
424
+ Each sub-component reads shared state from <code className="bg-muted px-1 rounded text-xs">SidebarContext</code> via <code className="bg-muted px-1 rounded text-xs">Sidebar.Root</code>.
425
+ </p>
426
+ </div>
427
+ </div>
428
+ </div>
429
+ );
430
+ },
431
+ };
432
+
433
+ /**
434
+ * Headless Hook — demonstrates using `useSidebar` directly for fully custom rendering.
435
+ * The hook manages all state; the UI is entirely up to the consumer.
436
+ */
437
+ export const HeadlessHook: Story = {
438
+ name: 'Headless Hook (useSidebar)',
439
+ render: () => {
440
+ const groups = [
441
+ {
442
+ id: 'main',
443
+ label: 'Principal',
444
+ items: [
445
+ { path: '/home', label: 'Início', icon: Home },
446
+ { path: '/dashboard', label: 'Dashboard', icon: BarChart },
447
+ { path: '/users', label: 'Usuários', icon: Users },
448
+ ],
449
+ },
450
+ ];
451
+
452
+ const {
453
+ expanded,
454
+ toggleExpanded,
455
+ navigationGroups: resolvedGroups,
456
+ } = useSidebar({ defaultExpanded: true, navigationGroups: groups });
457
+
458
+ const width = expanded ? 280 : 80;
459
+
460
+ return (
461
+ <div className="relative h-screen w-full border rounded-[var(--radius-lg)] bg-muted/20 overflow-hidden" style={{ transform: "translateZ(0)" }}>
462
+ {/* Fully custom sidebar built with useSidebar hook */}
463
+ <div
464
+ className="absolute inset-y-0 left-0 bg-sidebar text-sidebar-foreground flex flex-col transition-all duration-300 z-50"
465
+ style={{ width: `${width}px` }}
466
+ >
467
+ <div className="p-4">
468
+ <button
469
+ onClick={toggleExpanded}
470
+ className="w-full h-10 flex items-center justify-center rounded-md bg-sidebar-foreground/10 hover:bg-sidebar-foreground/20 text-sidebar-foreground transition-colors"
471
+ aria-label={expanded ? 'Collapse' : 'Expand'}
472
+ >
473
+ {expanded ? <PanelLeft className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
474
+ </button>
475
+ </div>
476
+ <nav className="flex-1 px-3 space-y-1">
477
+ {resolvedGroups.flatMap(g => g.items).map(item => {
478
+ const Icon = item.icon;
479
+ return (
480
+ <button
481
+ key={item.path}
482
+ className="w-full h-10 flex items-center gap-3 px-3 rounded-md text-sidebar-foreground/80 hover:bg-sidebar-foreground/15 hover:text-sidebar-foreground transition-colors"
483
+ >
484
+ {Icon && <Icon className="w-5 h-5 flex-shrink-0" />}
485
+ {expanded && <span className="truncate text-sm">{item.label}</span>}
486
+ </button>
487
+ );
488
+ })}
489
+ </nav>
490
+ </div>
491
+
492
+ <div
493
+ className="absolute inset-y-0 right-0 p-8 flex items-center justify-center transition-all duration-300"
494
+ style={{ left: `${width}px` }}
495
+ >
496
+ <div className="text-center max-w-md space-y-3">
497
+ <p className="text-muted-foreground font-bold">Headless Hook</p>
498
+ <p className="text-sm text-muted-foreground/80">
499
+ State managed by <code className="bg-muted px-1 rounded text-xs">useSidebar()</code>.
500
+ The entire UI is custom — no Sidebar component used.
501
+ </p>
502
+ <p className="text-xs text-muted-foreground">
503
+ Sidebar is currently: <strong>{expanded ? 'Expanded' : 'Collapsed'}</strong>
504
+ </p>
505
+ </div>
506
+ </div>
507
+ </div>
508
+ );
509
+ },
510
+ };