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
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
A drag-and-drop file input component that accepts one or multiple files. Renders a dropzone area with visual feedback on drag, displays selected file names, and validates against allowed MIME types and file size limits.
|
|
6
6
|
|
|
7
|
+
The package also exports a **headless `useFileUpload` hook** for building fully custom drop-zone UIs while reusing all the validation and drag-state logic.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Exports
|
|
12
|
+
|
|
13
|
+
| Export | Description |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `FileUpload` | Ready-to-use drag-and-drop file input component |
|
|
16
|
+
| `useFileUpload` | Headless hook — all logic, no UI |
|
|
17
|
+
| `UseFileUploadProps` | TypeScript props type for the hook |
|
|
18
|
+
| `UseFileUploadReturn` | TypeScript return type for the hook |
|
|
19
|
+
|
|
7
20
|
---
|
|
8
21
|
|
|
9
22
|
## When to Use
|
|
@@ -15,7 +28,7 @@ A drag-and-drop file input component that accepts one or multiple files. Renders
|
|
|
15
28
|
|
|
16
29
|
---
|
|
17
30
|
|
|
18
|
-
## Props
|
|
31
|
+
## `FileUpload` Component Props
|
|
19
32
|
|
|
20
33
|
| Prop | Type | Default | Required | Description |
|
|
21
34
|
|---|---|---|---|---|
|
|
@@ -75,9 +88,113 @@ import { FileUpload } from 'xertica-ui/ui';
|
|
|
75
88
|
|
|
76
89
|
---
|
|
77
90
|
|
|
91
|
+
## `useFileUpload` Hook
|
|
92
|
+
|
|
93
|
+
A headless hook that encapsulates all file upload logic — drag state, file validation, error messaging, and the hidden input ref — without rendering any UI. Use it when the `<FileUpload>` component's visual design doesn't fit your needs.
|
|
94
|
+
|
|
95
|
+
### Props
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Default | Description |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| `maxFiles` | `number` | `1` | Maximum number of files allowed |
|
|
100
|
+
| `maxSize` | `number` | `5242880` (5MB) | Maximum file size in bytes |
|
|
101
|
+
| `onFilesChange` | `(files: File[]) => void` | — | Called with the accepted file list whenever it changes |
|
|
102
|
+
| `onError` | `(rejected: File[], reason: 'size' \| 'count') => void` | — | Called when files are rejected |
|
|
103
|
+
| `disabled` | `boolean` | `false` | Whether the upload area is disabled |
|
|
104
|
+
|
|
105
|
+
### Return Value
|
|
106
|
+
|
|
107
|
+
| Property | Type | Description |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `files` | `File[]` | Currently accepted files |
|
|
110
|
+
| `dragActive` | `boolean` | Whether a drag is active over the drop zone |
|
|
111
|
+
| `errorMessage` | `string \| null` | Inline error message, or `null` |
|
|
112
|
+
| `inputRef` | `RefObject<HTMLInputElement>` | Attach to the hidden `<input type="file">` |
|
|
113
|
+
| `handleFiles` | `(files: FileList \| null) => void` | Process a `FileList` from any source |
|
|
114
|
+
| `handleDrag` | `(e: DragEvent) => void` | Attach to `onDragEnter`, `onDragOver`, `onDragLeave` |
|
|
115
|
+
| `handleDrop` | `(e: DragEvent) => void` | Attach to `onDrop` |
|
|
116
|
+
| `handleChange` | `(e: ChangeEvent<HTMLInputElement>) => void` | Attach to the hidden input's `onChange` |
|
|
117
|
+
| `removeFile` | `(index: number) => void` | Remove a file by its index |
|
|
118
|
+
| `openFileDialog` | `() => void` | Programmatically open the native file picker |
|
|
119
|
+
|
|
120
|
+
### Custom Drop Zone Example
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { useFileUpload } from 'xertica-ui/ui';
|
|
124
|
+
import { UploadCloud } from 'lucide-react';
|
|
125
|
+
|
|
126
|
+
function CustomDropZone() {
|
|
127
|
+
const {
|
|
128
|
+
files,
|
|
129
|
+
dragActive,
|
|
130
|
+
errorMessage,
|
|
131
|
+
inputRef,
|
|
132
|
+
handleDrag,
|
|
133
|
+
handleDrop,
|
|
134
|
+
handleChange,
|
|
135
|
+
removeFile,
|
|
136
|
+
openFileDialog,
|
|
137
|
+
} = useFileUpload({
|
|
138
|
+
maxFiles: 3,
|
|
139
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
140
|
+
onFilesChange: (files) => console.log('Files:', files),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div className="space-y-3">
|
|
145
|
+
<div
|
|
146
|
+
onDragEnter={handleDrag}
|
|
147
|
+
onDragOver={handleDrag}
|
|
148
|
+
onDragLeave={handleDrag}
|
|
149
|
+
onDrop={handleDrop}
|
|
150
|
+
onClick={openFileDialog}
|
|
151
|
+
className={cn(
|
|
152
|
+
"flex flex-col items-center justify-center rounded-xl border-2 border-dashed p-10 cursor-pointer transition-colors",
|
|
153
|
+
dragActive ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
<UploadCloud className="size-8 text-muted-foreground mb-2" />
|
|
157
|
+
<p className="text-sm text-muted-foreground">
|
|
158
|
+
Drop files here or <span className="text-primary font-medium">browse</span>
|
|
159
|
+
</p>
|
|
160
|
+
<input
|
|
161
|
+
ref={inputRef}
|
|
162
|
+
type="file"
|
|
163
|
+
multiple
|
|
164
|
+
className="hidden"
|
|
165
|
+
onChange={handleChange}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{errorMessage && (
|
|
170
|
+
<p className="text-sm text-destructive">{errorMessage}</p>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{files.length > 0 && (
|
|
174
|
+
<ul className="space-y-1">
|
|
175
|
+
{files.map((file, i) => (
|
|
176
|
+
<li key={i} className="flex items-center justify-between text-sm">
|
|
177
|
+
<span>{file.name}</span>
|
|
178
|
+
<Button size="sm" variant="ghost" onClick={() => removeFile(i)}>
|
|
179
|
+
Remove
|
|
180
|
+
</Button>
|
|
181
|
+
</li>
|
|
182
|
+
))}
|
|
183
|
+
</ul>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
78
192
|
## AI Rules
|
|
79
193
|
|
|
80
|
-
- Do not use native `<input type="file">` — always use `<FileUpload>` from `xertica-ui`.
|
|
194
|
+
- Do not use native `<input type="file">` — always use `<FileUpload>` or `useFileUpload` from `xertica-ui`.
|
|
81
195
|
- `maxSize` is in bytes — calculate using `MB * 1024 * 1024`.
|
|
82
196
|
- In forms, use `onFilesChange` to call `field.onChange` — not `{...field}`.
|
|
83
197
|
- For avatar uploads, use `accept="image/*"` and `multiple={false}`.
|
|
198
|
+
- When using `useFileUpload`, attach `handleDrag` to **all three** drag events: `onDragEnter`, `onDragOver`, and `onDragLeave`.
|
|
199
|
+
- Always attach `inputRef` to a hidden `<input type="file">` element — `openFileDialog()` requires it.
|
|
200
|
+
- `removeFile(index)` removes by array index, not by file name.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# FormattedDocument
|
|
2
|
+
|
|
3
|
+
A lightweight Markdown-to-HTML renderer with a collapsible preview. Designed for displaying AI-generated documents, reports, and structured text inside the assistant panel.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { FormattedDocument } from 'xertica-ui/assistant';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
<FormattedDocument
|
|
19
|
+
content="# Report\n\nThis is a **bold** statement.\n\n- Item one\n- Item two"
|
|
20
|
+
/>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Default | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `content` | `string` | _(required)_ | Raw Markdown string to render |
|
|
30
|
+
| `maxPreviewLength` | `number` | `500` | Character count shown before the "See more" toggle appears |
|
|
31
|
+
| `className` | `string` | `''` | Additional CSS classes for the outer container |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Supported Markdown Syntax
|
|
36
|
+
|
|
37
|
+
| Syntax | Output |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `# Heading 1` | `<h1>` — `text-xl font-medium` |
|
|
40
|
+
| `## Heading 2` | `<h2>` — `text-lg font-medium` |
|
|
41
|
+
| `### Heading 3` | `<h3>` — `text-base font-medium` |
|
|
42
|
+
| `**bold**` | `<strong>` — `font-medium text-foreground` |
|
|
43
|
+
| `*italic*` | `<em>` — `italic` |
|
|
44
|
+
| `- item` / `* item` | Unordered list (`<ul>`) |
|
|
45
|
+
| `1. item` | Ordered list (`<ol>`) |
|
|
46
|
+
| `- [ ] task` | Unchecked checkbox (disabled) |
|
|
47
|
+
| `- [x] task` | Checked checkbox (disabled) |
|
|
48
|
+
| `---` | Horizontal rule (`<hr>`) |
|
|
49
|
+
| `\n\n` | Paragraph break |
|
|
50
|
+
|
|
51
|
+
> **Note**: This is a simplified renderer. It does not support tables, links, images, inline code, or fenced code blocks. For full Markdown rendering with code highlighting, use `MarkdownMessage` instead.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Collapsible Preview
|
|
56
|
+
|
|
57
|
+
When the `content` string exceeds `maxPreviewLength` characters, the component renders a truncated preview with a "Ver mais" (See more) button. Clicking it expands the full content. A "Ver menos" (See less) button collapses it again.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// Long document — will show preview with toggle
|
|
61
|
+
<FormattedDocument
|
|
62
|
+
content={longAiGeneratedText}
|
|
63
|
+
maxPreviewLength={300}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Comparison: `FormattedDocument` vs `MarkdownMessage`
|
|
70
|
+
|
|
71
|
+
| Feature | `FormattedDocument` | `MarkdownMessage` |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Headers | ✅ | ✅ |
|
|
74
|
+
| Bold / Italic | ✅ | ✅ |
|
|
75
|
+
| Lists | ✅ | ✅ |
|
|
76
|
+
| Checkboxes | ✅ | ❌ |
|
|
77
|
+
| Code blocks | ❌ | ✅ (via `CodeBlock`) |
|
|
78
|
+
| Tables | ❌ | ✅ |
|
|
79
|
+
| Links | ❌ | ✅ |
|
|
80
|
+
| Collapsible preview | ✅ | ❌ |
|
|
81
|
+
| Best for | AI documents, reports | AI chat messages |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Usage in AI Context
|
|
86
|
+
|
|
87
|
+
`FormattedDocument` is used by `XerticaAssistant` when the AI returns a document-type response (triggered by the "Create document" action in `ModernChatInput`). The document is also editable via the `RichTextEditor` panel.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { FormattedDocument } from 'xertica-ui/assistant';
|
|
91
|
+
|
|
92
|
+
function DocumentViewer({ aiOutput }: { aiOutput: string }) {
|
|
93
|
+
return (
|
|
94
|
+
<div className="p-4 bg-card rounded-lg border border-border">
|
|
95
|
+
<FormattedDocument
|
|
96
|
+
content={aiOutput}
|
|
97
|
+
maxPreviewLength={800}
|
|
98
|
+
className="text-sm"
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## AI Rules
|
|
108
|
+
|
|
109
|
+
> [!IMPORTANT]
|
|
110
|
+
> - **Use for AI-generated documents, not chat messages**: For chat message rendering, use `MarkdownMessage`. `FormattedDocument` is optimized for longer structured content (reports, summaries, plans).
|
|
111
|
+
> - **No code block support**: If the AI response contains fenced code blocks (` ``` `), use `MarkdownMessage` or `CodeBlock` instead — `FormattedDocument` will render them as plain text.
|
|
112
|
+
> - **Checkboxes are read-only**: The rendered checkboxes have `disabled` attribute. They are for display only. For interactive task lists, build a custom component.
|
|
113
|
+
> - **`maxPreviewLength` is character-based**: It counts raw Markdown characters, not rendered text length. Set it higher than you think you need to avoid cutting off mid-sentence.
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Hooks — Xertica UI
|
|
2
|
+
|
|
3
|
+
Xertica UI exposes a set of React hooks via the `xertica-ui/hooks` subpath. These hooks provide access to global contexts (theme, language, layout, assistant, API keys, brand colors) and utility behaviors (audio player, keyboard shortcuts, mobile detection).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import {
|
|
11
|
+
useTheme,
|
|
12
|
+
useLanguage,
|
|
13
|
+
useBrandColors,
|
|
14
|
+
useAssistente,
|
|
15
|
+
useApiKey,
|
|
16
|
+
useLayout,
|
|
17
|
+
useAudioPlayer,
|
|
18
|
+
useLayoutShortcuts,
|
|
19
|
+
useIsMobile,
|
|
20
|
+
} from 'xertica-ui/hooks';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
All hooks are also available from the root barrel:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { useTheme, useLayout } from 'xertica-ui';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## `useTheme`
|
|
32
|
+
|
|
33
|
+
Accesses and controls the current color theme (light/dark).
|
|
34
|
+
|
|
35
|
+
**Source**: `contexts/ThemeContext.tsx`
|
|
36
|
+
|
|
37
|
+
### Return Value
|
|
38
|
+
|
|
39
|
+
| Property | Type | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `theme` | `'light' \| 'dark' \| 'system'` | Current active theme |
|
|
42
|
+
| `setTheme` | `(theme: Theme) => void` | Sets the theme explicitly |
|
|
43
|
+
| `toggleTheme` | `() => void` | Toggles between light and dark |
|
|
44
|
+
|
|
45
|
+
### Usage
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { useTheme } from 'xertica-ui/hooks';
|
|
49
|
+
|
|
50
|
+
function ThemeButton() {
|
|
51
|
+
const { theme, toggleTheme } = useTheme();
|
|
52
|
+
return (
|
|
53
|
+
<button onClick={toggleTheme}>
|
|
54
|
+
Current: {theme}
|
|
55
|
+
</button>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> **Note**: `useTheme` must be used inside `<XerticaProvider>` (which includes `ThemeProvider`). For a self-contained toggle that does not require context, use the `<ThemeToggle>` component instead.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## `useLanguage`
|
|
65
|
+
|
|
66
|
+
Accesses and controls the current UI language preference.
|
|
67
|
+
|
|
68
|
+
**Source**: `contexts/LanguageContext.tsx`
|
|
69
|
+
|
|
70
|
+
### Return Value
|
|
71
|
+
|
|
72
|
+
| Property | Type | Description |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `language` | `'pt-BR' \| 'en' \| 'es'` | Current language code |
|
|
75
|
+
| `setLanguage` | `(language: Language) => void` | Changes the language |
|
|
76
|
+
|
|
77
|
+
### Usage
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useLanguage } from 'xertica-ui/hooks';
|
|
81
|
+
|
|
82
|
+
function LanguageDisplay() {
|
|
83
|
+
const { language, setLanguage } = useLanguage();
|
|
84
|
+
return (
|
|
85
|
+
<select value={language} onChange={e => setLanguage(e.target.value as any)}>
|
|
86
|
+
<option value="pt-BR">Português</option>
|
|
87
|
+
<option value="en">English</option>
|
|
88
|
+
<option value="es">Español</option>
|
|
89
|
+
</select>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Note**: The language preference is persisted in `localStorage` under the key `xertica_language`. The library UI currently renders in Portuguese regardless of this setting — the selector is provided for consumer applications to implement their own i18n logic.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## `useBrandColors`
|
|
99
|
+
|
|
100
|
+
Accesses and modifies the brand color palette applied as CSS custom properties.
|
|
101
|
+
|
|
102
|
+
**Source**: `contexts/BrandColorsContext.tsx`
|
|
103
|
+
|
|
104
|
+
### Return Value
|
|
105
|
+
|
|
106
|
+
| Property | Type | Description |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `colors` | `BrandColors` | Current brand color values |
|
|
109
|
+
| `setBrandColor` | `(key: keyof BrandColors, value: string) => void` | Updates a single brand color |
|
|
110
|
+
| `currentTheme` | `string` | Name of the active color theme preset |
|
|
111
|
+
|
|
112
|
+
### Usage
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { useBrandColors } from 'xertica-ui/hooks';
|
|
116
|
+
|
|
117
|
+
function BrandCustomizer() {
|
|
118
|
+
const { colors, setBrandColor } = useBrandColors();
|
|
119
|
+
return (
|
|
120
|
+
<input
|
|
121
|
+
type="color"
|
|
122
|
+
value={colors.primary}
|
|
123
|
+
onChange={e => setBrandColor('primary', e.target.value)}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **Note**: Color changes are applied immediately as CSS variables on `:root`. They persist across re-renders but are not saved to `localStorage` by default.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## `useAssistente`
|
|
134
|
+
|
|
135
|
+
Accesses the global AI Assistant context — conversation list, current conversation, and navigation helpers.
|
|
136
|
+
|
|
137
|
+
**Source**: `contexts/AssistenteContext.tsx`
|
|
138
|
+
|
|
139
|
+
### Return Value
|
|
140
|
+
|
|
141
|
+
| Property | Type | Description |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `conversas` | `Conversa[]` | List of all conversations |
|
|
144
|
+
| `conversaAtual` | `string \| null` | ID of the currently active conversation |
|
|
145
|
+
| `setConversaAtual` | `(id: string \| null) => void` | Selects a conversation |
|
|
146
|
+
| `sugestoes` | `Sugestao[]` | Quick-reply suggestions for the welcome screen |
|
|
147
|
+
| `setSugestoes` | `(s: Sugestao[]) => void` | Replaces the suggestion list |
|
|
148
|
+
|
|
149
|
+
### Usage
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import { useAssistente } from 'xertica-ui/hooks';
|
|
153
|
+
|
|
154
|
+
function ConversationCount() {
|
|
155
|
+
const { conversas } = useAssistente();
|
|
156
|
+
return <span>{conversas.length} conversations</span>;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
> **Note**: This hook must be used inside `<XerticaProvider>` (which includes `AssistenteProvider`). It is primarily used internally by `XerticaAssistant`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## `useApiKey`
|
|
165
|
+
|
|
166
|
+
Accesses and manages API keys for Xertica services, Gemini AI, and Google Maps.
|
|
167
|
+
|
|
168
|
+
**Source**: `contexts/ApiKeyContext.tsx`
|
|
169
|
+
|
|
170
|
+
### Return Value
|
|
171
|
+
|
|
172
|
+
| Property | Type | Description |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `apiKey` | `string` | Xertica platform API key |
|
|
175
|
+
| `setApiKey` | `(key: string) => void` | Updates the Xertica API key |
|
|
176
|
+
| `geminiApiKey` | `string` | Google Gemini API key |
|
|
177
|
+
| `setGeminiApiKey` | `(key: string) => void` | Updates the Gemini API key |
|
|
178
|
+
| `isApiKeyValid` | `boolean` | Whether the Xertica API key is non-empty |
|
|
179
|
+
| `googleMapsApiKey` | `string` | Google Maps API key |
|
|
180
|
+
| `setGoogleMapsApiKey` | `(key: string) => void` | Updates the Maps API key |
|
|
181
|
+
| `isGoogleMapsKeyValid` | `boolean` | Whether the Maps key is non-empty |
|
|
182
|
+
| `reloadMapsApi` | `() => Promise<void>` | Reloads the Maps script after key change |
|
|
183
|
+
|
|
184
|
+
### Usage
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
import { useApiKey } from 'xertica-ui/hooks';
|
|
188
|
+
|
|
189
|
+
function ApiKeySettings() {
|
|
190
|
+
const { geminiApiKey, setGeminiApiKey, isApiKeyValid } = useApiKey();
|
|
191
|
+
return (
|
|
192
|
+
<input
|
|
193
|
+
type="password"
|
|
194
|
+
value={geminiApiKey}
|
|
195
|
+
onChange={e => setGeminiApiKey(e.target.value)}
|
|
196
|
+
placeholder="Enter Gemini API key"
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> **Note**: API keys are persisted in `localStorage`. The `XerticaAssistant` component reads `geminiApiKey` automatically when `mode="real"`.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## `useLayout`
|
|
207
|
+
|
|
208
|
+
Accesses and controls the global layout state — sidebar expansion, assistant panel, and width values.
|
|
209
|
+
|
|
210
|
+
**Source**: `contexts/LayoutContext.tsx`
|
|
211
|
+
|
|
212
|
+
### Return Value
|
|
213
|
+
|
|
214
|
+
| Property | Type | Description |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| `sidebarExpanded` | `boolean` | Whether the sidebar is expanded |
|
|
217
|
+
| `sidebarWidth` | `number` | Current sidebar width in pixels |
|
|
218
|
+
| `setSidebarWidth` | `(width: number) => void` | Updates the sidebar width |
|
|
219
|
+
| `toggleSidebar` | `() => void` | Toggles sidebar expanded state |
|
|
220
|
+
| `assistenteExpanded` | `boolean` | Whether the AI assistant panel is open |
|
|
221
|
+
| `toggleAssistente` | `() => void` | Toggles the assistant panel |
|
|
222
|
+
| `toggleAssistenteWithTab` | `(tab: string) => void` | Opens the assistant on a specific tab |
|
|
223
|
+
|
|
224
|
+
### Usage
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { useLayout } from 'xertica-ui/hooks';
|
|
228
|
+
|
|
229
|
+
function ContentArea() {
|
|
230
|
+
const { sidebarExpanded, sidebarWidth } = useLayout();
|
|
231
|
+
return (
|
|
232
|
+
<main style={{ marginLeft: sidebarWidth }}>
|
|
233
|
+
{/* content */}
|
|
234
|
+
</main>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
> **See also**: `docs/layout.md` for the complete layout system reference.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## `useAudioPlayer`
|
|
244
|
+
|
|
245
|
+
Headless hook that encapsulates all state and side effects for audio playback. Use this to build a fully custom audio player UI while reusing the library's playback logic.
|
|
246
|
+
|
|
247
|
+
**Source**: `components/media/audio-player/use-audio-player.ts`
|
|
248
|
+
|
|
249
|
+
### Props (`UseAudioPlayerProps`)
|
|
250
|
+
|
|
251
|
+
| Prop | Type | Default | Description |
|
|
252
|
+
|---|---|---|---|
|
|
253
|
+
| `src` | `string` | — | Audio file URL |
|
|
254
|
+
| `autoPlay` | `boolean` | `false` | Start playing on mount |
|
|
255
|
+
| `initialTime` | `number` | `0` | Initial seek position in seconds |
|
|
256
|
+
| `initialDuration` | `number` | — | Pre-known duration (loaded from metadata if omitted) |
|
|
257
|
+
| `variant` | `'card' \| 'bar'` | `'card'` | Layout variant (affects floating behavior) |
|
|
258
|
+
| `isOpen` | `boolean` | `true` | Whether the bar variant is visible |
|
|
259
|
+
| `enableAutoFloat` | `boolean` | `true` | Auto-float when scrolled out of view (card only) |
|
|
260
|
+
| `onCloseFloating` | `() => void` | — | Called when user closes the floating player |
|
|
261
|
+
|
|
262
|
+
### Return Value (`UseAudioPlayerReturn`)
|
|
263
|
+
|
|
264
|
+
**Refs**
|
|
265
|
+
|
|
266
|
+
| Property | Type | Description |
|
|
267
|
+
|---|---|---|
|
|
268
|
+
| `audioRef` | `RefObject<HTMLAudioElement>` | Attach to `<audio>` element |
|
|
269
|
+
| `containerRef` | `RefObject<HTMLDivElement>` | Attach to the player container for scroll detection |
|
|
270
|
+
|
|
271
|
+
**Playback State**
|
|
272
|
+
|
|
273
|
+
| Property | Type | Description |
|
|
274
|
+
|---|---|---|
|
|
275
|
+
| `isPlaying` | `boolean` | Whether audio is currently playing |
|
|
276
|
+
| `currentTime` | `number` | Current playback position in seconds |
|
|
277
|
+
| `duration` | `number` | Total duration in seconds |
|
|
278
|
+
| `volume` | `number` | Volume level (0–1) |
|
|
279
|
+
| `isMuted` | `boolean` | Whether audio is muted |
|
|
280
|
+
| `playbackSpeed` | `number` | Current playback speed (0.5, 1, 1.5, 2) |
|
|
281
|
+
|
|
282
|
+
**UI State**
|
|
283
|
+
|
|
284
|
+
| Property | Type | Description |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| `isFloating` | `boolean` | Whether the player is in floating mode |
|
|
287
|
+
| `isManualFloating` | `boolean` | Whether floating was triggered manually |
|
|
288
|
+
| `isVisible` | `boolean` | Whether the container is in the viewport |
|
|
289
|
+
| `isMobile` | `boolean` | Whether the viewport is mobile-sized |
|
|
290
|
+
| `enableAutoFloatLocal` | `boolean` | Current auto-float setting |
|
|
291
|
+
|
|
292
|
+
**Layout Context**
|
|
293
|
+
|
|
294
|
+
| Property | Type | Description |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| `sidebarExpanded` | `boolean` | Sidebar state (for bar positioning) |
|
|
297
|
+
| `sidebarWidth` | `number` | Sidebar width in pixels |
|
|
298
|
+
| `assistenteExpanded` | `boolean` | Assistant panel state |
|
|
299
|
+
|
|
300
|
+
**Handlers**
|
|
301
|
+
|
|
302
|
+
| Property | Type | Description |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| `togglePlay` | `() => void` | Play/pause toggle |
|
|
305
|
+
| `toggleMute` | `() => void` | Mute/unmute toggle |
|
|
306
|
+
| `handleSeek` | `(value: number[]) => void` | Seek to position (Slider-compatible) |
|
|
307
|
+
| `handleVolumeChange` | `(value: number[]) => void` | Change volume (Slider-compatible) |
|
|
308
|
+
| `handleSetFloating` | `(floating: boolean) => void` | Set floating state |
|
|
309
|
+
| `handleEnableManualFloat` | `() => void` | Trigger manual float |
|
|
310
|
+
| `setPlaybackSpeed` | `(speed: number) => void` | Change playback speed |
|
|
311
|
+
| `resetAudio` | `() => void` | Reset to beginning |
|
|
312
|
+
|
|
313
|
+
**Audio Element Event Handlers**
|
|
314
|
+
|
|
315
|
+
| Property | Type | Description |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| `onPlay` | `() => void` | Attach to `<audio onPlay>` |
|
|
318
|
+
| `onPause` | `() => void` | Attach to `<audio onPause>` |
|
|
319
|
+
| `onEnded` | `() => void` | Attach to `<audio onEnded>` |
|
|
320
|
+
| `onTimeUpdate` | `(e) => void` | Attach to `<audio onTimeUpdate>` |
|
|
321
|
+
| `onLoadedMetadata` | `(e) => void` | Attach to `<audio onLoadedMetadata>` |
|
|
322
|
+
|
|
323
|
+
**Utilities**
|
|
324
|
+
|
|
325
|
+
| Property | Type | Description |
|
|
326
|
+
|---|---|---|
|
|
327
|
+
| `formatTime` | `(time: number) => string` | Formats seconds as `MM:SS` |
|
|
328
|
+
|
|
329
|
+
### Usage
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import { useAudioPlayer } from 'xertica-ui/hooks';
|
|
333
|
+
|
|
334
|
+
function MyCustomPlayer({ src }: { src: string }) {
|
|
335
|
+
const {
|
|
336
|
+
audioRef,
|
|
337
|
+
containerRef,
|
|
338
|
+
isPlaying,
|
|
339
|
+
togglePlay,
|
|
340
|
+
currentTime,
|
|
341
|
+
duration,
|
|
342
|
+
formatTime,
|
|
343
|
+
onPlay,
|
|
344
|
+
onPause,
|
|
345
|
+
onEnded,
|
|
346
|
+
onTimeUpdate,
|
|
347
|
+
onLoadedMetadata,
|
|
348
|
+
} = useAudioPlayer({ src });
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<div ref={containerRef}>
|
|
352
|
+
<audio
|
|
353
|
+
ref={audioRef}
|
|
354
|
+
src={src}
|
|
355
|
+
onPlay={onPlay}
|
|
356
|
+
onPause={onPause}
|
|
357
|
+
onEnded={onEnded}
|
|
358
|
+
onTimeUpdate={onTimeUpdate}
|
|
359
|
+
onLoadedMetadata={onLoadedMetadata}
|
|
360
|
+
/>
|
|
361
|
+
<button onClick={togglePlay}>{isPlaying ? 'Pause' : 'Play'}</button>
|
|
362
|
+
<span>{formatTime(currentTime)} / {formatTime(duration)}</span>
|
|
363
|
+
</div>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
> **Note**: For most use cases, use the `<AudioPlayer>` component directly. Use `useAudioPlayer` only when you need a fully custom player UI.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## `useLayoutShortcuts`
|
|
373
|
+
|
|
374
|
+
Registers global keyboard shortcuts for layout control. Must be called **once** in the root layout component.
|
|
375
|
+
|
|
376
|
+
**Source**: `components/hooks/use-layout-shortcuts.ts`
|
|
377
|
+
|
|
378
|
+
### Registered Shortcuts
|
|
379
|
+
|
|
380
|
+
| Shortcut | Action |
|
|
381
|
+
|---|---|
|
|
382
|
+
| `Ctrl+B` / `Cmd+B` | Toggle sidebar |
|
|
383
|
+
|
|
384
|
+
### Usage
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { useLayoutShortcuts } from 'xertica-ui/hooks';
|
|
388
|
+
|
|
389
|
+
function AppLayout({ children }: { children: React.ReactNode }) {
|
|
390
|
+
useLayoutShortcuts(); // registers Ctrl+B for sidebar toggle
|
|
391
|
+
return <div>{children}</div>;
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
> **Important**: Call this hook only once in your application. Calling it in multiple components will register duplicate event listeners.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## `useIsMobile` / `useMobile`
|
|
400
|
+
|
|
401
|
+
Detects whether the current viewport is mobile-sized (< 768px).
|
|
402
|
+
|
|
403
|
+
**Source**: `components/shared/use-mobile.ts`
|
|
404
|
+
|
|
405
|
+
### Return Value
|
|
406
|
+
|
|
407
|
+
Returns `boolean` — `true` if the viewport width is below the mobile breakpoint.
|
|
408
|
+
|
|
409
|
+
### Usage
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
import { useIsMobile } from 'xertica-ui/hooks';
|
|
413
|
+
|
|
414
|
+
function ResponsiveComponent() {
|
|
415
|
+
const isMobile = useIsMobile();
|
|
416
|
+
return isMobile ? <MobileView /> : <DesktopView />;
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
> **Note**: Both `useIsMobile` and `useMobile` are exported and are identical. `useIsMobile` is the preferred name. See [`use-mobile.md`](./use-mobile.md) for the full reference.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## AI Rules
|
|
425
|
+
|
|
426
|
+
> [!IMPORTANT]
|
|
427
|
+
> - **Always use hooks inside providers**: All context hooks (`useTheme`, `useLanguage`, `useBrandColors`, `useAssistente`, `useApiKey`, `useLayout`) require `<XerticaProvider>` in the component tree. Calling them outside a provider throws an error.
|
|
428
|
+
> - **`useLayoutShortcuts` is singleton**: Call it exactly once in your root layout component, never in page-level or feature components.
|
|
429
|
+
> - **Prefer `<AudioPlayer>` over `useAudioPlayer`**: The headless hook is for advanced custom UIs only. For standard playback, use the component.
|
|
430
|
+
> - **`useLayout` for sidebar state**: Never manage sidebar width or expansion state manually. Always read from `useLayout()`.
|