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
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import 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
|
|
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,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 (
|
|
67
|
-
const currentWidth = isExpanded ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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"><Sidebar.Root></code>,{' '}
|
|
419
|
+
<code className="bg-muted px-1 rounded text-xs"><Sidebar.Header></code>,{' '}
|
|
420
|
+
<code className="bg-muted px-1 rounded text-xs"><Sidebar.Nav></code>, and{' '}
|
|
421
|
+
<code className="bg-muted px-1 rounded text-xs"><Sidebar.Footer></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
|
+
};
|