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.
Files changed (50) hide show
  1. package/bin/cli.ts +2 -1
  2. package/components/assistant/markdown-message/markdown-message.mdx +1 -1
  3. package/components/brand/language-selector/language-selector.mdx +1 -1
  4. package/components/brand/theme-toggle/theme-toggle.mdx +1 -1
  5. package/components/brand/xertica-logo/xertica-logo.mdx +1 -1
  6. package/components/brand/xertica-provider/xertica-provider.mdx +1 -1
  7. package/components/brand/xertica-xlogo/xertica-xlogo.mdx +1 -1
  8. package/components/index.ts +6 -0
  9. package/components/layout/header/header.mdx +1 -1
  10. package/components/layout/sidebar/sidebar.mdx +2 -1
  11. package/components/media/floating-media-wrapper.mdx +2 -2
  12. package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +2 -4
  13. package/components/pages/home-content/HomeContent.tsx +1 -1
  14. package/components/pages/home-content/home-content.mdx +1 -1
  15. package/components/pages/home-page/HomePage.stories.tsx +39 -0
  16. package/components/pages/home-page/HomePage.tsx +2 -3
  17. package/components/pages/home-page/home-page.mdx +56 -0
  18. package/components/pages/login-page/LoginPage.tsx +2 -5
  19. package/components/pages/reset-password-page/reset-password-page.mdx +6 -2
  20. package/components/pages/template-page/TemplatePage.stories.tsx +39 -0
  21. package/components/pages/template-page/TemplatePage.tsx +3 -1
  22. package/components/pages/template-page/template-page.mdx +54 -0
  23. package/components/pages/verify-email-page/VerifyEmailPage.tsx +2 -4
  24. package/components/ui/accordion/accordion.stories.tsx +27 -1
  25. package/components/ui/alert-dialog/alert-dialog.stories.tsx +30 -0
  26. package/components/ui/button/button.stories.tsx +23 -0
  27. package/components/ui/checkbox/checkbox.stories.tsx +20 -1
  28. package/components/ui/dialog/dialog.stories.tsx +30 -0
  29. package/components/ui/google-maps-loader/google-maps-loader.mdx +1 -1
  30. package/components/ui/input/input.stories.tsx +24 -0
  31. package/components/ui/switch/switch.stories.tsx +20 -1
  32. package/components/ui/tabs/tabs.stories.tsx +26 -1
  33. package/components.json +1507 -530
  34. package/dist/cli.js +3 -2
  35. package/dist/components/index.d.ts +6 -0
  36. package/dist/index.cjs.js +1233 -484
  37. package/dist/index.es.js +778 -28
  38. package/dist/ui.cjs.js +178 -178
  39. package/dist/ui.es.js +1 -1
  40. package/dist/use-mobile-CaENcqm-.js +4508 -0
  41. package/dist/use-mobile-DMOvImGQ.cjs +4542 -0
  42. package/dist/xertica-ui.css +1 -1
  43. package/docs/decision-tree.md +287 -0
  44. package/guidelines/Guidelines.md +250 -657
  45. package/llms-compact.txt +327 -0
  46. package/llms.txt +160 -71
  47. package/package.json +193 -192
  48. package/templates/CLAUDE.md +160 -0
  49. package/templates/guidelines/Guidelines.md +245 -99
  50. 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.3');
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
  ```
@@ -15,7 +15,7 @@ import * as LanguageSelectorStories from './language-selector.stories';
15
15
  ## Usage
16
16
 
17
17
  ```tsx
18
- import { LanguageSelector } from 'xertica-ui';
18
+ import { LanguageSelector } from 'xertica-ui/brand';
19
19
 
20
20
  <LanguageSelector />
21
21
  ```
@@ -15,7 +15,7 @@ import * as ThemeToggleStories from './theme-toggle.stories';
15
15
  ## Usage
16
16
 
17
17
  ```tsx
18
- import { ThemeToggle } from 'xertica-ui';
18
+ import { ThemeToggle } from 'xertica-ui/brand';
19
19
 
20
20
  <ThemeToggle />
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
  ```
@@ -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, useLayout } from 'xertica-ui';
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 = (provider: string) => {
42
- // Simulate social/SSO login
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={`flex-1 flex flex-col overflow-hidden transition-all duration-300 bg-red-500`}
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 currently uses `bg-red-500` as a development placeholder. Override it with `className` or remove it before shipping.
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={(messageId, type, reason) => {
82
- console.log(`Evaluation received: ${type} on message ${messageId}. Reason: ${reason}`);
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 = (provider: string) => {
60
- // Simulate social/SSO login
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+ characters with mixed casing or numbers |
43
- | `strong` | 8+ characters with uppercase, lowercase, numbers, and symbols |
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={(id, type, reason) => console.log('Feedback:', id, type, reason)}
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 = (provider: string) => {
44
- // Simulate social/SSO login
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
+ };