xertica-ui 2.0.3 → 2.0.5
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 +23 -0
- package/README.md +3 -1
- package/bin/cli.ts +2 -1
- package/components/assistant/markdown-message/markdown-message.mdx +1 -1
- package/components/brand/language-selector/language-selector.mdx +1 -1
- package/components/brand/theme-toggle/theme-toggle.mdx +1 -1
- package/components/brand/xertica-logo/xertica-logo.mdx +1 -1
- package/components/brand/xertica-provider/xertica-provider.mdx +1 -1
- package/components/brand/xertica-xlogo/xertica-xlogo.mdx +1 -1
- package/components/index.ts +6 -0
- package/components/layout/header/header.mdx +1 -1
- package/components/layout/sidebar/sidebar.mdx +2 -1
- package/components/media/floating-media-wrapper.mdx +2 -2
- package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +2 -4
- package/components/pages/home-content/HomeContent.tsx +1 -1
- package/components/pages/home-content/home-content.mdx +1 -1
- package/components/pages/home-page/HomePage.stories.tsx +39 -0
- package/components/pages/home-page/HomePage.tsx +2 -3
- package/components/pages/home-page/home-page.mdx +56 -0
- package/components/pages/login-page/LoginPage.tsx +2 -5
- package/components/pages/reset-password-page/reset-password-page.mdx +6 -2
- package/components/pages/template-page/TemplatePage.stories.tsx +39 -0
- package/components/pages/template-page/TemplatePage.tsx +3 -1
- package/components/pages/template-page/template-page.mdx +54 -0
- package/components/pages/verify-email-page/VerifyEmailPage.tsx +2 -4
- package/components/ui/accordion/accordion.stories.tsx +27 -1
- package/components/ui/alert-dialog/alert-dialog.stories.tsx +30 -0
- package/components/ui/button/button.stories.tsx +23 -0
- package/components/ui/checkbox/checkbox.stories.tsx +20 -1
- package/components/ui/dialog/dialog.stories.tsx +30 -0
- package/components/ui/google-maps-loader/google-maps-loader.mdx +1 -1
- package/components/ui/input/input.stories.tsx +24 -0
- package/components/ui/switch/switch.stories.tsx +20 -1
- package/components/ui/tabs/tabs.stories.tsx +26 -1
- package/components.json +1507 -530
- package/dist/cli.js +3 -2
- package/dist/components/index.d.ts +6 -0
- package/dist/index.cjs.js +1233 -484
- package/dist/index.es.js +778 -28
- package/dist/ui.cjs.js +178 -178
- package/dist/ui.es.js +1 -1
- package/dist/use-mobile-CaENcqm-.js +4508 -0
- package/dist/use-mobile-DMOvImGQ.cjs +4542 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/decision-tree.md +287 -0
- package/guidelines/Guidelines.md +250 -657
- package/llms-compact.txt +327 -0
- package/llms.txt +160 -71
- package/package.json +192 -191
- package/templates/CLAUDE.md +160 -0
- package/templates/guidelines/Guidelines.md +245 -99
- package/templates/package.json +19 -2
- package/templates/vite.config.js +21 -5
- package/templates/vite.config.ts +20 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [2.0.5] — 2026-05-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`llms-compact.txt`** — novo arquivo de referência compacto para LLMs, sintetizando todos os componentes em formato reduzido ideal para contextos de tokens limitados.
|
|
15
|
+
- **`docs/decision-tree.md`** — guia de árvore de decisão para agentes de IA selecionarem o componente correto com base nos requisitos da UI.
|
|
16
|
+
- **`templates/CLAUDE.md`** — `CLAUDE.md` agora é scaffolded automaticamente em projetos criados via `npx xertica-ui@latest init`, fornecendo contexto arquitetural (FSD/FDA, subpath imports, tokens semânticos) para Claude Code e outros assistentes de IA.
|
|
17
|
+
- **Storybook stories** — adicionadas stories interativas para Accordion, AlertDialog, Button, Checkbox, Dialog, Input, Switch, Tabs, HomePage e TemplatePage.
|
|
18
|
+
- **`components/pages/home-page/home-page.mdx`** e **`template-page.mdx`** — novas entradas de documentação MDX para as page components.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`guidelines/Guidelines.md`** — reescrita completa do guia de arquitetura FSD/FDA: maior detalhamento de responsabilidades por camada, convenções de import e fluxo de adição de novas rotas.
|
|
23
|
+
- **`components.json`** — atualização completa do registro de componentes, cobrindo todos os 97 componentes com metadados de subpath, props e variantes.
|
|
24
|
+
- **`llms.txt`** — seção de subpath imports e mapeamento de camadas FSD/FDA atualizados.
|
|
25
|
+
- **`vite.config.ts`** — ajustes no build multi-entry para garantir correta emissão dos arquivos CJS/ESM.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **`npx xertica-ui init` usa versão em cache** — documentado que o npx armazena pacotes em cache localmente sem TTL curto. Sempre use `npx xertica-ui@latest init` para garantir a versão mais recente. O `templates/CLAUDE.md` scaffolded no projeto gerado inclui essa orientação.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
10
33
|
## [2.0.3] — 2026-05-11
|
|
11
34
|
|
|
12
35
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Enterprise-grade React design system** built on Tailwind CSS v4, Radix UI, and Lucide Icons — with a robust AI-first documentation layer for precise LLM-driven composition and autonomous agent interaction.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/xertica-ui)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -31,6 +31,8 @@ npm run dev
|
|
|
31
31
|
|
|
32
32
|
The CLI scaffolds a complete Vite + React + TypeScript project with Portuguese UI localization and English AI-ready documentation.
|
|
33
33
|
|
|
34
|
+
> **Note:** Always use `@latest` with npx. Without it, npx may execute a locally cached older version instead of fetching the latest from the registry.
|
|
35
|
+
|
|
34
36
|
---
|
|
35
37
|
|
|
36
38
|
## 📦 Installation as a Library
|
package/bin/cli.ts
CHANGED
|
@@ -18,7 +18,7 @@ const program = new Command();
|
|
|
18
18
|
program
|
|
19
19
|
.name('xertica-ui')
|
|
20
20
|
.description('CLI to initialize Xertica UI projects')
|
|
21
|
-
.version('2.0.
|
|
21
|
+
.version('2.0.5');
|
|
22
22
|
|
|
23
23
|
program
|
|
24
24
|
.command('init')
|
|
@@ -83,6 +83,7 @@ program
|
|
|
83
83
|
'eslint.config.js',
|
|
84
84
|
'.env.example',
|
|
85
85
|
'guidelines',
|
|
86
|
+
'CLAUDE.md',
|
|
86
87
|
];
|
|
87
88
|
|
|
88
89
|
for (const file of rootFilesToCopy) {
|
|
@@ -15,7 +15,7 @@ import * as MarkdownMessageStories from './markdown-message.stories';
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
17
|
```tsx
|
|
18
|
-
import { MarkdownMessage } from 'xertica-ui';
|
|
18
|
+
import { MarkdownMessage } from 'xertica-ui/assistant';
|
|
19
19
|
|
|
20
20
|
<MarkdownMessage content="**Hello!** Here is a `code` example." />
|
|
21
21
|
```
|
|
@@ -12,7 +12,7 @@ import { Meta, Title, Subtitle, Description } from '@storybook/addon-docs/blocks
|
|
|
12
12
|
`XerticaLogo` renders the complete Xertica brand identity — the X symbol alongside the wordmark text. It adapts to different backgrounds through its `variant` prop and supports a direct `color` override for edge cases.
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
|
-
import { XerticaLogo } from 'xertica-ui';
|
|
15
|
+
import { XerticaLogo } from 'xertica-ui/brand';
|
|
16
16
|
|
|
17
17
|
<XerticaLogo className="h-8 w-auto" />
|
|
18
18
|
```
|
|
@@ -14,7 +14,7 @@ The `XerticaProvider` is a lightweight root wrapper that orchestrates essential
|
|
|
14
14
|
**It MUST be placed at the root of your application.**
|
|
15
15
|
|
|
16
16
|
```tsx
|
|
17
|
-
import { XerticaProvider } from 'xertica-ui';
|
|
17
|
+
import { XerticaProvider } from 'xertica-ui/brand';
|
|
18
18
|
import 'xertica-ui/style.css'; // Don't forget the global styles
|
|
19
19
|
|
|
20
20
|
function App() {
|
|
@@ -12,7 +12,7 @@ import { Meta, Title, Subtitle } from '@storybook/addon-docs/blocks';
|
|
|
12
12
|
`XerticaXLogo` renders only the X-symbol portion of the Xertica identity. It shares the same prop API as `XerticaLogo` and is the preferred choice wherever horizontal space is constrained.
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
|
-
import { XerticaXLogo } from 'xertica-ui';
|
|
15
|
+
import { XerticaXLogo } from 'xertica-ui/brand';
|
|
16
16
|
|
|
17
17
|
<XerticaXLogo className="h-8 w-8" />
|
|
18
18
|
```
|
package/components/index.ts
CHANGED
|
@@ -34,6 +34,12 @@ export type {
|
|
|
34
34
|
// ============================================================================
|
|
35
35
|
|
|
36
36
|
export { TemplatePage } from './pages/template-page';
|
|
37
|
+
export { HomePage } from './pages/home-page';
|
|
38
|
+
export { LoginPage } from './pages/login-page';
|
|
39
|
+
export { HomeContent } from './pages/home-content';
|
|
40
|
+
export { ForgotPasswordPage } from './pages/forgot-password-page';
|
|
41
|
+
export { VerifyEmailPage } from './pages/verify-email-page';
|
|
42
|
+
export { ResetPasswordPage } from './pages/reset-password-page';
|
|
37
43
|
|
|
38
44
|
// UI Components - All available via /components/ui/index.ts
|
|
39
45
|
export * from './ui';
|
|
@@ -94,7 +94,7 @@ The `Header` ensures a navigation area is **never empty**:
|
|
|
94
94
|
|
|
95
95
|
### Developer Integration Guide
|
|
96
96
|
```tsx
|
|
97
|
-
import { Header } from 'xertica-ui';
|
|
97
|
+
import { Header } from 'xertica-ui/layout';
|
|
98
98
|
import { Home, Settings, Users } from 'lucide-react';
|
|
99
99
|
|
|
100
100
|
export function MyLayout({ children }) {
|
|
@@ -76,7 +76,8 @@ The expansion state is automatically persisted in a cookie (`sidebar_state`), en
|
|
|
76
76
|
The Sidebar should ideally be synchronized with the `useLayout()` hook to ensure the main application container adjusts its padding accordingly.
|
|
77
77
|
|
|
78
78
|
```tsx
|
|
79
|
-
import { Sidebar
|
|
79
|
+
import { Sidebar } from 'xertica-ui/layout';
|
|
80
|
+
import { useLayout } from 'xertica-ui/hooks';
|
|
80
81
|
|
|
81
82
|
const { sidebarExpanded, toggleSidebar, sidebarWidth } = useLayout();
|
|
82
83
|
|
|
@@ -17,8 +17,8 @@ import { Meta, Title, Subtitle } from '@storybook/addon-docs/blocks';
|
|
|
17
17
|
Position and size are persisted to `localStorage` per `playerId`, so the user's floating window survives page navigation.
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
|
-
import { FloatingMediaWrapper } from 'xertica-ui';
|
|
21
|
-
import { VideoPlayer } from 'xertica-ui';
|
|
20
|
+
import { FloatingMediaWrapper } from 'xertica-ui/media';
|
|
21
|
+
import { VideoPlayer } from 'xertica-ui/media';
|
|
22
22
|
|
|
23
23
|
function MyPage() {
|
|
24
24
|
const [isFloating, setIsFloating] = React.useState(false);
|
|
@@ -38,10 +38,8 @@ export function ForgotPasswordPage() {
|
|
|
38
38
|
setIsLoading(false);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const handleSocialLogin = (
|
|
42
|
-
//
|
|
43
|
-
console.log(`Login with ${provider}`);
|
|
44
|
-
// Here real integration with each provider would be implemented
|
|
41
|
+
const handleSocialLogin = (_provider: string) => {
|
|
42
|
+
// Wire your SSO/social provider integration here
|
|
45
43
|
};
|
|
46
44
|
|
|
47
45
|
return (
|
|
@@ -63,7 +63,7 @@ export function HomeContent({ user, onLogout, onSettings }: HomeContentProps) {
|
|
|
63
63
|
style={{
|
|
64
64
|
paddingLeft: sidebarExpanded ? `${sidebarWidth}px` : SIDEBAR_COLLAPSED_WIDTH
|
|
65
65
|
}}
|
|
66
|
-
className=
|
|
66
|
+
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
|
67
67
|
>
|
|
68
68
|
<Header
|
|
69
69
|
showThemeToggle={true}
|
|
@@ -60,4 +60,4 @@ import { HomeContent } from 'xertica-ui';
|
|
|
60
60
|
> - **Context requirement** — `HomeContent` calls `useLayout()` at the top level. It must always be a descendant of `XerticaProvider` (or a bare `LayoutProvider`).
|
|
61
61
|
> - **Feature cards** — The card grid is currently hardcoded to the "Template CLI" feature. To add cards, fork the component and extend the `grid` section.
|
|
62
62
|
> - **Breadcrumbs** — Breadcrumb labels are resolved via a local `labelTranslations` map and `getRouteByPath`. Ensure your route definitions include a `label` field for correct breadcrumb rendering.
|
|
63
|
-
> - **Background color** — The outer container
|
|
63
|
+
> - **Background color** — The outer container uses `bg-muted` from the design system tokens. Override with `className` if a different background is needed.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { HomePage } from './HomePage';
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
4
|
+
import { LayoutProvider } from '../../../contexts/LayoutContext';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof HomePage> = {
|
|
8
|
+
title: 'Pages/HomePage',
|
|
9
|
+
component: HomePage,
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<MemoryRouter initialEntries={['/home']}>
|
|
13
|
+
<LayoutProvider>
|
|
14
|
+
<Story />
|
|
15
|
+
</LayoutProvider>
|
|
16
|
+
</MemoryRouter>
|
|
17
|
+
),
|
|
18
|
+
],
|
|
19
|
+
parameters: {
|
|
20
|
+
layout: 'fullscreen',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof HomePage>;
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
user: { email: 'ariel@xertica.com' },
|
|
30
|
+
onLogout: () => {},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const NoUser: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
user: null,
|
|
37
|
+
onLogout: () => {},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -78,9 +78,8 @@ export function HomePage({ user, onLogout }: HomePageProps) {
|
|
|
78
78
|
responseGenerator={gerarResposta}
|
|
79
79
|
richSuggestions={richSuggestions}
|
|
80
80
|
feedbackOptions={feedbackOptions}
|
|
81
|
-
onEvaluation={(
|
|
82
|
-
|
|
83
|
-
// Here you would implement logic to save to the backend
|
|
81
|
+
onEvaluation={() => {
|
|
82
|
+
// Wire your feedback persistence logic here
|
|
84
83
|
}}
|
|
85
84
|
/>
|
|
86
85
|
</div>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Meta, Title, Subtitle } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as HomePageStories from './HomePage.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={HomePageStories} />
|
|
5
|
+
|
|
6
|
+
<Title>HomePage</Title>
|
|
7
|
+
<Subtitle>Full application shell — Sidebar, HomeContent, and XerticaAssistant composed together.</Subtitle>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`HomePage` is the root authenticated page. It assembles the three major layout pillars:
|
|
14
|
+
|
|
15
|
+
- **Sidebar** — primary navigation, collapsible, synced via `LayoutContext`.
|
|
16
|
+
- **HomeContent** — dashboard area with breadcrumb header and feature card grid.
|
|
17
|
+
- **XerticaAssistant** — floating AI assistant panel, toggled via `LayoutContext`.
|
|
18
|
+
|
|
19
|
+
All three components communicate through `LayoutContext` — sidebar width, expanded state, and assistant visibility are synchronized automatically.
|
|
20
|
+
|
|
21
|
+
**Requires:** `LayoutContext` (provided by `XerticaProvider`), React Router v6.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Required | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `user` | `{ email: string } \| null` | Yes | Currently authenticated user. Passed to Sidebar and HomeContent for display. |
|
|
30
|
+
| `onLogout` | `() => void` | Yes | Called when the user triggers logout from the Sidebar or Header. |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Composition
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { HomePage } from 'xertica-ui';
|
|
38
|
+
|
|
39
|
+
// Inside your router:
|
|
40
|
+
<Route
|
|
41
|
+
path="/home"
|
|
42
|
+
element={<HomePage user={currentUser} onLogout={handleLogout} />}
|
|
43
|
+
/>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`XerticaProvider` must wrap the app root — it initializes `LayoutContext` which all three pillars depend on.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## AI Best Practices
|
|
51
|
+
|
|
52
|
+
> [!IMPORTANT]
|
|
53
|
+
> - **Top-level only** — `HomePage` is a full-screen layout component. Do not nest it inside other layouts.
|
|
54
|
+
> - **No logic here** — Keep business logic in `HomeContent` or lower-level components. `HomePage` is a composition shell only.
|
|
55
|
+
> - **LayoutContext required** — All child components (`Sidebar`, `HomeContent`, `XerticaAssistant`) call `useLayout()`. This component must always be a descendant of `XerticaProvider`.
|
|
56
|
+
> - **Extend via HomeContent** — To add dashboard sections, edit `HomeContent` — not `HomePage`.
|
|
@@ -56,11 +56,8 @@ export function LoginPage({ onLogin }: LoginPageProps) {
|
|
|
56
56
|
setIsLoading(false);
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
const handleSocialLogin = (
|
|
60
|
-
//
|
|
61
|
-
console.log(`Login with ${provider}`);
|
|
62
|
-
// Here real integration with each provider would be implemented
|
|
63
|
-
// For now, simulate a successful login
|
|
59
|
+
const handleSocialLogin = (_provider: string) => {
|
|
60
|
+
// Wire your SSO/social provider integration here
|
|
64
61
|
onLogin('social@user.com', 'social-auth');
|
|
65
62
|
};
|
|
66
63
|
|
|
@@ -36,11 +36,15 @@ import { ResetPasswordPage } from 'xertica-ui';
|
|
|
36
36
|
|
|
37
37
|
## Password Strength Levels
|
|
38
38
|
|
|
39
|
+
The strength meter is based purely on length:
|
|
40
|
+
|
|
39
41
|
| Level | Criteria |
|
|
40
42
|
|---|---|
|
|
41
43
|
| `weak` | Fewer than 6 characters |
|
|
42
|
-
| `medium` | 6
|
|
43
|
-
| `strong` |
|
|
44
|
+
| `medium` | 6–9 characters |
|
|
45
|
+
| `strong` | 10 or more characters |
|
|
46
|
+
|
|
47
|
+
The meter is visual-only. Enforce your own policy server-side.
|
|
44
48
|
|
|
45
49
|
---
|
|
46
50
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { TemplatePage } from './TemplatePage';
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
4
|
+
import { LayoutProvider } from '../../../contexts/LayoutContext';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof TemplatePage> = {
|
|
8
|
+
title: 'Pages/TemplatePage',
|
|
9
|
+
component: TemplatePage,
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<MemoryRouter initialEntries={['/template']}>
|
|
13
|
+
<LayoutProvider>
|
|
14
|
+
<Story />
|
|
15
|
+
</LayoutProvider>
|
|
16
|
+
</MemoryRouter>
|
|
17
|
+
),
|
|
18
|
+
],
|
|
19
|
+
parameters: {
|
|
20
|
+
layout: 'fullscreen',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof TemplatePage>;
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
user: { email: 'ariel@xertica.com' },
|
|
30
|
+
onLogout: () => {},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const NoUser: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
user: null,
|
|
37
|
+
onLogout: () => {},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -57,7 +57,9 @@ export function TemplatePage({ user, onLogout }: TemplatePageProps) {
|
|
|
57
57
|
<XerticaAssistant
|
|
58
58
|
isExpanded={assistenteExpanded}
|
|
59
59
|
onToggle={toggleAssistente}
|
|
60
|
-
onEvaluation={(
|
|
60
|
+
onEvaluation={() => {
|
|
61
|
+
// Wire your feedback persistence logic here
|
|
62
|
+
}}
|
|
61
63
|
/>
|
|
62
64
|
</div>
|
|
63
65
|
);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Meta, Title, Subtitle } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as TemplatePageStories from './TemplatePage.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={TemplatePageStories} />
|
|
5
|
+
|
|
6
|
+
<Title>TemplatePage</Title>
|
|
7
|
+
<Subtitle>Full application shell with component kitchen-sink — the canonical layout reference for Xertica UI.</Subtitle>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`TemplatePage` is the reference implementation of the full application layout. It assembles:
|
|
14
|
+
|
|
15
|
+
- **Sidebar** — primary navigation, synchronized via `LayoutContext`.
|
|
16
|
+
- **TemplateContent** — kitchen-sink showcase of all Xertica UI components (forms, tables, charts, dialogs, etc.).
|
|
17
|
+
- **XerticaAssistant** — floating AI assistant panel.
|
|
18
|
+
|
|
19
|
+
Use this page as the canonical reference when building new authenticated layouts. It demonstrates correct usage of `useLayout()`, sidebar synchronization, and assistant integration.
|
|
20
|
+
|
|
21
|
+
**Requires:** `LayoutContext` (provided by `XerticaProvider`), React Router v6.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Required | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `user` | `{ email: string } \| null` | Yes | Currently authenticated user. Passed to Sidebar for avatar and user menu display. |
|
|
30
|
+
| `onLogout` | `() => void` | Yes | Called when the user triggers logout from the Sidebar. |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Composition
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { TemplatePage } from 'xertica-ui';
|
|
38
|
+
|
|
39
|
+
// Inside your router:
|
|
40
|
+
<Route
|
|
41
|
+
path="/template"
|
|
42
|
+
element={<TemplatePage user={currentUser} onLogout={handleLogout} />}
|
|
43
|
+
/>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## AI Best Practices
|
|
49
|
+
|
|
50
|
+
> [!IMPORTANT]
|
|
51
|
+
> - **Reference implementation** — Use `TemplatePage` as the primary reference for assembling complex layouts. Copy its structure when creating new pages.
|
|
52
|
+
> - **Top-level only** — This is a full-screen layout component. Do not nest it inside other layouts.
|
|
53
|
+
> - **Extend via TemplateContent** — To change the showcased components, edit `TemplateContent`. `TemplatePage` itself is a composition shell only.
|
|
54
|
+
> - **LayoutContext required** — All child components call `useLayout()`. Always ensure `XerticaProvider` wraps the app root.
|
|
@@ -40,10 +40,8 @@ export function VerifyEmailPage() {
|
|
|
40
40
|
setTimeout(() => setResendSuccess(false), 3000);
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const handleSocialLogin = (
|
|
44
|
-
//
|
|
45
|
-
console.log(`Login with ${provider}`);
|
|
46
|
-
// Here real integration with each provider would be implemented
|
|
43
|
+
const handleSocialLogin = (_provider: string) => {
|
|
44
|
+
// Wire your SSO/social provider integration here
|
|
47
45
|
};
|
|
48
46
|
|
|
49
47
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
2
3
|
import {
|
|
3
4
|
Accordion,
|
|
4
5
|
AccordionContent,
|
|
@@ -73,4 +74,29 @@ export const Multiple: Story = {
|
|
|
73
74
|
</AccordionItem>
|
|
74
75
|
</Accordion>
|
|
75
76
|
),
|
|
76
|
-
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const ExpandCollapse: Story = {
|
|
80
|
+
args: { type: 'single', collapsible: true },
|
|
81
|
+
render: (args) => (
|
|
82
|
+
<Accordion {...args} className="w-full max-w-[500px]">
|
|
83
|
+
<AccordionItem value="item-1">
|
|
84
|
+
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
|
85
|
+
<AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
|
|
86
|
+
</AccordionItem>
|
|
87
|
+
<AccordionItem value="item-2">
|
|
88
|
+
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
|
89
|
+
<AccordionContent>Yes. It is animated by default.</AccordionContent>
|
|
90
|
+
</AccordionItem>
|
|
91
|
+
</Accordion>
|
|
92
|
+
),
|
|
93
|
+
play: async ({ canvasElement }) => {
|
|
94
|
+
const canvas = within(canvasElement);
|
|
95
|
+
const triggers = canvas.getAllByRole('button');
|
|
96
|
+
await expect(triggers[0]).toHaveAttribute('aria-expanded', 'false');
|
|
97
|
+
await userEvent.click(triggers[0]);
|
|
98
|
+
await expect(triggers[0]).toHaveAttribute('aria-expanded', 'true');
|
|
99
|
+
await userEvent.click(triggers[0]);
|
|
100
|
+
await expect(triggers[0]).toHaveAttribute('aria-expanded', 'false');
|
|
101
|
+
},
|
|
102
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
2
3
|
import {
|
|
3
4
|
AlertDialog,
|
|
4
5
|
AlertDialogAction,
|
|
@@ -51,3 +52,32 @@ export const Default: Story = {
|
|
|
51
52
|
</AlertDialog>
|
|
52
53
|
),
|
|
53
54
|
};
|
|
55
|
+
|
|
56
|
+
export const ConfirmAction: Story = {
|
|
57
|
+
render: () => (
|
|
58
|
+
<AlertDialog>
|
|
59
|
+
<AlertDialogTrigger asChild>
|
|
60
|
+
<Button variant="destructive">Delete</Button>
|
|
61
|
+
</AlertDialogTrigger>
|
|
62
|
+
<AlertDialogContent>
|
|
63
|
+
<AlertDialogHeader>
|
|
64
|
+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
65
|
+
<AlertDialogDescription>This cannot be undone.</AlertDialogDescription>
|
|
66
|
+
</AlertDialogHeader>
|
|
67
|
+
<AlertDialogFooter>
|
|
68
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
69
|
+
<AlertDialogAction>Delete</AlertDialogAction>
|
|
70
|
+
</AlertDialogFooter>
|
|
71
|
+
</AlertDialogContent>
|
|
72
|
+
</AlertDialog>
|
|
73
|
+
),
|
|
74
|
+
play: async ({ canvasElement }) => {
|
|
75
|
+
const canvas = within(canvasElement);
|
|
76
|
+
// Only test the trigger — AlertDialog content renders in a portal outside canvasElement
|
|
77
|
+
const trigger = canvas.getByRole('button', { name: 'Delete' });
|
|
78
|
+
await expect(trigger).toBeInTheDocument();
|
|
79
|
+
await expect(trigger).not.toBeDisabled();
|
|
80
|
+
await userEvent.click(trigger);
|
|
81
|
+
await expect(trigger).toBeInTheDocument();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
2
3
|
import { Button } from './button';
|
|
3
4
|
import { Plus, Trash2, Settings, ArrowRight } from 'lucide-react';
|
|
4
5
|
import React from 'react';
|
|
@@ -135,3 +136,25 @@ export const Loading: Story = {
|
|
|
135
136
|
</Button>
|
|
136
137
|
),
|
|
137
138
|
};
|
|
139
|
+
|
|
140
|
+
export const Clickable: Story = {
|
|
141
|
+
args: { children: 'Click me' },
|
|
142
|
+
play: async ({ canvasElement }) => {
|
|
143
|
+
const canvas = within(canvasElement);
|
|
144
|
+
const button = canvas.getByRole('button', { name: 'Click me' });
|
|
145
|
+
await expect(button).toBeInTheDocument();
|
|
146
|
+
await expect(button).not.toBeDisabled();
|
|
147
|
+
await userEvent.click(button);
|
|
148
|
+
await expect(button).toBeInTheDocument();
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const DisabledState: Story = {
|
|
153
|
+
args: { children: 'Disabled', disabled: true },
|
|
154
|
+
play: async ({ canvasElement }) => {
|
|
155
|
+
const canvas = within(canvasElement);
|
|
156
|
+
const button = canvas.getByRole('button', { name: 'Disabled' });
|
|
157
|
+
await expect(button).toBeDisabled();
|
|
158
|
+
await expect(button).toHaveAttribute('disabled');
|
|
159
|
+
},
|
|
160
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
2
3
|
import { Checkbox } from './checkbox';
|
|
3
4
|
import { Label } from '../label';
|
|
4
5
|
import React from 'react';
|
|
@@ -80,4 +81,22 @@ export const Sizes: Story = {
|
|
|
80
81
|
</div>
|
|
81
82
|
</div>
|
|
82
83
|
),
|
|
83
|
-
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const Toggle: Story = {
|
|
87
|
+
render: () => (
|
|
88
|
+
<div className="flex items-center space-x-2">
|
|
89
|
+
<Checkbox id="play-check" />
|
|
90
|
+
<Label htmlFor="play-check">Accept terms</Label>
|
|
91
|
+
</div>
|
|
92
|
+
),
|
|
93
|
+
play: async ({ canvasElement }) => {
|
|
94
|
+
const canvas = within(canvasElement);
|
|
95
|
+
const checkbox = canvas.getByRole('checkbox');
|
|
96
|
+
await expect(checkbox).not.toBeChecked();
|
|
97
|
+
await userEvent.click(checkbox);
|
|
98
|
+
await expect(checkbox).toBeChecked();
|
|
99
|
+
await userEvent.click(checkbox);
|
|
100
|
+
await expect(checkbox).not.toBeChecked();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
2
3
|
import {
|
|
3
4
|
Dialog,
|
|
4
5
|
DialogBody,
|
|
@@ -141,3 +142,32 @@ export const WithScrollableBody: Story = {
|
|
|
141
142
|
</Dialog>
|
|
142
143
|
),
|
|
143
144
|
};
|
|
145
|
+
|
|
146
|
+
export const OpenClose: Story = {
|
|
147
|
+
render: () => (
|
|
148
|
+
<Dialog>
|
|
149
|
+
<DialogTrigger asChild>
|
|
150
|
+
<Button variant="outline">Open Dialog</Button>
|
|
151
|
+
</DialogTrigger>
|
|
152
|
+
<DialogContent>
|
|
153
|
+
<DialogHeader>
|
|
154
|
+
<DialogTitle>Confirm action</DialogTitle>
|
|
155
|
+
<DialogDescription>Are you sure you want to proceed?</DialogDescription>
|
|
156
|
+
</DialogHeader>
|
|
157
|
+
<DialogFooter>
|
|
158
|
+
<Button type="submit">Confirm</Button>
|
|
159
|
+
</DialogFooter>
|
|
160
|
+
</DialogContent>
|
|
161
|
+
</Dialog>
|
|
162
|
+
),
|
|
163
|
+
play: async ({ canvasElement }) => {
|
|
164
|
+
const canvas = within(canvasElement);
|
|
165
|
+
// Only test the trigger — Dialog content renders in a portal outside canvasElement
|
|
166
|
+
const trigger = canvas.getByRole('button', { name: 'Open Dialog' });
|
|
167
|
+
await expect(trigger).toBeInTheDocument();
|
|
168
|
+
await expect(trigger).not.toBeDisabled();
|
|
169
|
+
await userEvent.click(trigger);
|
|
170
|
+
// Verify trigger received the click (dialog state managed by Radix portal)
|
|
171
|
+
await expect(trigger).toBeInTheDocument();
|
|
172
|
+
},
|
|
173
|
+
};
|