xertica-ui 2.0.3 → 2.0.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/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 +193 -192
- package/templates/CLAUDE.md +160 -0
- package/templates/guidelines/Guidelines.md +245 -99
- package/templates/package.json +2 -2
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.4');
|
|
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
|
+
};
|
|
@@ -13,7 +13,7 @@ import { Meta, Title, Subtitle } from '@storybook/addon-docs/blocks';
|
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
15
|
// Automatically wired when using XerticaProvider:
|
|
16
|
-
import { XerticaProvider } from 'xertica-ui';
|
|
16
|
+
import { XerticaProvider } from 'xertica-ui/brand';
|
|
17
17
|
|
|
18
18
|
<XerticaProvider googleMapsApiKey={import.meta.env.VITE_GOOGLE_MAPS_KEY}>
|
|
19
19
|
<App />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
2
3
|
import { Input } from './input';
|
|
3
4
|
import { Search, Mail, Lock } from 'lucide-react';
|
|
4
5
|
import React from 'react';
|
|
@@ -116,3 +117,26 @@ export const Sizes: Story = {
|
|
|
116
117
|
</div>
|
|
117
118
|
),
|
|
118
119
|
};
|
|
120
|
+
|
|
121
|
+
export const Typing: Story = {
|
|
122
|
+
args: { placeholder: 'Type here...' },
|
|
123
|
+
play: async ({ canvasElement }) => {
|
|
124
|
+
const canvas = within(canvasElement);
|
|
125
|
+
const input = canvas.getByRole('textbox');
|
|
126
|
+
await userEvent.click(input);
|
|
127
|
+
await expect(input).toHaveFocus();
|
|
128
|
+
await userEvent.type(input, 'Hello Xertica');
|
|
129
|
+
await expect(input).toHaveValue('Hello Xertica');
|
|
130
|
+
await userEvent.clear(input);
|
|
131
|
+
await expect(input).toHaveValue('');
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const DisabledInteraction: Story = {
|
|
136
|
+
args: { disabled: true, placeholder: 'Cannot type here' },
|
|
137
|
+
play: async ({ canvasElement }) => {
|
|
138
|
+
const canvas = within(canvasElement);
|
|
139
|
+
const input = canvas.getByRole('textbox');
|
|
140
|
+
await expect(input).toBeDisabled();
|
|
141
|
+
},
|
|
142
|
+
};
|