agnosticui-cli 2.0.0-alpha.21 → 2.0.0-alpha.22

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.
@@ -2,9 +2,15 @@
2
2
  // Component classification sets
3
3
  // ---------------------------------------------------------------------------
4
4
  const TEXT_CHILD_COMPONENTS = new Set([
5
- 'Alert', 'Badge', 'BadgeFx', 'Button', 'ButtonFx', 'Card',
5
+ 'Alert', 'Badge', 'Button', 'Card',
6
6
  'Kbd', 'Link', 'Mark', 'MessageBubble', 'Tag',
7
7
  ]);
8
+ // Components with non-standard Vue file naming (exported via index.ts)
9
+ const VUE_INDEX_EXPORTS = {
10
+ Timeline: 'VueTimeline',
11
+ };
12
+ // Components that have Fx animation props
13
+ const FX_COMPONENTS = new Set(['BadgeFx', 'ButtonFx', 'IconButtonFx']);
8
14
  // Components with an `open` prop that are invisible until triggered
9
15
  const OPEN_CONTROLLED_COMPONENTS = new Set([
10
16
  'Dialog', 'Drawer', 'Toast', 'Collapsible',
@@ -14,6 +20,15 @@ const REQUIRED_ARRAY_COMPONENTS = new Set([
14
20
  'Combobox',
15
21
  ]);
16
22
  // ---------------------------------------------------------------------------
23
+ // Shared header prepended to every auto-generated file
24
+ // ---------------------------------------------------------------------------
25
+ const STORY_FILE_HEADER = `// ================================================================
26
+ // AUTO-GENERATED by agnosticui-cli: do not edit by hand.
27
+ // Run: npx agnosticui-cli storybook --force to regenerate
28
+ // To keep customizations: copy to <ComponentName>.stories.custom.*
29
+ // ================================================================
30
+ `;
31
+ // ---------------------------------------------------------------------------
17
32
  // Storybook config generators
18
33
  // ---------------------------------------------------------------------------
19
34
  export function generateStorybookMain(framework) {
@@ -31,7 +46,22 @@ export function generateStorybookMain(framework) {
31
46
  frameworkPkg = '@storybook/web-components-vite';
32
47
  storybookImportType = '@storybook/web-components-vite';
33
48
  }
34
- return `import type { StorybookConfig } from '${storybookImportType}';
49
+ // Vue: pass isCustomElement via framework.options so ag-* tags aren't treated as unresolved Vue components
50
+ const frameworkConfig = framework === 'vue'
51
+ ? `{
52
+ name: '${frameworkPkg}',
53
+ options: {
54
+ vuePluginOptions: {
55
+ template: {
56
+ compilerOptions: {
57
+ isCustomElement: (tag: string) => tag.startsWith('ag-'),
58
+ },
59
+ },
60
+ },
61
+ },
62
+ }`
63
+ : `'${frameworkPkg}'`;
64
+ return STORY_FILE_HEADER + `import type { StorybookConfig } from '${storybookImportType}';
35
65
  import type { InlineConfig } from 'vite';
36
66
 
37
67
  const config: StorybookConfig = {
@@ -43,7 +73,7 @@ const config: StorybookConfig = {
43
73
  '@storybook/addon-docs',
44
74
  '@storybook/addon-a11y',
45
75
  ],
46
- framework: '${frameworkPkg}',
76
+ framework: ${frameworkConfig},
47
77
  async viteFinal(config): Promise<InlineConfig> {
48
78
  // Remove noDiscovery so Storybook can pre-bundle its own dependencies.
49
79
  // The main app uses noDiscovery for performance, but Storybook needs discovery on.
@@ -70,7 +100,7 @@ export function generateStorybookPreview(framework, componentsRelPath) {
70
100
  // componentsRelPath is relative from .storybook/ to src/components/ag
71
101
  // The CSS imports in preview.ts are relative to .storybook/
72
102
  const cssPath = `${componentsRelPath}/styles`;
73
- return `import type { Preview } from '${previewImportPkg}';
103
+ return STORY_FILE_HEADER + `import type { Preview } from '${previewImportPkg}';
74
104
  import '${cssPath}/ag-tokens.css';
75
105
  import '${cssPath}/ag-tokens-dark.css';
76
106
 
@@ -91,6 +121,19 @@ const preview: Preview = {
91
121
  export default preview;
92
122
  `;
93
123
  }
124
+ export function generateStorybookManager() {
125
+ return STORY_FILE_HEADER + `import { addons } from 'storybook/manager-api';
126
+ import { create } from 'storybook/theming/create';
127
+
128
+ const theme = create({
129
+ base: 'light',
130
+ brandTitle: 'AgnosticUI: Auto-generated stories (run: ag storybook --force to regenerate)',
131
+ brandUrl: 'https://github.com/AgnosticUI/agnosticui',
132
+ });
133
+
134
+ addons.setConfig({ theme });
135
+ `;
136
+ }
94
137
  // ---------------------------------------------------------------------------
95
138
  // argTypes registry
96
139
  // ---------------------------------------------------------------------------
@@ -130,6 +173,45 @@ const ARGTYPES = {
130
173
  },
131
174
  dot: { control: 'boolean' },
132
175
  interactive: { control: 'boolean' },
176
+ },`,
177
+ BadgeFx: ` argTypes: {
178
+ fx: {
179
+ control: 'select',
180
+ options: ['bounce', 'pulse', 'jelly'],
181
+ },
182
+ fxSpeed: {
183
+ control: 'select',
184
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
185
+ },
186
+ fxEase: {
187
+ control: 'select',
188
+ options: ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'bounce', 'spring-sm', 'spring-md', 'spring-lg'],
189
+ },
190
+ fxDisabled: { control: 'boolean' },
191
+ },`,
192
+ ButtonFx: ` argTypes: {
193
+ fx: {
194
+ control: 'select',
195
+ options: ['bounce', 'pulse', 'jelly', 'shake', 'pulse-wobble'],
196
+ },
197
+ fxSpeed: {
198
+ control: 'select',
199
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
200
+ },
201
+ fxEase: {
202
+ control: 'select',
203
+ options: ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'bounce', 'spring-sm', 'spring-md', 'spring-lg'],
204
+ },
205
+ fxDisabled: { control: 'boolean' },
206
+ variant: {
207
+ control: 'select',
208
+ options: ['', 'primary', 'secondary', 'success', 'warning', 'danger', 'monochrome'],
209
+ },
210
+ size: {
211
+ control: 'select',
212
+ options: ['x-sm', 'sm', 'md', 'lg', 'xl'],
213
+ },
214
+ disabled: { control: 'boolean' },
133
215
  },`,
134
216
  Button: ` argTypes: {
135
217
  variant: {
@@ -363,10 +445,12 @@ const DEFAULT_ARGS = {
363
445
  children: '5',
364
446
  },`,
365
447
  BadgeFx: ` args: {
448
+ fx: 'bounce',
366
449
  children: 'BadgeFx',
367
450
  },`,
368
451
  ButtonFx: ` args: {
369
- children: 'ButtonFx',
452
+ fx: 'bounce',
453
+ children: 'Click me',
370
454
  },`,
371
455
  Card: ` args: {
372
456
  children: 'Card content goes here.',
@@ -489,9 +573,247 @@ export const Disabled: Story = {
489
573
  return '';
490
574
  }
491
575
  // ---------------------------------------------------------------------------
576
+ // React story overrides — components that need rich custom content
577
+ // ---------------------------------------------------------------------------
578
+ const REACT_STORY_OVERRIDES = {
579
+ Sidebar: `import type { Meta, StoryObj } from '@storybook/react';
580
+ import { useState } from 'react';
581
+ import { ReactSidebar } from './ReactSidebar';
582
+ import {
583
+ ReactSidebarNav,
584
+ ReactSidebarNavItem,
585
+ ReactSidebarNavSubmenu,
586
+ ReactSidebarNavPopoverSubmenu,
587
+ } from '../../SidebarNav/react/index';
588
+ import { ReactPopover } from '../../Popover/react/ReactPopover';
589
+
590
+ const HomeIcon = () => (
591
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
592
+ <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
593
+ <polyline points="9 22 9 12 15 12 15 22"/>
594
+ </svg>
595
+ );
596
+ const FolderIcon = () => (
597
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
598
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
599
+ </svg>
600
+ );
601
+ const UsersIcon = () => (
602
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
603
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
604
+ <circle cx="9" cy="7" r="4"/>
605
+ <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
606
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
607
+ </svg>
608
+ );
609
+ const SettingsIcon = () => (
610
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
611
+ <circle cx="12" cy="12" r="3"/>
612
+ <path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
613
+ </svg>
614
+ );
615
+ const ChevronRightIcon = () => (
616
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
617
+ <polyline points="9 18 15 12 9 6"/>
618
+ </svg>
619
+ );
620
+
621
+ const NAV_CSS = \`
622
+ .nav-button {
623
+ display: flex;
624
+ align-items: center;
625
+ gap: var(--ag-space-3);
626
+ position: relative;
627
+ padding: var(--ag-space-2) var(--ag-space-3);
628
+ margin-block-end: var(--ag-space-1);
629
+ border: none;
630
+ background: none;
631
+ cursor: pointer;
632
+ width: 100%;
633
+ text-align: left;
634
+ border-radius: var(--ag-radius-sm);
635
+ transition: background var(--ag-fx-duration-sm);
636
+ color: inherit;
637
+ }
638
+ .nav-button svg { flex-shrink: 0; }
639
+ .nav-button:hover { background: var(--ag-background-secondary); }
640
+ .nav-button.active {
641
+ background: var(--ag-primary-background);
642
+ color: var(--ag-primary-text);
643
+ font-weight: 500;
644
+ }
645
+ .nav-label {
646
+ flex-grow: 1;
647
+ overflow: hidden;
648
+ text-overflow: ellipsis;
649
+ white-space: nowrap;
650
+ transition: opacity var(--ag-sidebar-transition-duration) var(--ag-sidebar-transition-easing);
651
+ }
652
+ .chevron {
653
+ display: flex;
654
+ align-items: center;
655
+ transition: transform var(--ag-fx-duration-md), opacity var(--ag-fx-duration-sm);
656
+ margin-left: auto;
657
+ }
658
+ .nav-button[aria-expanded="true"] .chevron { transform: rotate(90deg); }
659
+ ag-sidebar[collapsed] .nav-label,
660
+ ag-sidebar[collapsed] .chevron {
661
+ opacity: 0;
662
+ pointer-events: none;
663
+ display: none;
664
+ }
665
+ ag-sidebar[collapsed] .nav-button {
666
+ width: auto;
667
+ padding: var(--ag-space-2);
668
+ }
669
+ ag-sidebar[collapsed] ag-sidebar-nav-submenu:not(.popover-submenu),
670
+ ag-sidebar:not([collapsed]) ag-popover,
671
+ ag-sidebar[collapsed] .nav-button-expanded,
672
+ ag-sidebar:not([collapsed]) .nav-button-collapsed {
673
+ display: none !important;
674
+ }
675
+ ag-sidebar[collapsed] ag-popover.nav-button-collapsed {
676
+ display: block !important;
677
+ }
678
+ ag-sidebar-nav-submenu {
679
+ display: none;
680
+ overflow: hidden;
681
+ }
682
+ ag-sidebar-nav-submenu[open] {
683
+ display: block;
684
+ }
685
+ .nav-sublink {
686
+ display: block;
687
+ padding: var(--ag-space-2) var(--ag-space-3);
688
+ margin-block-end: var(--ag-space-1);
689
+ color: inherit;
690
+ text-decoration: none;
691
+ border-radius: var(--ag-radius-sm);
692
+ transition: background var(--ag-fx-duration-sm);
693
+ }
694
+ .nav-sublink:hover { background: var(--ag-background-secondary); }
695
+ .nav-button-collapsed::part(ag-popover-body) { padding: var(--ag-space-1); }
696
+ \`;
697
+
698
+ function handleSubmenuToggle(e: React.MouseEvent) {
699
+ e.preventDefault();
700
+ e.stopPropagation();
701
+ const button = e.currentTarget as HTMLElement;
702
+ const navItem = button.closest('ag-sidebar-nav-item');
703
+ const submenu = navItem?.querySelector('ag-sidebar-nav-submenu');
704
+ if (!submenu) return;
705
+ const isExpanded = button.getAttribute('aria-expanded') === 'true';
706
+ button.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
707
+ if (isExpanded) submenu.removeAttribute('open');
708
+ else submenu.setAttribute('open', '');
709
+ }
710
+
711
+ function SidebarDemo({ initialCollapsed = false, variant = 'default' }: { initialCollapsed?: boolean; variant?: string }) {
712
+ const [collapsed, setCollapsed] = useState(initialCollapsed);
713
+ return (
714
+ <>
715
+ <style>{NAV_CSS}</style>
716
+ <div style={{ position: 'relative', display: 'flex', height: '500px', border: '1px solid var(--ag-border-color)', borderRadius: '0.5rem', overflow: 'hidden', contain: 'layout' as any }}>
717
+ <ReactSidebar collapsed={collapsed} variant={variant as any} aria-label="Main navigation" showMobileToggle>
718
+ <h2 slot="ag-header-start" style={{ margin: 0, fontSize: '1.125rem', fontWeight: 600 }}>Dashboard</h2>
719
+ <button
720
+ type="button"
721
+ slot="ag-header-toggle"
722
+ onClick={() => setCollapsed(c => !c)}
723
+ style={{ background: 'none', border: 'none', padding: '8px 0', cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'inherit' }}
724
+ aria-label="Toggle sidebar"
725
+ >
726
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
727
+ <rect width="18" height="18" x="3" y="3" rx="2" ry="2"/>
728
+ <path d="M9 3v18"/>
729
+ </svg>
730
+ </button>
731
+ <ReactSidebarNav>
732
+ <ReactSidebarNavItem>
733
+ <button type="button" className="nav-button active" aria-current="page">
734
+ <HomeIcon /><span className="nav-label">Dashboard</span>
735
+ </button>
736
+ </ReactSidebarNavItem>
737
+ <ReactSidebarNavItem>
738
+ <button type="button" className="nav-button nav-button-expanded" aria-expanded="false" onClick={handleSubmenuToggle}>
739
+ <FolderIcon /><span className="nav-label">Projects</span>
740
+ <span className="chevron"><ChevronRightIcon /></span>
741
+ </button>
742
+ <ReactPopover className="nav-button-collapsed" placement="right-start" triggerType="click" distance={8} arrow={true}>
743
+ <button slot="trigger" type="button" className="nav-button"><FolderIcon /></button>
744
+ <ReactSidebarNavPopoverSubmenu slot="content">
745
+ <a href="#" className="nav-sublink" onClick={e => e.preventDefault()}>Project Alpha</a>
746
+ <a href="#" className="nav-sublink" onClick={e => e.preventDefault()}>Project Beta</a>
747
+ <a href="#" className="nav-sublink" onClick={e => e.preventDefault()}>Project Gamma</a>
748
+ </ReactSidebarNavPopoverSubmenu>
749
+ </ReactPopover>
750
+ <ReactSidebarNavSubmenu>
751
+ <a className="nav-sublink" href="#" onClick={e => e.preventDefault()}>Project Alpha</a>
752
+ <a className="nav-sublink" href="#" onClick={e => e.preventDefault()}>Project Beta</a>
753
+ <a className="nav-sublink" href="#" onClick={e => e.preventDefault()}>Project Gamma</a>
754
+ </ReactSidebarNavSubmenu>
755
+ </ReactSidebarNavItem>
756
+ <ReactSidebarNavItem>
757
+ <button type="button" className="nav-button">
758
+ <UsersIcon /><span className="nav-label">Team</span>
759
+ </button>
760
+ </ReactSidebarNavItem>
761
+ <ReactSidebarNavItem>
762
+ <button type="button" className="nav-button nav-button-expanded" aria-expanded="false" onClick={handleSubmenuToggle}>
763
+ <SettingsIcon /><span className="nav-label">Settings</span>
764
+ <span className="chevron"><ChevronRightIcon /></span>
765
+ </button>
766
+ <ReactPopover className="nav-button-collapsed" placement="right-start" triggerType="click" distance={8} arrow={true}>
767
+ <button slot="trigger" type="button" className="nav-button"><SettingsIcon /></button>
768
+ <ReactSidebarNavPopoverSubmenu slot="content">
769
+ <a href="#" className="nav-sublink" onClick={e => e.preventDefault()}>Profile</a>
770
+ <a href="#" className="nav-sublink" onClick={e => e.preventDefault()}>Billing</a>
771
+ <a href="#" className="nav-sublink" onClick={e => e.preventDefault()}>Security</a>
772
+ </ReactSidebarNavPopoverSubmenu>
773
+ </ReactPopover>
774
+ <ReactSidebarNavSubmenu>
775
+ <a className="nav-sublink" href="#" onClick={e => e.preventDefault()}>Profile</a>
776
+ <a className="nav-sublink" href="#" onClick={e => e.preventDefault()}>Billing</a>
777
+ <a className="nav-sublink" href="#" onClick={e => e.preventDefault()}>Security</a>
778
+ </ReactSidebarNavSubmenu>
779
+ </ReactSidebarNavItem>
780
+ </ReactSidebarNav>
781
+ <div slot="ag-footer" style={{ fontSize: '0.875rem', color: 'var(--ag-text-secondary)' }}>
782
+ © 2024 Company
783
+ </div>
784
+ </ReactSidebar>
785
+ <main style={{ flex: 1, padding: '2rem', overflow: 'auto', background: 'var(--ag-background)' }}>
786
+ <h1 style={{ marginTop: 0 }}>Main Content</h1>
787
+ <p>Click the header toggle to collapse the sidebar into rail mode.</p>
788
+ <p>When collapsed, click icon-only items with submenus to see them in popovers.</p>
789
+ </main>
790
+ </div>
791
+ </>
792
+ );
793
+ }
794
+
795
+ const meta = {
796
+ title: 'AgnosticUI/Sidebar',
797
+ component: ReactSidebar,
798
+ tags: ['autodocs'],
799
+ parameters: { layout: 'fullscreen' },
800
+ } satisfies Meta<typeof ReactSidebar>;
801
+
802
+ export default meta;
803
+ type Story = StoryObj<typeof meta>;
804
+
805
+ export const Default: Story = { render: () => <SidebarDemo /> };
806
+ export const Collapsed: Story = { render: () => <SidebarDemo initialCollapsed={true} /> };
807
+ export const Elevated: Story = { render: () => <SidebarDemo variant="elevated" /> };
808
+ `,
809
+ };
810
+ // ---------------------------------------------------------------------------
492
811
  // Per-component story generator (React only)
493
812
  // ---------------------------------------------------------------------------
494
813
  export function generateReactStory(name) {
814
+ if (REACT_STORY_OVERRIDES[name]) {
815
+ return STORY_FILE_HEADER + REACT_STORY_OVERRIDES[name];
816
+ }
495
817
  const lines = [];
496
818
  // Imports
497
819
  lines.push(`import type { Meta, StoryObj } from '@storybook/react';`);
@@ -656,6 +978,2896 @@ export function generateReactStory(name) {
656
978
  if (extras) {
657
979
  lines.push(extras);
658
980
  }
659
- return lines.join('\n') + '\n';
981
+ return STORY_FILE_HEADER + lines.join('\n') + '\n';
982
+ }
983
+ // ---------------------------------------------------------------------------
984
+ // Vue story overrides — full story content for components needing special handling
985
+ // ---------------------------------------------------------------------------
986
+ const VUE_STORY_OVERRIDES = {
987
+ Breadcrumb: `import type { Meta, StoryObj } from '@storybook/vue3';
988
+ import VueBreadcrumb from './VueBreadcrumb.vue';
989
+
990
+ const ITEMS = [
991
+ { label: 'Home', href: '#', isCurrent: false },
992
+ { label: 'Category', href: '#', isCurrent: false },
993
+ { label: 'Current Page', href: '#', isCurrent: true },
994
+ ];
995
+
996
+ const meta = {
997
+ title: 'AgnosticUI/Breadcrumb',
998
+ component: VueBreadcrumb,
999
+ tags: ['autodocs'],
1000
+ argTypes: {
1001
+ type: {
1002
+ control: 'select',
1003
+ options: ['default', 'slash', 'bullet', 'arrow'],
1004
+ },
1005
+ },
1006
+ args: { items: ITEMS, type: 'default' },
1007
+ render: (args: any) => ({
1008
+ components: { VueBreadcrumb },
1009
+ setup() { return { args }; },
1010
+ template: '<VueBreadcrumb v-bind="args" />',
1011
+ }),
1012
+ } satisfies Meta<typeof VueBreadcrumb>;
1013
+
1014
+ export default meta;
1015
+ type Story = StoryObj<typeof meta>;
1016
+
1017
+ export const Default: Story = {};
1018
+ export const Slash: Story = { args: { type: 'slash' } };
1019
+ export const Arrow: Story = { args: { type: 'arrow' } };
1020
+ `,
1021
+ Flex: `import type { Meta, StoryObj } from '@storybook/vue3';
1022
+ import VueFlexRow from './VueFlexRow.vue';
1023
+
1024
+ const meta = {
1025
+ title: 'AgnosticUI/Flex',
1026
+ component: VueFlexRow,
1027
+ tags: ['autodocs'],
1028
+ argTypes: {
1029
+ justify: {
1030
+ control: 'select',
1031
+ options: ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly'],
1032
+ },
1033
+ wrap: {
1034
+ control: 'select',
1035
+ options: ['nowrap', 'wrap', 'wrap-reverse'],
1036
+ },
1037
+ align: {
1038
+ control: 'select',
1039
+ options: ['flex-start', 'flex-end', 'center', 'baseline', 'stretch'],
1040
+ },
1041
+ },
1042
+ render: (args: any) => ({
1043
+ components: { VueFlexRow },
1044
+ setup() { return { args }; },
1045
+ template: \`
1046
+ <VueFlexRow v-bind="args" style="--flex-gap: 1rem">
1047
+ <div style="padding:1rem;background:#e0e7ff;border:1px solid #6366f1">Item 1</div>
1048
+ <div style="padding:1rem;background:#dbeafe;border:1px solid #3b82f6">Item 2</div>
1049
+ <div style="padding:1rem;background:#ddd6fe;border:1px solid #8b5cf6">Item 3</div>
1050
+ </VueFlexRow>
1051
+ \`,
1052
+ }),
1053
+ } satisfies Meta<typeof VueFlexRow>;
1054
+
1055
+ export default meta;
1056
+ type Story = StoryObj<typeof meta>;
1057
+
1058
+ export const Default: Story = {};
1059
+ export const Centered: Story = { args: { justify: 'center' } };
1060
+ export const SpaceBetween: Story = { args: { justify: 'space-between' } };
1061
+ `,
1062
+ Header: `import type { Meta, StoryObj } from '@storybook/vue3';
1063
+ import VueHeader from './VueHeader.vue';
1064
+
1065
+ const meta = {
1066
+ title: 'AgnosticUI/Header',
1067
+ component: VueHeader,
1068
+ tags: ['autodocs'],
1069
+ argTypes: {
1070
+ sticky: { control: 'boolean' },
1071
+ contentJustify: {
1072
+ control: 'select',
1073
+ options: ['start', 'end', 'between', 'around', 'center'],
1074
+ },
1075
+ },
1076
+ args: { sticky: false, contentJustify: 'between' },
1077
+ render: (args: any) => ({
1078
+ components: { VueHeader },
1079
+ setup() { return { args }; },
1080
+ template: \`
1081
+ <VueHeader v-bind="args">
1082
+ <template #logo>
1083
+ <a href="#" style="text-decoration:none;font-weight:700;font-size:1.25rem">AgnosticUI</a>
1084
+ </template>
1085
+ <nav>
1086
+ <ul style="display:flex;gap:1.5rem;list-style:none;margin:0;padding:0">
1087
+ <li><a href="#">Home</a></li>
1088
+ <li><a href="#">About</a></li>
1089
+ <li><a href="#">Contact</a></li>
1090
+ </ul>
1091
+ </nav>
1092
+ </VueHeader>
1093
+ \`,
1094
+ }),
1095
+ } satisfies Meta<typeof VueHeader>;
1096
+
1097
+ export default meta;
1098
+ type Story = StoryObj<typeof meta>;
1099
+
1100
+ export const Default: Story = {};
1101
+ `,
1102
+ Icon: `import type { Meta, StoryObj } from '@storybook/vue3';
1103
+ import VueIcon from './VueIcon.vue';
1104
+ import { defineComponent, h } from 'vue';
1105
+
1106
+ const StarIcon = defineComponent({
1107
+ render() {
1108
+ return h('svg', { width: '1em', height: '1em', viewBox: '0 0 24 24', fill: 'currentColor' }, [
1109
+ h('path', { d: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z' }),
1110
+ ]);
1111
+ },
1112
+ });
1113
+
1114
+ const meta = {
1115
+ title: 'AgnosticUI/Icon',
1116
+ component: VueIcon,
1117
+ tags: ['autodocs'],
1118
+ argTypes: {
1119
+ size: {
1120
+ control: 'select',
1121
+ options: ['14', '16', '18', '20', '24', '32', '36', '48'],
1122
+ },
1123
+ type: {
1124
+ control: 'select',
1125
+ options: ['', 'info', 'action', 'success', 'warning', 'error'],
1126
+ },
1127
+ noFill: { control: 'boolean' },
1128
+ },
1129
+ args: { size: '24', type: '' },
1130
+ render: (args: any) => ({
1131
+ components: { VueIcon, StarIcon },
1132
+ setup() { return { args }; },
1133
+ template: '<VueIcon v-bind="args" no-fill><StarIcon /></VueIcon>',
1134
+ }),
1135
+ } satisfies Meta<typeof VueIcon>;
1136
+
1137
+ export default meta;
1138
+ type Story = StoryObj<typeof meta>;
1139
+
1140
+ export const Default: Story = {};
1141
+ export const Types: Story = {
1142
+ render: () => ({
1143
+ components: { VueIcon, StarIcon },
1144
+ template: \`
1145
+ <div style="display:flex;gap:1rem;align-items:center">
1146
+ <VueIcon no-fill><StarIcon /></VueIcon>
1147
+ <VueIcon type="info" no-fill><StarIcon /></VueIcon>
1148
+ <VueIcon type="success" no-fill><StarIcon /></VueIcon>
1149
+ <VueIcon type="warning" no-fill><StarIcon /></VueIcon>
1150
+ <VueIcon type="error" no-fill><StarIcon /></VueIcon>
1151
+ </div>
1152
+ \`,
1153
+ }),
1154
+ };
1155
+ export const Sizes: Story = {
1156
+ render: () => ({
1157
+ components: { VueIcon, StarIcon },
1158
+ template: \`
1159
+ <div style="display:flex;gap:1rem;align-items:center">
1160
+ <VueIcon size="16" no-fill><StarIcon /></VueIcon>
1161
+ <VueIcon size="20" no-fill><StarIcon /></VueIcon>
1162
+ <VueIcon size="24" no-fill><StarIcon /></VueIcon>
1163
+ <VueIcon size="32" no-fill><StarIcon /></VueIcon>
1164
+ <VueIcon size="48" no-fill><StarIcon /></VueIcon>
1165
+ </div>
1166
+ \`,
1167
+ }),
1168
+ };
1169
+ `,
1170
+ IconButton: `import type { Meta, StoryObj } from '@storybook/vue3';
1171
+ import VueIconButton from './VueIconButton.vue';
1172
+
1173
+ const meta = {
1174
+ title: 'AgnosticUI/IconButton',
1175
+ component: VueIconButton,
1176
+ tags: ['autodocs'],
1177
+ argTypes: {
1178
+ variant: {
1179
+ control: 'select',
1180
+ options: ['primary', 'secondary', 'success', 'warning', 'danger', 'ghost', 'monochrome'],
1181
+ },
1182
+ size: {
1183
+ control: 'select',
1184
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
1185
+ },
1186
+ disabled: { control: 'boolean' },
1187
+ loading: { control: 'boolean' },
1188
+ label: { control: 'text' },
1189
+ unicode: { control: 'text' },
1190
+ },
1191
+ args: { label: 'Action', unicode: '★', size: 'md', variant: 'primary' },
1192
+ render: (args: any) => ({
1193
+ components: { VueIconButton },
1194
+ setup() { return { args }; },
1195
+ template: '<VueIconButton v-bind="args" />',
1196
+ }),
1197
+ } satisfies Meta<typeof VueIconButton>;
1198
+
1199
+ export default meta;
1200
+ type Story = StoryObj<typeof meta>;
1201
+
1202
+ export const Default: Story = {};
1203
+ export const Variants: Story = {
1204
+ render: () => ({
1205
+ components: { VueIconButton },
1206
+ template: \`
1207
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
1208
+ <VueIconButton label="Primary" unicode="★" variant="primary" />
1209
+ <VueIconButton label="Secondary" unicode="★" variant="secondary" />
1210
+ <VueIconButton label="Success" unicode="★" variant="success" />
1211
+ <VueIconButton label="Danger" unicode="★" variant="danger" />
1212
+ <VueIconButton label="Ghost" unicode="★" variant="ghost" />
1213
+ </div>
1214
+ \`,
1215
+ }),
1216
+ };
1217
+ export const Sizes: Story = {
1218
+ render: () => ({
1219
+ components: { VueIconButton },
1220
+ template: \`
1221
+ <div style="display:flex;gap:8px;align-items:center">
1222
+ <VueIconButton label="XS" unicode="★" size="xs" variant="primary" />
1223
+ <VueIconButton label="SM" unicode="★" size="sm" variant="primary" />
1224
+ <VueIconButton label="MD" unicode="★" size="md" variant="primary" />
1225
+ <VueIconButton label="LG" unicode="★" size="lg" variant="primary" />
1226
+ <VueIconButton label="XL" unicode="★" size="xl" variant="primary" />
1227
+ </div>
1228
+ \`,
1229
+ }),
1230
+ };
1231
+ `,
1232
+ IconButtonFx: `import type { Meta, StoryObj } from '@storybook/vue3';
1233
+ import VueIconButtonFx from './VueIconButtonFx.vue';
1234
+ import { defineComponent, h } from 'vue';
1235
+
1236
+ const HeartIcon = defineComponent({
1237
+ render() {
1238
+ return h('svg', { width: '1em', height: '1em', viewBox: '0 0 24 24', fill: 'currentColor' }, [
1239
+ h('path', { d: 'M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z' }),
1240
+ ]);
1241
+ },
1242
+ });
1243
+
1244
+ const meta = {
1245
+ title: 'AgnosticUI/IconButtonFx',
1246
+ component: VueIconButtonFx,
1247
+ tags: ['autodocs'],
1248
+ argTypes: {
1249
+ fx: {
1250
+ control: 'select',
1251
+ options: ['bounce', 'pulse', 'jelly', 'shake', 'pulse-wobble'],
1252
+ },
1253
+ fxSpeed: {
1254
+ control: 'select',
1255
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
1256
+ },
1257
+ fxDisabled: { control: 'boolean' },
1258
+ variant: {
1259
+ control: 'select',
1260
+ options: ['primary', 'secondary', 'success', 'warning', 'danger', 'ghost', 'monochrome'],
1261
+ },
1262
+ size: {
1263
+ control: 'select',
1264
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
1265
+ },
1266
+ disabled: { control: 'boolean' },
1267
+ label: { control: 'text' },
1268
+ },
1269
+ args: { fx: 'bounce', label: 'Like', size: 'md', variant: 'primary' },
1270
+ render: (args: any) => ({
1271
+ components: { VueIconButtonFx, HeartIcon },
1272
+ setup() { return { args }; },
1273
+ template: '<VueIconButtonFx v-bind="args"><HeartIcon /></VueIconButtonFx>',
1274
+ }),
1275
+ } satisfies Meta<typeof VueIconButtonFx>;
1276
+
1277
+ export default meta;
1278
+ type Story = StoryObj<typeof meta>;
1279
+
1280
+ export const Default: Story = {};
1281
+ export const Variants: Story = {
1282
+ render: () => ({
1283
+ components: { VueIconButtonFx, HeartIcon },
1284
+ template: \`
1285
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
1286
+ <VueIconButtonFx fx="bounce" label="Primary" variant="primary"><HeartIcon /></VueIconButtonFx>
1287
+ <VueIconButtonFx fx="pulse" label="Secondary" variant="secondary"><HeartIcon /></VueIconButtonFx>
1288
+ <VueIconButtonFx fx="jelly" label="Ghost" variant="ghost"><HeartIcon /></VueIconButtonFx>
1289
+ </div>
1290
+ \`,
1291
+ }),
1292
+ };
1293
+ `,
1294
+ Image: `import type { Meta, StoryObj } from '@storybook/vue3';
1295
+ import VueImage from './VueImage.vue';
1296
+
1297
+ const meta = {
1298
+ title: 'AgnosticUI/Image',
1299
+ component: VueImage,
1300
+ tags: ['autodocs'],
1301
+ argTypes: {
1302
+ fit: {
1303
+ control: 'select',
1304
+ options: ['cover', 'contain', 'fill', 'none', 'scale-down'],
1305
+ },
1306
+ loading: {
1307
+ control: 'select',
1308
+ options: ['lazy', 'eager'],
1309
+ },
1310
+ fade: { control: 'boolean' },
1311
+ },
1312
+ args: {
1313
+ src: 'https://picsum.photos/400/300',
1314
+ alt: 'Sample landscape photo',
1315
+ width: 400,
1316
+ height: 300,
1317
+ },
1318
+ render: (args: any) => ({
1319
+ components: { VueImage },
1320
+ setup() { return { args }; },
1321
+ template: '<VueImage v-bind="args" />',
1322
+ }),
1323
+ } satisfies Meta<typeof VueImage>;
1324
+
1325
+ export default meta;
1326
+ type Story = StoryObj<typeof meta>;
1327
+
1328
+ export const Default: Story = {};
1329
+ export const WithFade: Story = { args: { fade: true } };
1330
+ export const ContainFit: Story = {
1331
+ args: {
1332
+ src: 'https://picsum.photos/400/300?grayscale',
1333
+ alt: 'Contained image',
1334
+ fit: 'contain',
1335
+ aspectRatio: '16/9',
1336
+ },
1337
+ };
1338
+ `,
1339
+ IntlFormatter: `import type { Meta, StoryObj } from '@storybook/vue3';
1340
+ import VueIntlFormatter from './VueIntlFormatter.vue';
1341
+
1342
+ const meta = {
1343
+ title: 'AgnosticUI/IntlFormatter',
1344
+ component: VueIntlFormatter,
1345
+ tags: ['autodocs'],
1346
+ argTypes: {
1347
+ type: {
1348
+ control: 'select',
1349
+ options: ['date', 'number', 'percent', 'currency'],
1350
+ },
1351
+ lang: { control: 'text' },
1352
+ },
1353
+ args: { type: 'number', value: 1234567.89, lang: 'en-US' },
1354
+ render: (args: any) => ({
1355
+ components: { VueIntlFormatter },
1356
+ setup() { return { args }; },
1357
+ template: '<VueIntlFormatter v-bind="args" />',
1358
+ }),
1359
+ } satisfies Meta<typeof VueIntlFormatter>;
1360
+
1361
+ export default meta;
1362
+ type Story = StoryObj<typeof meta>;
1363
+
1364
+ export const Default: Story = {};
1365
+ export const Currency: Story = {
1366
+ args: { type: 'currency', value: 9999.99, currency: 'USD', lang: 'en-US' },
1367
+ };
1368
+ export const Date: Story = {
1369
+ args: { type: 'date', date: '2024-06-15', dateStyle: 'long', lang: 'en-US' },
1370
+ };
1371
+ export const Percent: Story = {
1372
+ args: { type: 'percent', value: 0.754, lang: 'en-US' },
1373
+ };
1374
+ `,
1375
+ Menu: `import type { Meta, StoryObj } from '@storybook/vue3';
1376
+ import { VueMenu, VueMenuItem, VueMenuSeparator } from './index';
1377
+
1378
+ const meta = {
1379
+ title: 'AgnosticUI/Menu',
1380
+ component: VueMenu,
1381
+ tags: ['autodocs'],
1382
+ argTypes: {
1383
+ buttonVariant: {
1384
+ control: 'select',
1385
+ options: ['', 'primary', 'secondary', 'success', 'warning', 'danger', 'monochrome'],
1386
+ },
1387
+ size: {
1388
+ control: 'select',
1389
+ options: ['x-sm', 'sm', 'md', 'lg', 'xl'],
1390
+ },
1391
+ disabled: { control: 'boolean' },
1392
+ },
1393
+ args: { size: 'md' },
1394
+ render: (args: any) => ({
1395
+ components: { VueMenu, VueMenuItem, VueMenuSeparator },
1396
+ setup() { return { args }; },
1397
+ template: \`
1398
+ <VueMenu v-bind="args">
1399
+ <template #menu>
1400
+ <VueMenuItem value="edit">Edit</VueMenuItem>
1401
+ <VueMenuItem value="duplicate">Duplicate</VueMenuItem>
1402
+ <VueMenuSeparator />
1403
+ <VueMenuItem value="delete">Delete</VueMenuItem>
1404
+ </template>
1405
+ </VueMenu>
1406
+ \`,
1407
+ }),
1408
+ } satisfies Meta<typeof VueMenu>;
1409
+
1410
+ export default meta;
1411
+ type Story = StoryObj<typeof meta>;
1412
+
1413
+ export const Default: Story = {};
1414
+ `,
1415
+ Popover: `import type { Meta, StoryObj } from '@storybook/vue3';
1416
+ import VuePopover from './VuePopover.vue';
1417
+
1418
+ const meta = {
1419
+ title: 'AgnosticUI/Popover',
1420
+ component: VuePopover,
1421
+ tags: ['autodocs'],
1422
+ argTypes: {
1423
+ placement: {
1424
+ control: 'select',
1425
+ options: ['top', 'top-start', 'top-end', 'right', 'bottom', 'bottom-start', 'bottom-end', 'left'],
1426
+ },
1427
+ triggerType: {
1428
+ control: 'select',
1429
+ options: ['click', 'hover', 'focus'],
1430
+ },
1431
+ arrow: { control: 'boolean' },
1432
+ showCloseButton: { control: 'boolean' },
1433
+ disabled: { control: 'boolean' },
1434
+ },
1435
+ args: { placement: 'bottom', arrow: true, triggerType: 'click', showCloseButton: true },
1436
+ render: (args: any) => ({
1437
+ components: { VuePopover },
1438
+ setup() { return { args }; },
1439
+ template: \`
1440
+ <VuePopover v-bind="args">
1441
+ <button slot="trigger">Open Popover</button>
1442
+ <span slot="title">Popover Title</span>
1443
+ <div slot="content">
1444
+ <p>Popover content goes here.</p>
1445
+ </div>
1446
+ </VuePopover>
1447
+ \`,
1448
+ }),
1449
+ } satisfies Meta<typeof VuePopover>;
1450
+
1451
+ export default meta;
1452
+ type Story = StoryObj<typeof meta>;
1453
+
1454
+ export const Default: Story = {};
1455
+ export const HoverTrigger: Story = {
1456
+ args: { triggerType: 'hover' },
1457
+ render: (args: any) => ({
1458
+ components: { VuePopover },
1459
+ setup() { return { args }; },
1460
+ template: \`
1461
+ <VuePopover v-bind="args">
1462
+ <button slot="trigger">Hover Me</button>
1463
+ <span slot="title">Hover Popover</span>
1464
+ <div slot="content"><p>Opens on hover.</p></div>
1465
+ </VuePopover>
1466
+ \`,
1467
+ }),
1468
+ };
1469
+ `,
1470
+ Sidebar: `import type { Meta, StoryObj } from '@storybook/vue3';
1471
+ import { onMounted } from 'vue';
1472
+ import VueSidebar from './VueSidebar.vue';
1473
+ import { VueSidebarNav, VueSidebarNavItem, VueSidebarNavSubmenu } from '../../SidebarNav/vue/index';
1474
+ import VuePopover from '../../Popover/vue/VuePopover.vue';
1475
+ import { VueSidebarNavPopoverSubmenu } from '../../SidebarNav/vue/index';
1476
+
1477
+ const HOME_SVG = \`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>\`;
1478
+ const FOLDER_SVG = \`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>\`;
1479
+ const USERS_SVG = \`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>\`;
1480
+ const SETTINGS_SVG = \`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>\`;
1481
+ const CHEVRON_RIGHT_SVG = \`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>\`;
1482
+ const PANEL_TOGGLE_SVG = \`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 3v18"/></svg>\`;
1483
+
1484
+ const NAV_STYLES = \`
1485
+ .nav-button {
1486
+ display: flex;
1487
+ align-items: center;
1488
+ gap: var(--ag-space-3);
1489
+ position: relative;
1490
+ padding: var(--ag-space-2) var(--ag-space-3);
1491
+ margin-block-end: var(--ag-space-1);
1492
+ border: none;
1493
+ background: none;
1494
+ cursor: pointer;
1495
+ width: 100%;
1496
+ text-align: left;
1497
+ border-radius: var(--ag-radius-sm);
1498
+ transition: background var(--ag-fx-duration-sm);
1499
+ color: inherit;
1500
+ }
1501
+ .nav-button svg { flex-shrink: 0; }
1502
+ .nav-button:hover { background: var(--ag-background-secondary); }
1503
+ .nav-button.active {
1504
+ background: var(--ag-primary-background);
1505
+ color: var(--ag-primary-text);
1506
+ font-weight: 500;
1507
+ }
1508
+ .nav-label {
1509
+ flex-grow: 1;
1510
+ overflow: hidden;
1511
+ text-overflow: ellipsis;
1512
+ white-space: nowrap;
1513
+ transition: opacity var(--ag-sidebar-transition-duration) var(--ag-sidebar-transition-easing);
1514
+ }
1515
+ .chevron {
1516
+ display: flex;
1517
+ align-items: center;
1518
+ transition: transform var(--ag-fx-duration-md), opacity var(--ag-fx-duration-sm);
1519
+ margin-left: auto;
1520
+ }
1521
+ .nav-button[aria-expanded="true"] .chevron { transform: rotate(90deg); }
1522
+ ag-sidebar[collapsed] .nav-label,
1523
+ ag-sidebar[collapsed] .chevron {
1524
+ opacity: 0;
1525
+ pointer-events: none;
1526
+ display: none;
1527
+ }
1528
+ ag-sidebar[collapsed] .nav-button {
1529
+ width: auto;
1530
+ padding: var(--ag-space-2);
1531
+ }
1532
+ ag-sidebar[collapsed] ag-sidebar-nav-submenu:not(.popover-submenu),
1533
+ ag-sidebar:not([collapsed]) ag-popover,
1534
+ ag-sidebar[collapsed] .nav-button-expanded,
1535
+ ag-sidebar:not([collapsed]) .nav-button-collapsed {
1536
+ display: none !important;
1537
+ }
1538
+ ag-sidebar[collapsed] ag-popover.nav-button-collapsed {
1539
+ display: block !important;
1540
+ }
1541
+ ag-sidebar-nav-submenu {
1542
+ display: none;
1543
+ overflow: hidden;
1544
+ }
1545
+ ag-sidebar-nav-submenu[open] {
1546
+ display: block;
1547
+ }
1548
+ .nav-sublink {
1549
+ display: block;
1550
+ padding: var(--ag-space-2) var(--ag-space-3);
1551
+ margin-block-end: var(--ag-space-1);
1552
+ color: inherit;
1553
+ text-decoration: none;
1554
+ border-radius: var(--ag-radius-sm);
1555
+ transition: background var(--ag-fx-duration-sm);
1556
+ }
1557
+ .nav-sublink:hover { background: var(--ag-background-secondary); }
1558
+ .nav-button-collapsed::part(ag-popover-body) { padding: var(--ag-space-1); }
1559
+ \`;
1560
+
1561
+ const meta = {
1562
+ title: 'AgnosticUI/Sidebar',
1563
+ component: VueSidebar,
1564
+ tags: ['autodocs'],
1565
+ parameters: { layout: 'fullscreen' },
1566
+ } satisfies Meta<typeof VueSidebar>;
1567
+
1568
+ export default meta;
1569
+ type Story = StoryObj<typeof meta>;
1570
+
1571
+ function makeStory(initialCollapsed = false, variant = 'default') {
1572
+ return {
1573
+ render: () => ({
1574
+ components: { VueSidebar, VueSidebarNav, VueSidebarNavItem, VueSidebarNavSubmenu, VueSidebarNavPopoverSubmenu, VuePopover },
1575
+ setup() {
1576
+ onMounted(() => {
1577
+ const id = 'ag-sidebar-story-styles';
1578
+ if (!document.getElementById(id)) {
1579
+ const el = document.createElement('style');
1580
+ el.id = id;
1581
+ el.textContent = NAV_STYLES;
1582
+ document.head.appendChild(el);
1583
+ }
1584
+ });
1585
+
1586
+ const toggleCollapse = (e: Event) => {
1587
+ const sidebar = (e.target as HTMLElement).closest('ag-sidebar') as any;
1588
+ if (sidebar && sidebar.toggleCollapse) sidebar.toggleCollapse();
1589
+ };
1590
+
1591
+ const handleSubmenuToggle = (e: Event) => {
1592
+ e.preventDefault();
1593
+ e.stopPropagation();
1594
+ const button = e.currentTarget as HTMLElement;
1595
+ const navItem = button.closest('ag-sidebar-nav-item');
1596
+ const submenu = navItem?.querySelector('ag-sidebar-nav-submenu');
1597
+ if (!submenu) return;
1598
+ const isExpanded = button.getAttribute('aria-expanded') === 'true';
1599
+ button.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
1600
+ if (isExpanded) submenu.removeAttribute('open');
1601
+ else submenu.setAttribute('open', '');
1602
+ };
1603
+
1604
+ return {
1605
+ initialCollapsed, variant, toggleCollapse, handleSubmenuToggle,
1606
+ HOME_SVG, FOLDER_SVG, USERS_SVG, SETTINGS_SVG, CHEVRON_RIGHT_SVG, PANEL_TOGGLE_SVG,
1607
+ };
1608
+ },
1609
+ template: \`
1610
+ <div style="position:relative;display:flex;height:500px;border:1px solid var(--ag-border-color);border-radius:0.5rem;overflow:hidden;contain:layout">
1611
+ <VueSidebar :collapsed="initialCollapsed" :variant="variant" aria-label="Main navigation" show-mobile-toggle>
1612
+ <h2 slot="ag-header-start" style="margin:0;font-size:1.125rem;font-weight:600">Dashboard</h2>
1613
+ <button type="button" slot="ag-header-toggle" @click="toggleCollapse"
1614
+ style="background:none;border:none;padding:8px 0;cursor:pointer;display:flex;align-items:center;color:inherit"
1615
+ aria-label="Toggle sidebar">
1616
+ <span v-html="PANEL_TOGGLE_SVG"></span>
1617
+ </button>
1618
+ <VueSidebarNav>
1619
+ <VueSidebarNavItem>
1620
+ <button type="button" class="nav-button active" aria-current="page">
1621
+ <span v-html="HOME_SVG"></span>
1622
+ <span class="nav-label">Dashboard</span>
1623
+ </button>
1624
+ </VueSidebarNavItem>
1625
+ <VueSidebarNavItem>
1626
+ <button type="button" class="nav-button nav-button-expanded" aria-expanded="false" @click="handleSubmenuToggle">
1627
+ <span v-html="FOLDER_SVG"></span>
1628
+ <span class="nav-label">Projects</span>
1629
+ <span class="chevron" v-html="CHEVRON_RIGHT_SVG"></span>
1630
+ </button>
1631
+ <VuePopover class="nav-button-collapsed" placement="right-start" trigger-type="click" :distance="8" :arrow="true">
1632
+ <button slot="trigger" type="button" class="nav-button">
1633
+ <span v-html="FOLDER_SVG"></span>
1634
+ </button>
1635
+ <VueSidebarNavPopoverSubmenu slot="content">
1636
+ <a href="#" class="nav-sublink" @click.prevent>Project Alpha</a>
1637
+ <a href="#" class="nav-sublink" @click.prevent>Project Beta</a>
1638
+ <a href="#" class="nav-sublink" @click.prevent>Project Gamma</a>
1639
+ </VueSidebarNavPopoverSubmenu>
1640
+ </VuePopover>
1641
+ <VueSidebarNavSubmenu>
1642
+ <a class="nav-sublink" href="#" @click.prevent>Project Alpha</a>
1643
+ <a class="nav-sublink" href="#" @click.prevent>Project Beta</a>
1644
+ <a class="nav-sublink" href="#" @click.prevent>Project Gamma</a>
1645
+ </VueSidebarNavSubmenu>
1646
+ </VueSidebarNavItem>
1647
+ <VueSidebarNavItem>
1648
+ <button type="button" class="nav-button">
1649
+ <span v-html="USERS_SVG"></span>
1650
+ <span class="nav-label">Team</span>
1651
+ </button>
1652
+ </VueSidebarNavItem>
1653
+ <VueSidebarNavItem>
1654
+ <button type="button" class="nav-button nav-button-expanded" aria-expanded="false" @click="handleSubmenuToggle">
1655
+ <span v-html="SETTINGS_SVG"></span>
1656
+ <span class="nav-label">Settings</span>
1657
+ <span class="chevron" v-html="CHEVRON_RIGHT_SVG"></span>
1658
+ </button>
1659
+ <VuePopover class="nav-button-collapsed" placement="right-start" trigger-type="click" :distance="8" :arrow="true">
1660
+ <button slot="trigger" type="button" class="nav-button">
1661
+ <span v-html="SETTINGS_SVG"></span>
1662
+ </button>
1663
+ <VueSidebarNavPopoverSubmenu slot="content">
1664
+ <a href="#" class="nav-sublink" @click.prevent>Profile</a>
1665
+ <a href="#" class="nav-sublink" @click.prevent>Billing</a>
1666
+ <a href="#" class="nav-sublink" @click.prevent>Security</a>
1667
+ </VueSidebarNavPopoverSubmenu>
1668
+ </VuePopover>
1669
+ <VueSidebarNavSubmenu>
1670
+ <a class="nav-sublink" href="#" @click.prevent>Profile</a>
1671
+ <a class="nav-sublink" href="#" @click.prevent>Billing</a>
1672
+ <a class="nav-sublink" href="#" @click.prevent>Security</a>
1673
+ </VueSidebarNavSubmenu>
1674
+ </VueSidebarNavItem>
1675
+ </VueSidebarNav>
1676
+ <div slot="ag-footer" style="font-size:0.875rem;color:var(--ag-text-secondary)">
1677
+ © 2024 Company
1678
+ </div>
1679
+ </VueSidebar>
1680
+ <main style="flex:1;padding:2rem;overflow:auto;background:var(--ag-background)">
1681
+ <h1 style="margin-top:0">Main Content</h1>
1682
+ <p>Click the header toggle to collapse the sidebar into rail mode.</p>
1683
+ <p>When collapsed, click icon-only items with submenus to see them in popovers.</p>
1684
+ </main>
1685
+ </div>
1686
+ \`,
1687
+ }),
1688
+ };
1689
+ }
1690
+
1691
+ export const Default: Story = makeStory() as Story;
1692
+ export const Collapsed: Story = makeStory(true) as Story;
1693
+ export const Elevated: Story = makeStory(false, 'elevated') as Story;
1694
+ `,
1695
+ Accordion: `import type { Meta, StoryObj } from '@storybook/vue3';
1696
+ import { VueAccordion, VueAccordionItem, VueAccordionHeader, VueAccordionContent } from './index';
1697
+
1698
+ const meta = {
1699
+ title: 'AgnosticUI/Accordion',
1700
+ component: VueAccordionItem,
1701
+ tags: ['autodocs'],
1702
+ argTypes: {
1703
+ useChevron: { control: 'boolean' },
1704
+ useX: { control: 'boolean' },
1705
+ useMinus: { control: 'boolean' },
1706
+ noIndicator: { control: 'boolean' },
1707
+ bordered: { control: 'boolean' },
1708
+ background: { control: 'boolean' },
1709
+ disabled: { control: 'boolean' },
1710
+ },
1711
+ args: { useChevron: true },
1712
+ render: (args: any) => ({
1713
+ components: { VueAccordion, VueAccordionItem, VueAccordionHeader, VueAccordionContent },
1714
+ setup() { return { args }; },
1715
+ template: \`
1716
+ <VueAccordion>
1717
+ <VueAccordionItem v-bind="args">
1718
+ <VueAccordionHeader>What is AgnosticUI?</VueAccordionHeader>
1719
+ <VueAccordionContent>
1720
+ AgnosticUI is a framework-agnostic UI component library that works with React, Vue, and Lit.
1721
+ </VueAccordionContent>
1722
+ </VueAccordionItem>
1723
+ <VueAccordionItem v-bind="args">
1724
+ <VueAccordionHeader>How do I install it?</VueAccordionHeader>
1725
+ <VueAccordionContent>
1726
+ Run <code>npx agnosticui-cli init</code> to set up your project, then use <code>npx agnosticui-cli add Button</code> to add components.
1727
+ </VueAccordionContent>
1728
+ </VueAccordionItem>
1729
+ <VueAccordionItem v-bind="args">
1730
+ <VueAccordionHeader>Is it accessible?</VueAccordionHeader>
1731
+ <VueAccordionContent>
1732
+ Yes — all components are built with accessibility in mind, following WAI-ARIA patterns.
1733
+ </VueAccordionContent>
1734
+ </VueAccordionItem>
1735
+ </VueAccordion>
1736
+ \`,
1737
+ }),
1738
+ } satisfies Meta<typeof VueAccordionItem>;
1739
+
1740
+ export default meta;
1741
+ type Story = StoryObj<typeof meta>;
1742
+
1743
+ export const Default: Story = {};
1744
+ export const OpenByDefault: Story = {
1745
+ render: (args: any) => ({
1746
+ components: { VueAccordion, VueAccordionItem, VueAccordionHeader, VueAccordionContent },
1747
+ setup() { return { args }; },
1748
+ template: \`
1749
+ <VueAccordion>
1750
+ <VueAccordionItem v-bind="args" :open="true">
1751
+ <VueAccordionHeader>Open by default</VueAccordionHeader>
1752
+ <VueAccordionContent>This item starts open.</VueAccordionContent>
1753
+ </VueAccordionItem>
1754
+ <VueAccordionItem v-bind="args">
1755
+ <VueAccordionHeader>Closed by default</VueAccordionHeader>
1756
+ <VueAccordionContent>Click to expand.</VueAccordionContent>
1757
+ </VueAccordionItem>
1758
+ </VueAccordion>
1759
+ \`,
1760
+ }),
1761
+ };
1762
+ export const Bordered: Story = { args: { bordered: true } };
1763
+ export const PlusMinusIndicator: Story = { args: { useMinus: true, useChevron: false } };
1764
+ `,
1765
+ Fieldset: `import type { Meta, StoryObj } from '@storybook/vue3';
1766
+ import VueFieldset from './VueFieldset.vue';
1767
+ import VueInput from '../../Input/vue/VueInput.vue';
1768
+ import VueCheckbox from '../../Checkbox/vue/VueCheckbox.vue';
1769
+
1770
+ const meta = {
1771
+ title: 'AgnosticUI/Fieldset',
1772
+ component: VueFieldset,
1773
+ tags: ['autodocs'],
1774
+ argTypes: {
1775
+ bordered: { control: 'boolean' },
1776
+ layout: { control: 'select', options: ['vertical', 'horizontal'] },
1777
+ legendHidden: { control: 'boolean' },
1778
+ legend: { control: 'text' },
1779
+ },
1780
+ args: { legend: 'Personal Information', bordered: false, layout: 'vertical' },
1781
+ render: (args: any) => ({
1782
+ components: { VueFieldset, VueInput },
1783
+ setup() { return { args }; },
1784
+ template: \`
1785
+ <VueFieldset v-bind="args">
1786
+ <VueInput label="First Name" type="text" />
1787
+ <VueInput label="Last Name" type="text" />
1788
+ <VueInput label="Email" type="email" />
1789
+ </VueFieldset>
1790
+ \`,
1791
+ }),
1792
+ } satisfies Meta<typeof VueFieldset>;
1793
+
1794
+ export default meta;
1795
+ type Story = StoryObj<typeof meta>;
1796
+
1797
+ export const Default: Story = {};
1798
+ export const Bordered: Story = { args: { legend: 'Contact Preferences', bordered: true } };
1799
+ export const Horizontal: Story = { args: { legend: 'Options', layout: 'horizontal' } };
1800
+ `,
1801
+ Progress: `import type { Meta, StoryObj } from '@storybook/vue3';
1802
+ import VueProgress from './VueProgress.vue';
1803
+
1804
+ const meta = {
1805
+ title: 'AgnosticUI/Progress',
1806
+ component: VueProgress,
1807
+ tags: ['autodocs'],
1808
+ argTypes: {
1809
+ value: { control: { type: 'number', min: 0, max: 100 } },
1810
+ max: { control: { type: 'number' } },
1811
+ label: { control: 'text' },
1812
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
1813
+ },
1814
+ args: { value: 60, max: 100, label: 'Loading progress' },
1815
+ render: (args: any) => ({
1816
+ components: { VueProgress },
1817
+ setup() { return { args }; },
1818
+ template: '<VueProgress v-bind="args" />',
1819
+ }),
1820
+ } satisfies Meta<typeof VueProgress>;
1821
+
1822
+ export default meta;
1823
+ type Story = StoryObj<typeof meta>;
1824
+
1825
+ export const Default: Story = {};
1826
+ export const HalfComplete: Story = { args: { value: 50, label: '50% complete' } };
1827
+ export const NearlyDone: Story = { args: { value: 85, label: 'Almost there' } };
1828
+ export const Indeterminate: Story = { args: { value: undefined, label: 'Loading...' } };
1829
+ `,
1830
+ ProgressRing: `import type { Meta, StoryObj } from '@storybook/vue3';
1831
+ import VueProgressRing from './VueProgressRing.vue';
1832
+
1833
+ const meta = {
1834
+ title: 'AgnosticUI/ProgressRing',
1835
+ component: VueProgressRing,
1836
+ tags: ['autodocs'],
1837
+ argTypes: {
1838
+ value: { control: { type: 'range', min: 0, max: 100, step: 1 } },
1839
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
1840
+ variant: { control: 'select', options: ['primary', 'success', 'warning', 'danger', 'info'] },
1841
+ label: { control: 'text' },
1842
+ },
1843
+ args: { value: 65, size: 'medium', variant: 'primary', label: 'Progress' },
1844
+ render: (args: any) => ({
1845
+ components: { VueProgressRing },
1846
+ setup() { return { args }; },
1847
+ template: '<VueProgressRing v-bind="args" />',
1848
+ }),
1849
+ } satisfies Meta<typeof VueProgressRing>;
1850
+
1851
+ export default meta;
1852
+ type Story = StoryObj<typeof meta>;
1853
+
1854
+ export const Default: Story = {};
1855
+ export const Variants: Story = {
1856
+ render: () => ({
1857
+ components: { VueProgressRing },
1858
+ template: \`
1859
+ <div style="display:flex;gap:1.5rem;align-items:center;flex-wrap:wrap">
1860
+ <VueProgressRing :value="65" variant="primary" label="Primary" />
1861
+ <VueProgressRing :value="80" variant="success" label="Success" />
1862
+ <VueProgressRing :value="45" variant="warning" label="Warning" />
1863
+ <VueProgressRing :value="30" variant="danger" label="Danger" />
1864
+ <VueProgressRing :value="55" variant="info" label="Info" />
1865
+ </div>
1866
+ \`,
1867
+ }),
1868
+ };
1869
+ export const Sizes: Story = {
1870
+ render: () => ({
1871
+ components: { VueProgressRing },
1872
+ template: \`
1873
+ <div style="display:flex;gap:1.5rem;align-items:center;flex-wrap:wrap">
1874
+ <VueProgressRing :value="65" size="small" label="Small" />
1875
+ <VueProgressRing :value="65" size="medium" label="Medium" />
1876
+ <VueProgressRing :value="65" size="large" label="Large" />
1877
+ </div>
1878
+ \`,
1879
+ }),
1880
+ };
1881
+ `,
1882
+ Tabs: `import type { Meta, StoryObj } from '@storybook/vue3';
1883
+ import { VueTabs, VueTab, VueTabPanel } from './index';
1884
+
1885
+ const meta = {
1886
+ title: 'AgnosticUI/Tabs',
1887
+ component: VueTabs,
1888
+ tags: ['autodocs'],
1889
+ argTypes: {
1890
+ activation: {
1891
+ control: 'select',
1892
+ options: ['manual', 'automatic'],
1893
+ },
1894
+ orientation: {
1895
+ control: 'select',
1896
+ options: ['horizontal', 'vertical'],
1897
+ },
1898
+ },
1899
+ args: { activation: 'manual', ariaLabel: 'Example tabs' },
1900
+ render: (args: any) => ({
1901
+ components: { VueTabs, VueTab, VueTabPanel },
1902
+ setup() { return { args }; },
1903
+ template: \`
1904
+ <VueTabs v-bind="args">
1905
+ <VueTab panel="panel-1">Tab 1</VueTab>
1906
+ <VueTab panel="panel-2">Tab 2</VueTab>
1907
+ <VueTab panel="panel-3">Tab 3</VueTab>
1908
+ <VueTabPanel panel="panel-1">Content for Tab 1</VueTabPanel>
1909
+ <VueTabPanel panel="panel-2">Content for Tab 2</VueTabPanel>
1910
+ <VueTabPanel panel="panel-3">Content for Tab 3</VueTabPanel>
1911
+ </VueTabs>
1912
+ \`,
1913
+ }),
1914
+ } satisfies Meta<typeof VueTabs>;
1915
+
1916
+ export default meta;
1917
+ type Story = StoryObj<typeof meta>;
1918
+
1919
+ export const Default: Story = {};
1920
+ export const Vertical: Story = { args: { orientation: 'vertical', ariaLabel: 'Vertical tabs' } };
1921
+ `,
1922
+ Timeline: `import type { Meta, StoryObj } from '@storybook/vue3';
1923
+ import { VueTimeline, VueTimelineItem } from './index';
1924
+
1925
+ const meta = {
1926
+ title: 'AgnosticUI/Timeline',
1927
+ component: VueTimeline,
1928
+ tags: ['autodocs'],
1929
+ argTypes: {
1930
+ orientation: {
1931
+ control: 'select',
1932
+ options: ['vertical', 'horizontal'],
1933
+ },
1934
+ variant: {
1935
+ control: 'select',
1936
+ options: ['', 'primary', 'success', 'warning', 'danger', 'monochrome'],
1937
+ },
1938
+ compact: { control: 'boolean' },
1939
+ },
1940
+ args: { orientation: 'horizontal', variant: '' },
1941
+ render: (args: any) => ({
1942
+ components: { VueTimeline, VueTimelineItem },
1943
+ setup() { return { args }; },
1944
+ template: \`
1945
+ <VueTimeline v-bind="args">
1946
+ <VueTimelineItem>
1947
+ <template #ag-start>Jan 2024</template>
1948
+ <template #ag-end>Project kickoff</template>
1949
+ </VueTimelineItem>
1950
+ <VueTimelineItem>
1951
+ <template #ag-start>Mar 2024</template>
1952
+ <template #ag-end>Design phase</template>
1953
+ </VueTimelineItem>
1954
+ <VueTimelineItem>
1955
+ <template #ag-start>Jun 2024</template>
1956
+ <template #ag-end>Development complete</template>
1957
+ </VueTimelineItem>
1958
+ </VueTimeline>
1959
+ \`,
1960
+ }),
1961
+ } satisfies Meta<typeof VueTimeline>;
1962
+
1963
+ export default meta;
1964
+ type Story = StoryObj<typeof meta>;
1965
+
1966
+ export const Default: Story = {};
1967
+ export const Vertical: Story = { args: { orientation: 'vertical' } };
1968
+ export const Primary: Story = { args: { variant: 'primary' } };
1969
+ `,
1970
+ };
1971
+ // ---------------------------------------------------------------------------
1972
+ // Vue story generator
1973
+ // ---------------------------------------------------------------------------
1974
+ export function generateVueStory(name) {
1975
+ // Return full override if one exists for this component
1976
+ if (VUE_STORY_OVERRIDES[name]) {
1977
+ return STORY_FILE_HEADER + VUE_STORY_OVERRIDES[name];
1978
+ }
1979
+ // Flex override: Vue component is VueFlexRow in VueFlexRow.vue
1980
+ const vueComponentName = name === 'Flex' ? 'VueFlexRow' : `Vue${name}`;
1981
+ const vueFileName = name === 'Flex' ? 'VueFlexRow' : `Vue${name}`;
1982
+ const lines = [];
1983
+ // Imports
1984
+ lines.push(`import type { Meta, StoryObj } from '@storybook/vue3';`);
1985
+ if (OPEN_CONTROLLED_COMPONENTS.has(name)) {
1986
+ lines.push(`import { ref } from 'vue';`);
1987
+ }
1988
+ if (name === 'Dialog') {
1989
+ lines.push(`import Vue${name} from './Vue${name}.vue';`);
1990
+ lines.push(`import VueButton from '../../Button/vue/VueButton.vue';`);
1991
+ }
1992
+ else if (name === 'Drawer') {
1993
+ lines.push(`import Vue${name} from './Vue${name}.vue';`);
1994
+ lines.push(`import VueButton from '../../Button/vue/VueButton.vue';`);
1995
+ }
1996
+ else if (name === 'Toast') {
1997
+ lines.push(`import Vue${name} from './Vue${name}.vue';`);
1998
+ lines.push(`import VueButton from '../../Button/vue/VueButton.vue';`);
1999
+ }
2000
+ else if (name === 'Collapsible') {
2001
+ lines.push(`import Vue${name} from './Vue${name}.vue';`);
2002
+ }
2003
+ else if (REQUIRED_ARRAY_COMPONENTS.has(name)) {
2004
+ lines.push(`import ${vueComponentName} from './${vueFileName}.vue';`);
2005
+ lines.push(`import type { ComboboxOption } from '../core/${name}';`);
2006
+ }
2007
+ else if (VUE_INDEX_EXPORTS[name]) {
2008
+ // Component uses non-standard file name — import via named export from index
2009
+ lines.push(`import { ${VUE_INDEX_EXPORTS[name]} } from './index';`);
2010
+ }
2011
+ else {
2012
+ lines.push(`import ${vueComponentName} from './${vueFileName}.vue';`);
2013
+ }
2014
+ lines.push('');
2015
+ // Sample data for Combobox
2016
+ if (name === 'Combobox') {
2017
+ lines.push(`const FRUITS: ComboboxOption[] = [`);
2018
+ lines.push(` { value: 'apple', label: 'Apple' },`);
2019
+ lines.push(` { value: 'banana', label: 'Banana' },`);
2020
+ lines.push(` { value: 'cherry', label: 'Cherry' },`);
2021
+ lines.push(` { value: 'grape', label: 'Grape' },`);
2022
+ lines.push(` { value: 'mango', label: 'Mango' },`);
2023
+ lines.push(` { value: 'orange', label: 'Orange' },`);
2024
+ lines.push(`];`);
2025
+ lines.push('');
2026
+ }
2027
+ const argTypes = ARGTYPES[name] ?? '';
2028
+ // Build meta args for Vue
2029
+ let metaArgs = '';
2030
+ if (name === 'Dialog') {
2031
+ metaArgs = ` args: {
2032
+ heading: 'Dialog title',
2033
+ description: 'Supporting description text goes here.',
2034
+ showCloseButton: true,
2035
+ },`;
2036
+ }
2037
+ else if (name === 'Drawer') {
2038
+ metaArgs = ` args: {
2039
+ heading: 'Drawer',
2040
+ showCloseButton: true,
2041
+ },`;
2042
+ }
2043
+ else if (name === 'Combobox') {
2044
+ metaArgs = ` args: {
2045
+ options: FRUITS,
2046
+ label: 'Select a fruit',
2047
+ placeholder: 'Choose...',
2048
+ id: 'combobox-default',
2049
+ },`;
2050
+ }
2051
+ else if (FX_COMPONENTS.has(name)) {
2052
+ const defaultLabel = name === 'BadgeFx' ? 'BadgeFx' : name === 'ButtonFx' ? 'Click me' : name;
2053
+ metaArgs = ` args: {
2054
+ fx: 'bounce',
2055
+ default: '${defaultLabel}',
2056
+ },`;
2057
+ }
2058
+ else if (TEXT_CHILD_COMPONENTS.has(name)) {
2059
+ // Use args.default for slot text
2060
+ metaArgs = ` args: {
2061
+ default: '${name}',
2062
+ },`;
2063
+ }
2064
+ // Build render for meta (only for non-open-controlled, non-collapsible)
2065
+ let metaRender = '';
2066
+ if (!OPEN_CONTROLLED_COMPONENTS.has(name)) {
2067
+ if (TEXT_CHILD_COMPONENTS.has(name) || FX_COMPONENTS.has(name)) {
2068
+ metaRender = ` render: (args: any) => ({
2069
+ components: { ${vueComponentName} },
2070
+ setup() { return { args }; },
2071
+ template: '<${vueComponentName} v-bind="args">{{ args.default }}</${vueComponentName}>',
2072
+ }),`;
2073
+ }
2074
+ else {
2075
+ metaRender = ` render: (args: any) => ({
2076
+ components: { ${vueComponentName} },
2077
+ setup() { return { args }; },
2078
+ template: '<${vueComponentName} v-bind="args" />',
2079
+ }),`;
2080
+ }
2081
+ }
2082
+ const metaParts = [];
2083
+ metaParts.push(` title: 'AgnosticUI/${name}',`);
2084
+ metaParts.push(` component: ${vueComponentName},`);
2085
+ metaParts.push(` tags: ['autodocs'],`);
2086
+ if (argTypes) {
2087
+ metaParts.push(argTypes);
2088
+ }
2089
+ if (metaArgs) {
2090
+ metaParts.push(metaArgs);
2091
+ }
2092
+ if (metaRender) {
2093
+ metaParts.push(metaRender);
2094
+ }
2095
+ lines.push(`const meta = {`);
2096
+ lines.push(metaParts.join('\n'));
2097
+ lines.push(`} satisfies Meta<typeof ${vueComponentName}>;`);
2098
+ lines.push('');
2099
+ lines.push(`export default meta;`);
2100
+ lines.push(`type Story = StoryObj<typeof meta>;`);
2101
+ lines.push('');
2102
+ // Default story
2103
+ if (name === 'Dialog') {
2104
+ lines.push(`export const Default: Story = {`);
2105
+ lines.push(` render: (args: any) => ({`);
2106
+ lines.push(` components: { VueDialog, VueButton },`);
2107
+ lines.push(` setup() {`);
2108
+ lines.push(` const open = ref(false);`);
2109
+ lines.push(` return { args, open };`);
2110
+ lines.push(` },`);
2111
+ lines.push(` template: \``);
2112
+ lines.push(` <div>`);
2113
+ lines.push(` <VueButton variant="primary" @click="open = true">Open Dialog</VueButton>`);
2114
+ lines.push(` <VueDialog v-bind="args" v-model:open="open">`);
2115
+ lines.push(` <p>Dialog content goes here.</p>`);
2116
+ lines.push(` </VueDialog>`);
2117
+ lines.push(` </div>`);
2118
+ lines.push(` \`,`);
2119
+ lines.push(` }),`);
2120
+ lines.push(`};`);
2121
+ }
2122
+ else if (name === 'Drawer') {
2123
+ lines.push(`export const Default: Story = {`);
2124
+ lines.push(` render: (args: any) => ({`);
2125
+ lines.push(` components: { VueDrawer, VueButton },`);
2126
+ lines.push(` setup() {`);
2127
+ lines.push(` const open = ref(false);`);
2128
+ lines.push(` return { args, open };`);
2129
+ lines.push(` },`);
2130
+ lines.push(` template: \``);
2131
+ lines.push(` <div>`);
2132
+ lines.push(` <VueButton variant="primary" @click="open = true">Open Drawer</VueButton>`);
2133
+ lines.push(` <VueDrawer v-bind="args" v-model:open="open">`);
2134
+ lines.push(` <div style="padding: 1rem">Drawer content goes here.</div>`);
2135
+ lines.push(` </VueDrawer>`);
2136
+ lines.push(` </div>`);
2137
+ lines.push(` \`,`);
2138
+ lines.push(` }),`);
2139
+ lines.push(`};`);
2140
+ }
2141
+ else if (name === 'Toast') {
2142
+ lines.push(`export const Default: Story = {`);
2143
+ lines.push(` render: (args: any) => ({`);
2144
+ lines.push(` components: { VueToast, VueButton },`);
2145
+ lines.push(` setup() {`);
2146
+ lines.push(` const open = ref(false);`);
2147
+ lines.push(` return { args, open };`);
2148
+ lines.push(` },`);
2149
+ lines.push(` template: \``);
2150
+ lines.push(` <div>`);
2151
+ lines.push(` <VueButton variant="primary" @click="open = true">Show Toast</VueButton>`);
2152
+ lines.push(` <VueToast v-bind="args" v-model:open="open">`);
2153
+ lines.push(` <span>Toast notification message</span>`);
2154
+ lines.push(` </VueToast>`);
2155
+ lines.push(` </div>`);
2156
+ lines.push(` \`,`);
2157
+ lines.push(` }),`);
2158
+ lines.push(`};`);
2159
+ }
2160
+ else if (name === 'Collapsible') {
2161
+ lines.push(`export const Default: Story = {`);
2162
+ lines.push(` render: () => ({`);
2163
+ lines.push(` components: { VueCollapsible },`);
2164
+ lines.push(` template: \``);
2165
+ lines.push(` <VueCollapsible>`);
2166
+ lines.push(` <template #summary>Toggle details</template>`);
2167
+ lines.push(` <div>Collapsible content goes here.</div>`);
2168
+ lines.push(` </VueCollapsible>`);
2169
+ lines.push(` \`,`);
2170
+ lines.push(` }),`);
2171
+ lines.push(`};`);
2172
+ }
2173
+ else if (name === 'Button') {
2174
+ lines.push(`export const Default: Story = {};`);
2175
+ lines.push('');
2176
+ lines.push(`export const Variants: Story = {`);
2177
+ lines.push(` render: () => ({`);
2178
+ lines.push(` components: { VueButton },`);
2179
+ lines.push(` template: \``);
2180
+ lines.push(` <div style="display:flex;gap:8px;flex-wrap:wrap">`);
2181
+ lines.push(` <VueButton>Default</VueButton>`);
2182
+ lines.push(` <VueButton variant="primary">Primary</VueButton>`);
2183
+ lines.push(` <VueButton variant="secondary">Secondary</VueButton>`);
2184
+ lines.push(` <VueButton variant="success">Success</VueButton>`);
2185
+ lines.push(` <VueButton variant="warning">Warning</VueButton>`);
2186
+ lines.push(` <VueButton variant="danger">Danger</VueButton>`);
2187
+ lines.push(` <VueButton variant="monochrome">Monochrome</VueButton>`);
2188
+ lines.push(` </div>`);
2189
+ lines.push(` \`,`);
2190
+ lines.push(` }),`);
2191
+ lines.push(`};`);
2192
+ lines.push('');
2193
+ lines.push(`export const Sizes: Story = {`);
2194
+ lines.push(` render: () => ({`);
2195
+ lines.push(` components: { VueButton },`);
2196
+ lines.push(` template: \``);
2197
+ lines.push(` <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">`);
2198
+ lines.push(` <VueButton size="x-sm">x-sm</VueButton>`);
2199
+ lines.push(` <VueButton size="sm">sm</VueButton>`);
2200
+ lines.push(` <VueButton size="md">md</VueButton>`);
2201
+ lines.push(` <VueButton size="lg">lg</VueButton>`);
2202
+ lines.push(` <VueButton size="xl">xl</VueButton>`);
2203
+ lines.push(` </div>`);
2204
+ lines.push(` \`,`);
2205
+ lines.push(` }),`);
2206
+ lines.push(`};`);
2207
+ lines.push('');
2208
+ lines.push(`export const Bordered: Story = {`);
2209
+ lines.push(` render: () => ({`);
2210
+ lines.push(` components: { VueButton },`);
2211
+ lines.push(` template: \``);
2212
+ lines.push(` <div style="display:flex;gap:8px;flex-wrap:wrap">`);
2213
+ lines.push(` <VueButton bordered>Default</VueButton>`);
2214
+ lines.push(` <VueButton variant="primary" bordered>Primary</VueButton>`);
2215
+ lines.push(` <VueButton variant="success" bordered>Success</VueButton>`);
2216
+ lines.push(` <VueButton variant="danger" bordered>Danger</VueButton>`);
2217
+ lines.push(` </div>`);
2218
+ lines.push(` \`,`);
2219
+ lines.push(` }),`);
2220
+ lines.push(`};`);
2221
+ lines.push('');
2222
+ lines.push(`export const Disabled: Story = { args: { variant: 'primary', disabled: true } };`);
2223
+ lines.push(`export const Loading: Story = { args: { variant: 'primary', loading: true } };`);
2224
+ }
2225
+ else {
2226
+ lines.push(`export const Default: Story = {};`);
2227
+ }
2228
+ return STORY_FILE_HEADER + lines.join('\n') + '\n';
2229
+ }
2230
+ // ---------------------------------------------------------------------------
2231
+ // Lit (web-components) story generator
2232
+ // ---------------------------------------------------------------------------
2233
+ function toKebabCase(str) {
2234
+ return str.replace(/([A-Z])/g, (_m, p1, offset) => (offset > 0 ? '-' : '') + p1.toLowerCase());
2235
+ }
2236
+ function getAgTagName(name) {
2237
+ // Special overrides
2238
+ if (name === 'Flex')
2239
+ return 'ag-flex-row';
2240
+ return `ag-${toKebabCase(name)}`;
2241
+ }
2242
+ // ---------------------------------------------------------------------------
2243
+ // Lit story overrides — components that need custom slot content or imports
2244
+ // ---------------------------------------------------------------------------
2245
+ const LIT_STORY_OVERRIDES = {
2246
+ Flex: `import type { Meta, StoryObj } from '@storybook/web-components';
2247
+ import { html } from 'lit';
2248
+ import './index';
2249
+
2250
+ const meta: Meta = {
2251
+ title: 'AgnosticUI/Flex',
2252
+ component: 'ag-flex-row',
2253
+ tags: ['autodocs'],
2254
+ render: () => html\`
2255
+ <ag-flex-row style="--flex-gap: 1rem">
2256
+ <div style="padding: 1rem; background: #e0e7ff; border: 1px solid #6366f1;">Item 1</div>
2257
+ <div style="padding: 1rem; background: #dbeafe; border: 1px solid #3b82f6;">Item 2</div>
2258
+ <div style="padding: 1rem; background: #ddd6fe; border: 1px solid #8b5cf6;">Item 3</div>
2259
+ </ag-flex-row>
2260
+ \`,
2261
+ } satisfies Meta;
2262
+
2263
+ export default meta;
2264
+ type Story = StoryObj;
2265
+
2266
+ export const Default: Story = {};
2267
+
2268
+ export const Column: Story = {
2269
+ render: () => html\`
2270
+ <ag-flex-col style="--flex-gap: 1rem">
2271
+ <div style="padding: 1rem; background: #fef3c7; border: 1px solid #f59e0b;">Row 1</div>
2272
+ <div style="padding: 1rem; background: #fed7aa; border: 1px solid #ea580c;">Row 2</div>
2273
+ <div style="padding: 1rem; background: #fecaca; border: 1px solid #ef4444;">Row 3</div>
2274
+ </ag-flex-col>
2275
+ \`,
2276
+ };
2277
+ `,
2278
+ Header: `import type { Meta, StoryObj } from '@storybook/web-components';
2279
+ import { html } from 'lit';
2280
+ import './Header';
2281
+
2282
+ const meta: Meta = {
2283
+ title: 'AgnosticUI/Header',
2284
+ component: 'ag-header',
2285
+ tags: ['autodocs'],
2286
+ render: () => html\`
2287
+ <ag-header>
2288
+ <a href="#" slot="logo" style="text-decoration: none; color: inherit; font-weight: 700; font-size: 1.25rem;">
2289
+ AgnosticUI
2290
+ </a>
2291
+ <nav>
2292
+ <ul style="display: flex; gap: 2rem; list-style: none; margin: 0; padding: 0;">
2293
+ <li><a href="#home" style="text-decoration: none; color: inherit;">Home</a></li>
2294
+ <li><a href="#about" style="text-decoration: none; color: inherit;">About</a></li>
2295
+ <li><a href="#docs" style="text-decoration: none; color: inherit;">Docs</a></li>
2296
+ </ul>
2297
+ </nav>
2298
+ </ag-header>
2299
+ \`,
2300
+ } satisfies Meta;
2301
+
2302
+ export default meta;
2303
+ type Story = StoryObj;
2304
+
2305
+ export const Default: Story = {};
2306
+
2307
+ export const WithLogo: Story = {
2308
+ render: () => html\`
2309
+ <ag-header>
2310
+ <a href="#" slot="logo" style="display: flex; align-items: center; gap: 0.5rem; text-decoration: none; color: inherit;">
2311
+ <svg width="28" height="28" viewBox="0 0 32 32">
2312
+ <circle cx="16" cy="16" r="14" fill="var(--ag-primary, #6366f1)" />
2313
+ <text x="16" y="22" text-anchor="middle" fill="white" font-size="16" font-weight="bold">A</text>
2314
+ </svg>
2315
+ <span style="font-weight: 700; font-size: 1.25rem;">MyApp</span>
2316
+ </a>
2317
+ <nav>
2318
+ <ul style="display: flex; gap: 2rem; list-style: none; margin: 0; padding: 0;">
2319
+ <li><a href="#home" style="text-decoration: none; color: inherit;">Home</a></li>
2320
+ <li><a href="#features" style="text-decoration: none; color: inherit;">Features</a></li>
2321
+ <li><a href="#pricing" style="text-decoration: none; color: inherit;">Pricing</a></li>
2322
+ </ul>
2323
+ </nav>
2324
+ </ag-header>
2325
+ \`,
2326
+ };
2327
+ `,
2328
+ Image: `import type { Meta, StoryObj } from '@storybook/web-components';
2329
+ import { html } from 'lit';
2330
+ import './Image';
2331
+
2332
+ const meta: Meta = {
2333
+ title: 'AgnosticUI/Image',
2334
+ component: 'ag-image',
2335
+ tags: ['autodocs'],
2336
+ render: () => html\`
2337
+ <ag-image
2338
+ .src=\${'https://picsum.photos/800/400'}
2339
+ .alt=\${'Sample landscape photo'}
2340
+ .width=\${800}
2341
+ .height=\${400}
2342
+ ></ag-image>
2343
+ \`,
2344
+ } satisfies Meta;
2345
+
2346
+ export default meta;
2347
+ type Story = StoryObj;
2348
+
2349
+ export const Default: Story = {};
2350
+
2351
+ export const WithFade: Story = {
2352
+ render: () => html\`
2353
+ <ag-image
2354
+ .src=\${'https://picsum.photos/600/400?random=2'}
2355
+ .alt=\${'Fading image'}
2356
+ .width=\${600}
2357
+ .height=\${400}
2358
+ .fade=\${true}
2359
+ .duration=\${500}
2360
+ ></ag-image>
2361
+ \`,
2362
+ };
2363
+
2364
+ export const WithAspectRatio: Story = {
2365
+ render: () => html\`
2366
+ <ag-image
2367
+ .src=\${'https://picsum.photos/1200/675?random=3'}
2368
+ .alt=\${'Wide image'}
2369
+ .aspectRatio=\${'16/9'}
2370
+ .fit=\${'cover'}
2371
+ style="width: 100%; max-width: 800px; display: block;"
2372
+ ></ag-image>
2373
+ \`,
2374
+ };
2375
+ `,
2376
+ Select: `import type { Meta, StoryObj } from '@storybook/web-components';
2377
+ import { html } from 'lit';
2378
+ import './Select';
2379
+
2380
+ const meta: Meta = {
2381
+ title: 'AgnosticUI/Select',
2382
+ component: 'ag-select',
2383
+ tags: ['autodocs'],
2384
+ argTypes: {
2385
+ size: { control: 'select', options: ['', 'small', 'large'] },
2386
+ disabled: { control: 'boolean' },
2387
+ multiple: { control: 'boolean' },
2388
+ invalid: { control: 'boolean' },
2389
+ required: { control: 'boolean' },
2390
+ },
2391
+ render: (args: any) => html\`
2392
+ <div style="max-width: 300px;">
2393
+ <label for="player-select" style="display: block; margin-bottom: 0.5rem; font-weight: 600;">
2394
+ Greatest Tennis Player
2395
+ </label>
2396
+ <ag-select
2397
+ id="player-select"
2398
+ name="tennis"
2399
+ .size=\${args.size || ''}
2400
+ ?disabled=\${args.disabled}
2401
+ ?multiple=\${args.multiple}
2402
+ ?invalid=\${args.invalid}
2403
+ ?required=\${args.required}
2404
+ >
2405
+ <option value="">- Select a player -</option>
2406
+ <option value="federer">Roger Federer</option>
2407
+ <option value="nadal">Rafael Nadal</option>
2408
+ <option value="djokovic">Novak Djokovic</option>
2409
+ <option value="agassi">Andre Agassi</option>
2410
+ <option value="sampras">Pete Sampras</option>
2411
+ </ag-select>
2412
+ </div>
2413
+ \`,
2414
+ } satisfies Meta;
2415
+
2416
+ export default meta;
2417
+ type Story = StoryObj;
2418
+
2419
+ export const Default: Story = {};
2420
+
2421
+ export const Multiple: Story = {
2422
+ render: () => html\`
2423
+ <div style="max-width: 300px;">
2424
+ <label style="display: block; margin-bottom: 0.5rem; font-weight: 600;">Multiple Select</label>
2425
+ <ag-select name="players" .multiple=\${true} .multipleSize=\${4}>
2426
+ <option value="federer">Roger Federer</option>
2427
+ <option value="nadal">Rafael Nadal</option>
2428
+ <option value="djokovic">Novak Djokovic</option>
2429
+ <option value="agassi">Andre Agassi</option>
2430
+ </ag-select>
2431
+ </div>
2432
+ \`,
2433
+ };
2434
+ `,
2435
+ Progress: `import type { Meta, StoryObj } from '@storybook/web-components';
2436
+ import { html } from 'lit';
2437
+ import './Progress';
2438
+
2439
+ const meta: Meta = {
2440
+ title: 'AgnosticUI/Progress',
2441
+ component: 'ag-progress',
2442
+ tags: ['autodocs'],
2443
+ argTypes: {
2444
+ value: { control: { type: 'number', min: 0, max: 100 } },
2445
+ max: { control: { type: 'number' } },
2446
+ label: { control: 'text' },
2447
+ },
2448
+ args: {
2449
+ value: 60,
2450
+ max: 100,
2451
+ label: 'Loading progress',
2452
+ },
2453
+ render: (args: any) => html\`
2454
+ <ag-progress
2455
+ .value=\${args.value}
2456
+ .max=\${args.max}
2457
+ .label=\${args.label}
2458
+ ></ag-progress>
2459
+ \`,
2460
+ } satisfies Meta;
2461
+
2462
+ export default meta;
2463
+ type Story = StoryObj;
2464
+
2465
+ export const Default: Story = {};
2466
+
2467
+ export const Variants: Story = {
2468
+ render: () => html\`
2469
+ <div style="display: flex; flex-direction: column; gap: 1.5rem; max-width: 500px;">
2470
+ <ag-progress .value=\${25} .max=\${100} .label=\${'25%'}></ag-progress>
2471
+ <ag-progress .value=\${50} .max=\${100} .label=\${'50%'}></ag-progress>
2472
+ <ag-progress .value=\${75} .max=\${100} .label=\${'75%'}></ag-progress>
2473
+ <ag-progress .value=\${100} .max=\${100} .label=\${'Complete'}></ag-progress>
2474
+ </div>
2475
+ \`,
2476
+ };
2477
+ `,
2478
+ ProgressRing: `import type { Meta, StoryObj } from '@storybook/web-components';
2479
+ import { html } from 'lit';
2480
+ import './ProgressRing';
2481
+
2482
+ const meta: Meta = {
2483
+ title: 'AgnosticUI/ProgressRing',
2484
+ component: 'ag-progress-ring',
2485
+ tags: ['autodocs'],
2486
+ argTypes: {
2487
+ value: { control: { type: 'range', min: 0, max: 100, step: 1 } },
2488
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
2489
+ variant: { control: 'select', options: ['primary', 'success', 'warning', 'danger', 'info'] },
2490
+ label: { control: 'text' },
2491
+ },
2492
+ args: {
2493
+ value: 65,
2494
+ size: 'medium',
2495
+ variant: 'primary',
2496
+ label: 'Progress',
2497
+ },
2498
+ render: (args: any) => html\`
2499
+ <ag-progress-ring
2500
+ .value=\${args.value}
2501
+ .size=\${args.size}
2502
+ .variant=\${args.variant}
2503
+ .label=\${args.label}
2504
+ ></ag-progress-ring>
2505
+ \`,
2506
+ } satisfies Meta;
2507
+
2508
+ export default meta;
2509
+ type Story = StoryObj;
2510
+
2511
+ export const Default: Story = {};
2512
+
2513
+ export const Variants: Story = {
2514
+ render: () => html\`
2515
+ <div style="display: flex; gap: 2rem; flex-wrap: wrap; align-items: center;">
2516
+ <ag-progress-ring .value=\${65} size="medium" variant="primary" label="Primary"></ag-progress-ring>
2517
+ <ag-progress-ring .value=\${80} size="medium" variant="success" label="Success"></ag-progress-ring>
2518
+ <ag-progress-ring .value=\${45} size="medium" variant="warning" label="Warning"></ag-progress-ring>
2519
+ <ag-progress-ring .value=\${30} size="medium" variant="danger" label="Danger"></ag-progress-ring>
2520
+ </div>
2521
+ \`,
2522
+ };
2523
+
2524
+ export const Sizes: Story = {
2525
+ render: () => html\`
2526
+ <div style="display: flex; gap: 2rem; align-items: center;">
2527
+ <ag-progress-ring .value=\${65} size="small" variant="primary" label="Small"></ag-progress-ring>
2528
+ <ag-progress-ring .value=\${65} size="medium" variant="primary" label="Medium"></ag-progress-ring>
2529
+ <ag-progress-ring .value=\${65} size="large" variant="primary" label="Large"></ag-progress-ring>
2530
+ </div>
2531
+ \`,
2532
+ };
2533
+ `,
2534
+ Accordion: `import type { Meta, StoryObj } from '@storybook/web-components';
2535
+ import { html } from 'lit';
2536
+ import './Accordion';
2537
+
2538
+ const meta: Meta = {
2539
+ title: 'AgnosticUI/Accordion',
2540
+ component: 'ag-accordion',
2541
+ tags: ['autodocs'],
2542
+ render: () => html\`
2543
+ <ag-accordion style="max-width: 600px;">
2544
+ <ag-accordion-item .open=\${true}>
2545
+ <span slot="header">Getting Started</span>
2546
+ <div slot="content">
2547
+ <p>AgnosticUI is a component library that works across React, Vue, and Lit. Install it with the CLI and add components to your project.</p>
2548
+ </div>
2549
+ </ag-accordion-item>
2550
+ <ag-accordion-item>
2551
+ <span slot="header">Configuration</span>
2552
+ <div slot="content">
2553
+ <p>Configure your project by editing the agnosticui.config.json file in your project root. Add components as needed.</p>
2554
+ </div>
2555
+ </ag-accordion-item>
2556
+ <ag-accordion-item>
2557
+ <span slot="header">Theming</span>
2558
+ <div slot="content">
2559
+ <p>Customize the appearance using CSS custom properties. Override tokens in your stylesheet to match your brand.</p>
2560
+ </div>
2561
+ </ag-accordion-item>
2562
+ </ag-accordion>
2563
+ \`,
2564
+ } satisfies Meta;
2565
+
2566
+ export default meta;
2567
+ type Story = StoryObj;
2568
+
2569
+ export const Default: Story = {};
2570
+
2571
+ export const Bordered: Story = {
2572
+ render: () => html\`
2573
+ <ag-accordion style="max-width: 600px;">
2574
+ <ag-accordion-item .open=\${true} .bordered=\${true}>
2575
+ <span slot="header">Section One</span>
2576
+ <div slot="content"><p>Content for section one goes here.</p></div>
2577
+ </ag-accordion-item>
2578
+ <ag-accordion-item .bordered=\${true}>
2579
+ <span slot="header">Section Two</span>
2580
+ <div slot="content"><p>Content for section two goes here.</p></div>
2581
+ </ag-accordion-item>
2582
+ </ag-accordion>
2583
+ \`,
2584
+ };
2585
+ `,
2586
+ Breadcrumb: `import type { Meta, StoryObj } from '@storybook/web-components';
2587
+ import { html } from 'lit';
2588
+ import './Breadcrumb';
2589
+
2590
+ const ITEMS = [
2591
+ { label: 'Home', href: '#' },
2592
+ { label: 'Products', href: '#products' },
2593
+ { label: 'Electronics', href: '#electronics' },
2594
+ { label: 'Laptops', current: true },
2595
+ ];
2596
+
2597
+ const meta: Meta = {
2598
+ title: 'AgnosticUI/Breadcrumb',
2599
+ component: 'ag-breadcrumb',
2600
+ tags: ['autodocs'],
2601
+ argTypes: {
2602
+ type: { control: 'select', options: ['default', 'slash', 'bullet', 'arrow'] },
2603
+ primary: { control: 'boolean' },
2604
+ },
2605
+ args: {
2606
+ type: 'default',
2607
+ primary: false,
2608
+ },
2609
+ render: (args: any) => html\`
2610
+ <ag-breadcrumb
2611
+ .items=\${ITEMS}
2612
+ .type=\${args.type}
2613
+ ?primary=\${args.primary}
2614
+ ></ag-breadcrumb>
2615
+ \`,
2616
+ } satisfies Meta;
2617
+
2618
+ export default meta;
2619
+ type Story = StoryObj;
2620
+
2621
+ export const Default: Story = {};
2622
+
2623
+ export const Slash: Story = { args: { type: 'slash' } };
2624
+ export const Arrow: Story = { args: { type: 'arrow' } };
2625
+ export const Bullet: Story = { args: { type: 'bullet' } };
2626
+ `,
2627
+ Icon: `import type { Meta, StoryObj } from '@storybook/web-components';
2628
+ import { html } from 'lit';
2629
+ import './Icon';
2630
+
2631
+ const STAR_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>\`;
2632
+ const INFO_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>\`;
2633
+ const CHECK_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>\`;
2634
+
2635
+ const meta: Meta = {
2636
+ title: 'AgnosticUI/Icon',
2637
+ component: 'ag-icon',
2638
+ tags: ['autodocs'],
2639
+ argTypes: {
2640
+ size: { control: 'select', options: ['14', '16', '18', '20', '24', '32', '36', '40', '48'] },
2641
+ type: { control: 'select', options: ['', 'info', 'action', 'success', 'warning', 'error'] },
2642
+ },
2643
+ args: { size: '24', type: '' },
2644
+ render: (args: any) => html\`
2645
+ <ag-icon .size=\${args.size} .type=\${args.type} no-fill>
2646
+ \${STAR_SVG}
2647
+ </ag-icon>
2648
+ \`,
2649
+ } satisfies Meta;
2650
+
2651
+ export default meta;
2652
+ type Story = StoryObj;
2653
+
2654
+ export const Default: Story = {};
2655
+
2656
+ export const Types: Story = {
2657
+ render: () => html\`
2658
+ <div style="display: flex; gap: 1rem; align-items: center;">
2659
+ <ag-icon size="24" no-fill>\${STAR_SVG}</ag-icon>
2660
+ <ag-icon size="24" type="info" no-fill>\${INFO_SVG}</ag-icon>
2661
+ <ag-icon size="24" type="success" no-fill>\${CHECK_SVG}</ag-icon>
2662
+ </div>
2663
+ \`,
2664
+ };
2665
+
2666
+ export const Sizes: Story = {
2667
+ render: () => html\`
2668
+ <div style="display: flex; gap: 1rem; align-items: center;">
2669
+ <ag-icon size="14" no-fill>\${STAR_SVG}</ag-icon>
2670
+ <ag-icon size="18" no-fill>\${STAR_SVG}</ag-icon>
2671
+ <ag-icon size="24" no-fill>\${STAR_SVG}</ag-icon>
2672
+ <ag-icon size="32" no-fill>\${STAR_SVG}</ag-icon>
2673
+ <ag-icon size="48" no-fill>\${STAR_SVG}</ag-icon>
2674
+ </div>
2675
+ \`,
2676
+ };
2677
+ `,
2678
+ IconButton: `import type { Meta, StoryObj } from '@storybook/web-components';
2679
+ import { html } from 'lit';
2680
+ import './IconButton';
2681
+
2682
+ const CLOSE_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>\`;
2683
+ const SETTINGS_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>\`;
2684
+ const MENU_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg>\`;
2685
+
2686
+ const meta: Meta = {
2687
+ title: 'AgnosticUI/IconButton',
2688
+ component: 'ag-icon-button',
2689
+ tags: ['autodocs'],
2690
+ argTypes: {
2691
+ label: { control: 'text' },
2692
+ size: { control: 'select', options: ['xs', 'sm', 'md', 'lg', 'xl'] },
2693
+ variant: { control: 'select', options: ['', 'primary', 'secondary', 'ghost', 'danger'] },
2694
+ disabled: { control: 'boolean' },
2695
+ loading: { control: 'boolean' },
2696
+ },
2697
+ args: { label: 'Close', size: 'md', variant: '' },
2698
+ render: (args: any) => html\`
2699
+ <ag-icon-button
2700
+ .label=\${args.label}
2701
+ .size=\${args.size}
2702
+ .variant=\${args.variant}
2703
+ ?disabled=\${args.disabled}
2704
+ ?loading=\${args.loading}
2705
+ >
2706
+ \${CLOSE_SVG}
2707
+ </ag-icon-button>
2708
+ \`,
2709
+ } satisfies Meta;
2710
+
2711
+ export default meta;
2712
+ type Story = StoryObj;
2713
+
2714
+ export const Default: Story = {};
2715
+
2716
+ export const Variants: Story = {
2717
+ render: () => html\`
2718
+ <div style="display: flex; gap: 1rem; align-items: center;">
2719
+ <ag-icon-button label="Default">\${CLOSE_SVG}</ag-icon-button>
2720
+ <ag-icon-button label="Settings" variant="primary">\${SETTINGS_SVG}</ag-icon-button>
2721
+ <ag-icon-button label="Menu" variant="secondary">\${MENU_SVG}</ag-icon-button>
2722
+ <ag-icon-button label="Ghost" variant="ghost">\${CLOSE_SVG}</ag-icon-button>
2723
+ </div>
2724
+ \`,
2725
+ };
2726
+
2727
+ export const Sizes: Story = {
2728
+ render: () => html\`
2729
+ <div style="display: flex; gap: 1rem; align-items: center;">
2730
+ <ag-icon-button label="XS" size="xs">\${CLOSE_SVG}</ag-icon-button>
2731
+ <ag-icon-button label="SM" size="sm">\${CLOSE_SVG}</ag-icon-button>
2732
+ <ag-icon-button label="MD" size="md">\${CLOSE_SVG}</ag-icon-button>
2733
+ <ag-icon-button label="LG" size="lg">\${CLOSE_SVG}</ag-icon-button>
2734
+ <ag-icon-button label="XL" size="xl">\${CLOSE_SVG}</ag-icon-button>
2735
+ </div>
2736
+ \`,
2737
+ };
2738
+ `,
2739
+ IconButtonFx: `import type { Meta, StoryObj } from '@storybook/web-components';
2740
+ import { html } from 'lit';
2741
+ import './IconButtonFx';
2742
+
2743
+ const HEART_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>\`;
2744
+ const STAR_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>\`;
2745
+
2746
+ const meta: Meta = {
2747
+ title: 'AgnosticUI/IconButtonFx',
2748
+ component: 'ag-icon-button-fx',
2749
+ tags: ['autodocs'],
2750
+ argTypes: {
2751
+ label: { control: 'text' },
2752
+ fx: { control: 'select', options: ['', 'pulse', 'bounce', 'jelly', 'press-pop', 'slide-in', 'grow', 'shrink', 'push', 'shake', 'wobble'] },
2753
+ size: { control: 'select', options: ['xs', 'sm', 'md', 'lg', 'xl'] },
2754
+ disabled: { control: 'boolean' },
2755
+ },
2756
+ args: { label: 'Like', fx: 'bounce', size: 'md' },
2757
+ render: (args: any) => html\`
2758
+ <ag-icon-button-fx
2759
+ .label=\${args.label}
2760
+ .fx=\${args.fx}
2761
+ .size=\${args.size}
2762
+ ?disabled=\${args.disabled}
2763
+ >
2764
+ \${HEART_SVG}
2765
+ </ag-icon-button-fx>
2766
+ \`,
2767
+ } satisfies Meta;
2768
+
2769
+ export default meta;
2770
+ type Story = StoryObj;
2771
+
2772
+ export const Default: Story = {};
2773
+
2774
+ export const FxVariants: Story = {
2775
+ render: () => html\`
2776
+ <div style="display: flex; gap: 1.5rem; flex-wrap: wrap; align-items: center;">
2777
+ <ag-icon-button-fx label="Bounce" fx="bounce">\${HEART_SVG}</ag-icon-button-fx>
2778
+ <ag-icon-button-fx label="Pulse" fx="pulse">\${STAR_SVG}</ag-icon-button-fx>
2779
+ <ag-icon-button-fx label="Jelly" fx="jelly">\${HEART_SVG}</ag-icon-button-fx>
2780
+ <ag-icon-button-fx label="Shake" fx="shake">\${STAR_SVG}</ag-icon-button-fx>
2781
+ </div>
2782
+ \`,
2783
+ };
2784
+ `,
2785
+ Menu: `import type { Meta, StoryObj } from '@storybook/web-components';
2786
+ import { html } from 'lit';
2787
+ import './Menu';
2788
+
2789
+ const meta: Meta = {
2790
+ title: 'AgnosticUI/Menu',
2791
+ component: 'ag-menu-button',
2792
+ tags: ['autodocs'],
2793
+ argTypes: {
2794
+ label: { control: 'text' },
2795
+ menuVariant: { control: 'select', options: ['chevron', 'button', 'icon'] },
2796
+ size: { control: 'select', options: ['x-sm', 'sm', 'md', 'lg', 'xl'] },
2797
+ disabled: { control: 'boolean' },
2798
+ },
2799
+ args: { label: 'Actions', menuVariant: 'chevron', size: 'md' },
2800
+ render: (args: any) => html\`
2801
+ <div style="padding: 3rem;">
2802
+ <ag-menu-button
2803
+ .menuVariant=\${args.menuVariant}
2804
+ .size=\${args.size}
2805
+ ?disabled=\${args.disabled}
2806
+ >
2807
+ \${args.label}
2808
+ <ag-menu slot="menu" .ariaLabel=\${'Menu options'}>
2809
+ <ag-menu-item .value=\${'edit'}>Edit</ag-menu-item>
2810
+ <ag-menu-item .value=\${'duplicate'}>Duplicate</ag-menu-item>
2811
+ <ag-menu-separator></ag-menu-separator>
2812
+ <ag-menu-item .value=\${'delete'}>Delete</ag-menu-item>
2813
+ </ag-menu>
2814
+ </ag-menu-button>
2815
+ </div>
2816
+ \`,
2817
+ } satisfies Meta;
2818
+
2819
+ export default meta;
2820
+ type Story = StoryObj;
2821
+
2822
+ export const Default: Story = {};
2823
+
2824
+ export const Variants: Story = {
2825
+ render: () => html\`
2826
+ <div style="padding: 3rem; display: flex; gap: 2rem; flex-wrap: wrap;">
2827
+ <ag-menu-button .menuVariant=\${'chevron'}>
2828
+ Chevron
2829
+ <ag-menu slot="menu" .ariaLabel=\${'Options'}>
2830
+ <ag-menu-item .value=\${'a'}>Option A</ag-menu-item>
2831
+ <ag-menu-item .value=\${'b'}>Option B</ag-menu-item>
2832
+ <ag-menu-item .value=\${'c'}>Option C</ag-menu-item>
2833
+ </ag-menu>
2834
+ </ag-menu-button>
2835
+ <ag-menu-button .menuVariant=\${'button'}>
2836
+ Button
2837
+ <ag-menu slot="menu" .ariaLabel=\${'Options'}>
2838
+ <ag-menu-item .value=\${'a'}>Option A</ag-menu-item>
2839
+ <ag-menu-item .value=\${'b'}>Option B</ag-menu-item>
2840
+ </ag-menu>
2841
+ </ag-menu-button>
2842
+ <ag-menu-button .menuVariant=\${'icon'}>
2843
+ Menu
2844
+ <ag-menu slot="menu" .ariaLabel=\${'Options'}>
2845
+ <ag-menu-item .value=\${'a'}>Option A</ag-menu-item>
2846
+ <ag-menu-item .value=\${'b'}>Option B</ag-menu-item>
2847
+ </ag-menu>
2848
+ </ag-menu-button>
2849
+ </div>
2850
+ \`,
2851
+ };
2852
+ `,
2853
+ Fieldset: `import type { Meta, StoryObj } from '@storybook/web-components';
2854
+ import { html } from 'lit';
2855
+ import './Fieldset';
2856
+ import '../../Input/core/Input';
2857
+
2858
+ const meta: Meta = {
2859
+ title: 'AgnosticUI/Fieldset',
2860
+ component: 'ag-fieldset',
2861
+ tags: ['autodocs'],
2862
+ argTypes: {
2863
+ legend: { control: 'text' },
2864
+ bordered: { control: 'boolean' },
2865
+ layout: { control: 'select', options: ['vertical', 'horizontal'] },
2866
+ },
2867
+ args: { legend: 'Personal Information', bordered: false, layout: 'vertical' },
2868
+ render: (args: any) => html\`
2869
+ <ag-fieldset
2870
+ .legend=\${args.legend}
2871
+ ?bordered=\${args.bordered}
2872
+ .layout=\${args.layout}
2873
+ >
2874
+ <ag-input id="first-name" name="first-name" label="First Name" placeholder="Jane"></ag-input>
2875
+ <ag-input id="last-name" name="last-name" label="Last Name" placeholder="Doe"></ag-input>
2876
+ <ag-input id="email" name="email" label="Email" type="email" placeholder="jane@example.com"></ag-input>
2877
+ </ag-fieldset>
2878
+ \`,
2879
+ } satisfies Meta;
2880
+
2881
+ export default meta;
2882
+ type Story = StoryObj;
2883
+
2884
+ export const Default: Story = {};
2885
+
2886
+ export const Bordered: Story = { args: { bordered: true } };
2887
+ `,
2888
+ IntlFormatter: `import type { Meta, StoryObj } from '@storybook/web-components';
2889
+ import { html } from 'lit';
2890
+ import './IntlFormatter';
2891
+
2892
+ const meta: Meta = {
2893
+ title: 'AgnosticUI/IntlFormatter',
2894
+ component: 'ag-intl-formatter',
2895
+ tags: ['autodocs'],
2896
+ argTypes: {
2897
+ type: { control: 'select', options: ['number', 'date', 'currency', 'percent'] },
2898
+ lang: { control: 'text' },
2899
+ value: { control: 'text' },
2900
+ },
2901
+ args: { type: 'number', value: '1234567.89', lang: 'en-US' },
2902
+ render: (args: any) => html\`
2903
+ <ag-intl-formatter
2904
+ .type=\${args.type}
2905
+ .value=\${args.value}
2906
+ .lang=\${args.lang}
2907
+ ></ag-intl-formatter>
2908
+ \`,
2909
+ } satisfies Meta;
2910
+
2911
+ export default meta;
2912
+ type Story = StoryObj;
2913
+
2914
+ export const Default: Story = {};
2915
+
2916
+ export const Currency: Story = {
2917
+ render: () => html\`
2918
+ <div style="display: flex; flex-direction: column; gap: 0.5rem;">
2919
+ <ag-intl-formatter .type=\${'currency'} .value=\${'1234.56'} .lang=\${'en-US'} .currency=\${'USD'}></ag-intl-formatter>
2920
+ <ag-intl-formatter .type=\${'currency'} .value=\${'1234.56'} .lang=\${'de-DE'} .currency=\${'EUR'}></ag-intl-formatter>
2921
+ </div>
2922
+ \`,
2923
+ };
2924
+
2925
+ export const DateFormat: Story = {
2926
+ render: () => html\`
2927
+ <ag-intl-formatter .type=\${'date'} .date=\${new Date().toISOString()} .lang=\${'en-US'} .dateStyle=\${'long'}></ag-intl-formatter>
2928
+ \`,
2929
+ };
2930
+ `,
2931
+ Popover: `import type { Meta, StoryObj } from '@storybook/web-components';
2932
+ import { html } from 'lit';
2933
+ import './Popover';
2934
+ import '../../Button/core/Button';
2935
+
2936
+ const meta: Meta = {
2937
+ title: 'AgnosticUI/Popover',
2938
+ component: 'ag-popover',
2939
+ tags: ['autodocs'],
2940
+ argTypes: {
2941
+ placement: { control: 'select', options: ['top', 'bottom', 'left', 'right', 'top-start', 'top-end', 'bottom-start', 'bottom-end'] },
2942
+ arrow: { control: 'boolean' },
2943
+ triggerType: { control: 'select', options: ['click', 'hover', 'focus'] },
2944
+ showCloseButton: { control: 'boolean' },
2945
+ },
2946
+ args: { placement: 'bottom', arrow: true, triggerType: 'click', showCloseButton: true },
2947
+ render: (args: any) => html\`
2948
+ <div style="padding: 4rem; display: flex; justify-content: center;">
2949
+ <ag-popover
2950
+ .placement=\${args.placement}
2951
+ ?arrow=\${args.arrow}
2952
+ .triggerType=\${args.triggerType}
2953
+ ?showCloseButton=\${args.showCloseButton}
2954
+ .closeLabel=\${'Close'}
2955
+ >
2956
+ <ag-button slot="trigger">Open Popover</ag-button>
2957
+ <span slot="title">Popover Title</span>
2958
+ <div slot="content" style="padding: 0.5rem 0;">
2959
+ <p>This is the popover content. It can contain any HTML.</p>
2960
+ </div>
2961
+ </ag-popover>
2962
+ </div>
2963
+ \`,
2964
+ } satisfies Meta;
2965
+
2966
+ export default meta;
2967
+ type Story = StoryObj;
2968
+
2969
+ export const Default: Story = {};
2970
+
2971
+ export const HoverTrigger: Story = {
2972
+ args: { triggerType: 'hover', placement: 'top', arrow: true },
2973
+ };
2974
+ `,
2975
+ Tabs: `import type { Meta, StoryObj } from '@storybook/web-components';
2976
+ import { html } from 'lit';
2977
+ import './Tabs';
2978
+
2979
+ const meta: Meta = {
2980
+ title: 'AgnosticUI/Tabs',
2981
+ component: 'ag-tabs',
2982
+ tags: ['autodocs'],
2983
+ argTypes: {
2984
+ orientation: { control: 'select', options: ['horizontal', 'vertical'] },
2985
+ activation: { control: 'select', options: ['manual', 'automatic'] },
2986
+ },
2987
+ args: { orientation: 'horizontal', activation: 'manual' },
2988
+ render: (args: any) => html\`
2989
+ <ag-tabs
2990
+ aria-label="Example Tabs"
2991
+ .orientation=\${args.orientation}
2992
+ .activation=\${args.activation}
2993
+ >
2994
+ <ag-tab slot="tab" panel="p1">Overview</ag-tab>
2995
+ <ag-tab slot="tab" panel="p2">Features</ag-tab>
2996
+ <ag-tab slot="tab" panel="p3">Pricing</ag-tab>
2997
+ <ag-tab-panel slot="panel" panel="p1">
2998
+ <div style="padding: 1rem;">
2999
+ <h3>Overview</h3>
3000
+ <p>AgnosticUI is a framework-agnostic component library that works with React, Vue, and Lit.</p>
3001
+ </div>
3002
+ </ag-tab-panel>
3003
+ <ag-tab-panel slot="panel" panel="p2">
3004
+ <div style="padding: 1rem;">
3005
+ <h3>Features</h3>
3006
+ <ul><li>Framework agnostic</li><li>Accessible by default</li><li>Themeable via CSS tokens</li></ul>
3007
+ </div>
3008
+ </ag-tab-panel>
3009
+ <ag-tab-panel slot="panel" panel="p3">
3010
+ <div style="padding: 1rem;">
3011
+ <h3>Pricing</h3>
3012
+ <p>Free and open source under the Apache 2.0 license.</p>
3013
+ </div>
3014
+ </ag-tab-panel>
3015
+ </ag-tabs>
3016
+ \`,
3017
+ } satisfies Meta;
3018
+
3019
+ export default meta;
3020
+ type Story = StoryObj;
3021
+
3022
+ export const Default: Story = {};
3023
+
3024
+ export const Vertical: Story = { args: { orientation: 'vertical' } };
3025
+ `,
3026
+ Timeline: `import type { Meta, StoryObj } from '@storybook/web-components';
3027
+ import { html } from 'lit';
3028
+ import './Timeline';
3029
+ import '../../Icon/core/Icon';
3030
+
3031
+ const CHECK_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>\`;
3032
+ const INFO_SVG = html\`<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>\`;
3033
+
3034
+ const meta: Meta = {
3035
+ title: 'AgnosticUI/Timeline',
3036
+ component: 'ag-timeline',
3037
+ tags: ['autodocs'],
3038
+ argTypes: {
3039
+ orientation: { control: 'select', options: ['horizontal', 'vertical'] },
3040
+ variant: { control: 'select', options: ['', 'primary', 'success', 'warning', 'danger'] },
3041
+ compact: { control: 'boolean' },
3042
+ },
3043
+ args: { orientation: 'horizontal', variant: '', compact: false },
3044
+ render: (args: any) => html\`
3045
+ <ag-timeline
3046
+ orientation=\${args.orientation}
3047
+ variant=\${args.variant}
3048
+ ?compact=\${args.compact}
3049
+ >
3050
+ <ag-timeline-item>
3051
+ <div slot="ag-start">Jan 2024</div>
3052
+ <div slot="ag-marker">
3053
+ <ag-icon type="success" size="18">\${CHECK_SVG}</ag-icon>
3054
+ </div>
3055
+ <div slot="ag-end">Project Kickoff</div>
3056
+ </ag-timeline-item>
3057
+ <ag-timeline-item>
3058
+ <div slot="ag-start">Mar 2024</div>
3059
+ <div slot="ag-marker">
3060
+ <ag-icon type="info" size="18">\${INFO_SVG}</ag-icon>
3061
+ </div>
3062
+ <div slot="ag-end">Beta Release</div>
3063
+ </ag-timeline-item>
3064
+ <ag-timeline-item>
3065
+ <div slot="ag-start">Jun 2024</div>
3066
+ <div slot="ag-marker">
3067
+ <ag-icon type="success" size="18">\${CHECK_SVG}</ag-icon>
3068
+ </div>
3069
+ <div slot="ag-end">v1.0 Launch</div>
3070
+ </ag-timeline-item>
3071
+ </ag-timeline>
3072
+ \`,
3073
+ } satisfies Meta;
3074
+
3075
+ export default meta;
3076
+ type Story = StoryObj;
3077
+
3078
+ export const Default: Story = {};
3079
+
3080
+ export const Vertical: Story = { args: { orientation: 'vertical' } };
3081
+ `,
3082
+ Sidebar: `import type { Meta, StoryObj } from '@storybook/web-components';
3083
+ import { html } from 'lit';
3084
+ import './Sidebar';
3085
+ import '../../SidebarNav/core/SidebarNav';
3086
+ import '../../Popover/core/Popover';
3087
+
3088
+ const HOME_SVG = \`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>\`;
3089
+ const FOLDER_SVG = \`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>\`;
3090
+ const USER_SVG = \`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>\`;
3091
+ const SETTINGS_SVG = \`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>\`;
3092
+ const CHEVRON_RIGHT_SVG = \`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>\`;
3093
+ const PANEL_TOGGLE_SVG = \`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/></svg>\`;
3094
+
3095
+ const NAV_STYLES = \`
3096
+ .nav-button {
3097
+ display: flex;
3098
+ align-items: center;
3099
+ gap: var(--ag-space-3);
3100
+ position: relative;
3101
+ padding: var(--ag-space-2) var(--ag-space-3);
3102
+ margin-block-end: var(--ag-space-1);
3103
+ border: none;
3104
+ background: none;
3105
+ cursor: pointer;
3106
+ width: 100%;
3107
+ text-align: left;
3108
+ border-radius: var(--ag-radius-sm);
3109
+ transition: background var(--ag-fx-duration-sm);
3110
+ color: inherit;
3111
+ }
3112
+ .nav-button svg { flex-shrink: 0; }
3113
+ .nav-button:hover { background: var(--ag-background-secondary); }
3114
+ .nav-button.active {
3115
+ background: var(--ag-primary-background);
3116
+ color: var(--ag-primary-text);
3117
+ font-weight: 500;
3118
+ }
3119
+ .nav-label {
3120
+ flex-grow: 1;
3121
+ overflow: hidden;
3122
+ text-overflow: ellipsis;
3123
+ white-space: nowrap;
3124
+ transition: opacity var(--ag-sidebar-transition-duration) var(--ag-sidebar-transition-easing);
3125
+ }
3126
+ .chevron {
3127
+ display: flex;
3128
+ align-items: center;
3129
+ transition: transform var(--ag-fx-duration-md), opacity var(--ag-fx-duration-sm);
3130
+ margin-left: auto;
3131
+ }
3132
+ .nav-button[aria-expanded="true"] .chevron { transform: rotate(90deg); }
3133
+ ag-sidebar[collapsed] .nav-label,
3134
+ ag-sidebar[collapsed] .chevron {
3135
+ opacity: 0;
3136
+ pointer-events: none;
3137
+ display: none;
3138
+ }
3139
+ ag-sidebar[collapsed] .nav-button {
3140
+ width: auto;
3141
+ padding: var(--ag-space-2);
3142
+ }
3143
+ ag-sidebar[collapsed] ag-sidebar-nav-submenu:not(.popover-submenu),
3144
+ ag-sidebar:not([collapsed]) ag-popover,
3145
+ ag-sidebar[collapsed] .nav-button-expanded,
3146
+ ag-sidebar:not([collapsed]) .nav-button-collapsed {
3147
+ display: none !important;
3148
+ }
3149
+ ag-sidebar[collapsed] ag-popover.nav-button-collapsed {
3150
+ display: block !important;
3151
+ }
3152
+ ag-sidebar-nav-submenu {
3153
+ display: none;
3154
+ overflow: hidden;
3155
+ }
3156
+ ag-sidebar-nav-submenu[open] {
3157
+ display: block;
3158
+ }
3159
+ .nav-sublink {
3160
+ display: block;
3161
+ padding: var(--ag-space-2) var(--ag-space-3);
3162
+ margin-block-end: var(--ag-space-1);
3163
+ color: inherit;
3164
+ text-decoration: none;
3165
+ border-radius: var(--ag-radius-sm);
3166
+ transition: background var(--ag-fx-duration-sm);
3167
+ }
3168
+ .nav-sublink:hover { background: var(--ag-background-secondary); }
3169
+ .nav-button-collapsed::part(ag-popover-body) { padding: var(--ag-space-1); }
3170
+ \`;
3171
+
3172
+ const handleSubmenuToggle = (e: Event) => {
3173
+ e.preventDefault();
3174
+ e.stopPropagation();
3175
+ const button = e.currentTarget as HTMLElement;
3176
+ const navItem = button.closest('ag-sidebar-nav-item');
3177
+ if (!navItem) return;
3178
+ const submenu = navItem.querySelector('ag-sidebar-nav-submenu');
3179
+ if (!submenu) return;
3180
+ const isExpanded = button.getAttribute('aria-expanded') === 'true';
3181
+ button.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
3182
+ if (isExpanded) submenu.removeAttribute('open');
3183
+ else submenu.setAttribute('open', '');
3184
+ };
3185
+
3186
+ const toggleCollapse = (e: Event) => {
3187
+ const sidebar = (e.target as HTMLElement).closest('ag-sidebar') as any;
3188
+ if (sidebar && sidebar.toggleCollapse) sidebar.toggleCollapse();
3189
+ };
3190
+
3191
+ const meta: Meta = {
3192
+ title: 'AgnosticUI/Sidebar',
3193
+ component: 'ag-sidebar',
3194
+ tags: ['autodocs'],
3195
+ parameters: { layout: 'fullscreen' },
3196
+ argTypes: {
3197
+ collapsed: { control: 'boolean' },
3198
+ variant: { control: 'select', options: ['default', 'bordered', 'elevated'] },
3199
+ },
3200
+ args: { collapsed: false, variant: 'default' },
3201
+ render: (args: any) => html\`
3202
+ <style>\${NAV_STYLES}</style>
3203
+ <div style="position: relative; display: flex; height: 500px; border: 1px solid var(--ag-border-color); border-radius: 0.5rem; overflow: hidden; contain: layout;">
3204
+ <ag-sidebar
3205
+ ?collapsed=\${args.collapsed}
3206
+ .variant=\${args.variant}
3207
+ aria-label="Main navigation"
3208
+ show-mobile-toggle
3209
+ >
3210
+ <h2 slot="ag-header-start" style="margin: 0; font-size: 1.125rem; font-weight: 600;">Dashboard</h2>
3211
+ <button
3212
+ type="button"
3213
+ slot="ag-header-toggle"
3214
+ aria-label="Toggle sidebar"
3215
+ style="background: none; border: none; padding: 8px 0; cursor: pointer; display: flex; align-items: center; color: inherit;"
3216
+ @click=\${toggleCollapse}
3217
+ >
3218
+ <span .innerHTML=\${PANEL_TOGGLE_SVG}></span>
3219
+ </button>
3220
+
3221
+ <ag-sidebar-nav>
3222
+ <ag-sidebar-nav-item>
3223
+ <button type="button" class="nav-button active" aria-current="page">
3224
+ <span .innerHTML=\${HOME_SVG}></span>
3225
+ <span class="nav-label">Dashboard</span>
3226
+ </button>
3227
+ </ag-sidebar-nav-item>
3228
+
3229
+ <ag-sidebar-nav-item>
3230
+ <button type="button" class="nav-button nav-button-expanded" aria-expanded="false" @click=\${handleSubmenuToggle}>
3231
+ <span .innerHTML=\${FOLDER_SVG}></span>
3232
+ <span class="nav-label">Projects</span>
3233
+ <span class="chevron"><span .innerHTML=\${CHEVRON_RIGHT_SVG}></span></span>
3234
+ </button>
3235
+ <ag-popover class="nav-button-collapsed" placement="right-start" trigger-type="click" distance="8" arrow>
3236
+ <button slot="trigger" type="button" class="nav-button">
3237
+ <span .innerHTML=\${FOLDER_SVG}></span>
3238
+ </button>
3239
+ <ag-sidebar-nav-popover-submenu slot="content">
3240
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Project Alpha</a>
3241
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Project Beta</a>
3242
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Project Gamma</a>
3243
+ </ag-sidebar-nav-popover-submenu>
3244
+ </ag-popover>
3245
+ <ag-sidebar-nav-submenu>
3246
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Project Alpha</a>
3247
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Project Beta</a>
3248
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Project Gamma</a>
3249
+ </ag-sidebar-nav-submenu>
3250
+ </ag-sidebar-nav-item>
3251
+
3252
+ <ag-sidebar-nav-item>
3253
+ <button type="button" class="nav-button">
3254
+ <span .innerHTML=\${USER_SVG}></span>
3255
+ <span class="nav-label">Team</span>
3256
+ </button>
3257
+ </ag-sidebar-nav-item>
3258
+
3259
+ <ag-sidebar-nav-item>
3260
+ <button type="button" class="nav-button nav-button-expanded" aria-expanded="false" @click=\${handleSubmenuToggle}>
3261
+ <span .innerHTML=\${SETTINGS_SVG}></span>
3262
+ <span class="nav-label">Settings</span>
3263
+ <span class="chevron"><span .innerHTML=\${CHEVRON_RIGHT_SVG}></span></span>
3264
+ </button>
3265
+ <ag-popover class="nav-button-collapsed" placement="right-start" trigger-type="click" distance="8" arrow>
3266
+ <button slot="trigger" type="button" class="nav-button">
3267
+ <span .innerHTML=\${SETTINGS_SVG}></span>
3268
+ </button>
3269
+ <ag-sidebar-nav-popover-submenu slot="content">
3270
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Profile</a>
3271
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Billing</a>
3272
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Security</a>
3273
+ </ag-sidebar-nav-popover-submenu>
3274
+ </ag-popover>
3275
+ <ag-sidebar-nav-submenu>
3276
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Profile</a>
3277
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Billing</a>
3278
+ <a href="#" class="nav-sublink" @click=\${(e: Event) => e.preventDefault()}>Security</a>
3279
+ </ag-sidebar-nav-submenu>
3280
+ </ag-sidebar-nav-item>
3281
+ </ag-sidebar-nav>
3282
+
3283
+ <div slot="ag-footer" style="font-size: 0.875rem; color: var(--ag-text-secondary);">
3284
+ © 2024 Company
3285
+ </div>
3286
+ </ag-sidebar>
3287
+
3288
+ <main style="flex: 1; padding: 2rem; overflow: auto; background: var(--ag-background);">
3289
+ <h1 style="margin-top: 0;">Main Content</h1>
3290
+ <p>Click the panel toggle button to collapse the sidebar into rail mode.</p>
3291
+ <p>When collapsed, click icon-only items with submenus to see them in popovers.</p>
3292
+ </main>
3293
+ </div>
3294
+ \`,
3295
+ } satisfies Meta;
3296
+
3297
+ export default meta;
3298
+ type Story = StoryObj;
3299
+
3300
+ export const Default: Story = {};
3301
+
3302
+ export const Collapsed: Story = { args: { collapsed: true } };
3303
+
3304
+ export const Elevated: Story = { args: { variant: 'elevated' } };
3305
+ `,
3306
+ ScrollToButton: `import type { Meta, StoryObj } from '@storybook/web-components';
3307
+ import { html } from 'lit';
3308
+ import './ScrollToButton';
3309
+
3310
+ const meta: Meta = {
3311
+ title: 'AgnosticUI/ScrollToButton',
3312
+ component: 'ag-scroll-to-button',
3313
+ tags: ['autodocs'],
3314
+ argTypes: {
3315
+ label: { control: 'text' },
3316
+ target: { control: 'select', options: ['top', 'bottom'] },
3317
+ scrollThreshold: { control: 'number' },
3318
+ smoothScroll: { control: 'boolean' },
3319
+ showLabel: { control: 'boolean' },
3320
+ },
3321
+ args: { label: 'Back to Top', target: 'top', scrollThreshold: 400, smoothScroll: true, showLabel: false },
3322
+ } satisfies Meta;
3323
+
3324
+ export default meta;
3325
+ type Story = StoryObj;
3326
+
3327
+ export const Default: Story = {
3328
+ render: (args: any) => html\`
3329
+ <div style="min-height: 200vh; padding: 2rem; position: relative;">
3330
+ <h2>Scroll Down to See the Button</h2>
3331
+ <p>The scroll-to-top button appears after scrolling \${args.scrollThreshold}px.</p>
3332
+ <div style="margin-top: 1rem; padding: 1rem; background: var(--ag-background-secondary, #f9fafb); border-radius: 0.5rem;">
3333
+ Scroll down this panel to trigger the floating button.
3334
+ </div>
3335
+ \${Array.from({ length: 20 }, (_, i) => html\`<p style="margin: 1.5rem 0;">Paragraph \${i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>\`)}
3336
+ <ag-scroll-to-button
3337
+ .label=\${args.label}
3338
+ .target=\${args.target}
3339
+ .scrollThreshold=\${args.scrollThreshold}
3340
+ ?smoothScroll=\${args.smoothScroll}
3341
+ ?showLabel=\${args.showLabel}
3342
+ ></ag-scroll-to-button>
3343
+ </div>
3344
+ \`,
3345
+ };
3346
+ `,
3347
+ ScrollProgress: `import type { Meta, StoryObj } from '@storybook/web-components';
3348
+ import { html } from 'lit';
3349
+ import './ScrollProgress';
3350
+
3351
+ const LOREM = Array.from({ length: 40 }, (_, i) =>
3352
+ \`<p style="margin: 1rem 0; line-height: 1.6; max-width: 700px;">Paragraph \${i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p>\`
3353
+ ).join('');
3354
+
3355
+ const meta: Meta = {
3356
+ title: 'AgnosticUI/ScrollProgress',
3357
+ component: 'ag-scroll-progress',
3358
+ tags: ['autodocs'],
3359
+ parameters: { layout: 'fullscreen' },
3360
+ argTypes: {
3361
+ mode: { control: 'select', options: ['bar', 'dot-trail', 'badge', 'ring'] },
3362
+ orientation: { control: 'select', options: ['top', 'bottom'] },
3363
+ },
3364
+ args: { mode: 'bar', orientation: 'top' },
3365
+ } satisfies Meta;
3366
+
3367
+ export default meta;
3368
+ type Story = StoryObj;
3369
+
3370
+ export const BarTop: Story = {
3371
+ parameters: { layout: 'fullscreen' },
3372
+ render: () => html\`
3373
+ <ag-scroll-progress .mode=\${'bar'} .orientation=\${'top'}></ag-scroll-progress>
3374
+ <div style="padding: 2rem;">
3375
+ <h2>Bar — Top</h2>
3376
+ <p>Scroll down. The progress bar fills along the <strong>top</strong> of the page.</p>
3377
+ <div .innerHTML=\${LOREM}></div>
3378
+ </div>
3379
+ \`,
3380
+ };
3381
+
3382
+ export const BarBottom: Story = {
3383
+ parameters: { layout: 'fullscreen' },
3384
+ render: () => html\`
3385
+ <ag-scroll-progress .mode=\${'bar'} .orientation=\${'bottom'}></ag-scroll-progress>
3386
+ <div style="padding: 2rem;">
3387
+ <h2>Bar — Bottom</h2>
3388
+ <p>Scroll down. The progress bar fills along the <strong>bottom</strong> of the page.</p>
3389
+ <div .innerHTML=\${LOREM}></div>
3390
+ </div>
3391
+ \`,
3392
+ };
3393
+
3394
+ export const DotTrail: Story = {
3395
+ parameters: { layout: 'fullscreen' },
3396
+ render: () => html\`
3397
+ <div style="position: fixed; bottom: 1.5rem; left: 50%; transform: translateX(-50%); z-index: 100;
3398
+ background: var(--ag-background, white); border-radius: 2rem; padding: 0.5rem 1rem;
3399
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);">
3400
+ <ag-scroll-progress .mode=\${'dot-trail'} .dots=\${7}></ag-scroll-progress>
3401
+ </div>
3402
+ <div style="padding: 2rem;">
3403
+ <h2>Dot Trail</h2>
3404
+ <p>Scroll down. The dots at the bottom of the page fill in to show progress.</p>
3405
+ <div .innerHTML=\${LOREM}></div>
3406
+ </div>
3407
+ \`,
3408
+ };
3409
+
3410
+ export const Badge: Story = {
3411
+ parameters: { layout: 'fullscreen' },
3412
+ render: () => html\`
3413
+ <div style="position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 100;">
3414
+ <ag-scroll-progress .mode=\${'badge'} .badgeVariant=\${'info'}></ag-scroll-progress>
3415
+ </div>
3416
+ <div style="padding: 2rem;">
3417
+ <h2>Badge</h2>
3418
+ <p>Scroll down. The percentage badge in the bottom-right updates as you scroll.</p>
3419
+ <div .innerHTML=\${LOREM}></div>
3420
+ </div>
3421
+ \`,
3422
+ };
3423
+
3424
+ export const Ring: Story = {
3425
+ parameters: { layout: 'fullscreen' },
3426
+ render: () => html\`
3427
+ <div style="position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 100;">
3428
+ <ag-scroll-progress .mode=\${'ring'} .ringSize=\${48} .ringVariant=\${'primary'}></ag-scroll-progress>
3429
+ </div>
3430
+ <div style="padding: 2rem;">
3431
+ <h2>Ring</h2>
3432
+ <p>Scroll down. The progress ring in the bottom-right fills as you scroll.</p>
3433
+ <div .innerHTML=\${LOREM}></div>
3434
+ </div>
3435
+ \`,
3436
+ };
3437
+ `,
3438
+ Toast: `import type { Meta, StoryObj } from '@storybook/web-components';
3439
+ import { html } from 'lit';
3440
+ import './Toast';
3441
+ import '../../Button/core/Button';
3442
+
3443
+ const meta: Meta = {
3444
+ title: 'AgnosticUI/Toast',
3445
+ component: 'ag-toast',
3446
+ tags: ['autodocs'],
3447
+ argTypes: {
3448
+ type: { control: 'select', options: ['info', 'success', 'warning', 'danger', 'error'] },
3449
+ position: { control: 'select', options: ['top-end', 'top', 'top-start', 'bottom-end', 'bottom', 'bottom-start'] },
3450
+ autoDismiss: { control: 'boolean' },
3451
+ showCloseButton: { control: 'boolean' },
3452
+ bordered: { control: 'boolean' },
3453
+ rounded: { control: 'boolean' },
3454
+ },
3455
+ args: {
3456
+ type: 'info',
3457
+ position: 'top-end',
3458
+ autoDismiss: false,
3459
+ showCloseButton: true,
3460
+ bordered: false,
3461
+ rounded: true,
3462
+ },
3463
+ } satisfies Meta;
3464
+
3465
+ export default meta;
3466
+ type Story = StoryObj;
3467
+
3468
+ export const Default: Story = {
3469
+ render: (args: any) => html\`
3470
+ <div style="min-height: 200px; padding: 1rem;">
3471
+ <ag-button
3472
+ @click=\${(e: Event) => {
3473
+ const toast = (e.target as HTMLElement).parentElement?.querySelector('ag-toast') as any;
3474
+ if (toast) { toast.open = false; requestAnimationFrame(() => { toast.open = true; }); }
3475
+ }}
3476
+ >Show Toast</ag-button>
3477
+ <ag-toast
3478
+ .open=\${args.open ?? false}
3479
+ .type=\${args.type}
3480
+ .position=\${args.position}
3481
+ .autoDismiss=\${args.autoDismiss}
3482
+ .showCloseButton=\${args.showCloseButton}
3483
+ .bordered=\${args.bordered}
3484
+ .rounded=\${args.rounded}
3485
+ @toast-close=\${(e: Event) => { (e.target as any).open = false; }}
3486
+ >
3487
+ Toast notification message here.
3488
+ </ag-toast>
3489
+ </div>
3490
+ \`,
3491
+ };
3492
+
3493
+ export const TopStart: Story = {
3494
+ render: () => html\`
3495
+ <div style="min-height: 200px; padding: 1rem;">
3496
+ <ag-toast .open=\${true} type="success" .position=\${'top-start'} .showCloseButton=\${true}>
3497
+ Saved successfully!
3498
+ </ag-toast>
3499
+ <p>Toast anchored to top-start.</p>
3500
+ </div>
3501
+ \`,
3502
+ };
3503
+
3504
+ export const BottomEnd: Story = {
3505
+ render: () => html\`
3506
+ <div style="min-height: 200px; padding: 1rem;">
3507
+ <ag-toast .open=\${true} type="warning" .position=\${'bottom-end'} .showCloseButton=\${true}>
3508
+ Warning: low disk space.
3509
+ </ag-toast>
3510
+ <p>Toast anchored to bottom-end.</p>
3511
+ </div>
3512
+ \`,
3513
+ };
3514
+
3515
+ export const BottomCenter: Story = {
3516
+ render: () => html\`
3517
+ <div style="min-height: 200px; padding: 1rem;">
3518
+ <ag-toast .open=\${true} type="danger" .position=\${'bottom'} .showCloseButton=\${true}>
3519
+ Connection lost. Retrying...
3520
+ </ag-toast>
3521
+ <p>Toast anchored to bottom-center.</p>
3522
+ </div>
3523
+ \`,
3524
+ };
3525
+ `,
3526
+ Tooltip: `import type { Meta, StoryObj } from '@storybook/web-components';
3527
+ import { html } from 'lit';
3528
+ import './Tooltip';
3529
+
3530
+ const meta: Meta = {
3531
+ title: 'AgnosticUI/Tooltip',
3532
+ component: 'ag-tooltip',
3533
+ tags: ['autodocs'],
3534
+ argTypes: {
3535
+ content: { control: 'text' },
3536
+ placement: { control: 'select', options: ['top', 'bottom', 'left', 'right'] },
3537
+ disabled: { control: 'boolean' },
3538
+ },
3539
+ args: { content: 'This is a helpful tooltip', placement: 'top', disabled: false },
3540
+ render: (args: any) => html\`
3541
+ <div style="display: flex; justify-content: center; padding: 4rem;">
3542
+ <ag-tooltip
3543
+ .content=\${args.content}
3544
+ .placement=\${args.placement}
3545
+ ?disabled=\${args.disabled}
3546
+ >
3547
+ <button>Hover over me</button>
3548
+ </ag-tooltip>
3549
+ </div>
3550
+ \`,
3551
+ } satisfies Meta;
3552
+
3553
+ export default meta;
3554
+ type Story = StoryObj;
3555
+
3556
+ export const Default: Story = {};
3557
+
3558
+ export const Placements: Story = {
3559
+ render: () => html\`
3560
+ <div style="display: flex; gap: 2rem; justify-content: center; align-items: center; padding: 5rem 2rem;">
3561
+ <ag-tooltip content="Top tooltip" placement="top">
3562
+ <button>Top</button>
3563
+ </ag-tooltip>
3564
+ <ag-tooltip content="Bottom tooltip" placement="bottom">
3565
+ <button>Bottom</button>
3566
+ </ag-tooltip>
3567
+ <ag-tooltip content="Left tooltip" placement="left">
3568
+ <button>Left</button>
3569
+ </ag-tooltip>
3570
+ <ag-tooltip content="Right tooltip" placement="right">
3571
+ <button>Right</button>
3572
+ </ag-tooltip>
3573
+ </div>
3574
+ \`,
3575
+ };
3576
+ `,
3577
+ };
3578
+ export function generateLitStory(name) {
3579
+ // Check for override first
3580
+ if (LIT_STORY_OVERRIDES[name]) {
3581
+ return STORY_FILE_HEADER + LIT_STORY_OVERRIDES[name];
3582
+ }
3583
+ const tag = getAgTagName(name);
3584
+ // For import path, the side-effect import uses the PascalCase name
3585
+ // e.g. import './Button' or import './SkeletonLoader'
3586
+ const lines = [];
3587
+ // Imports
3588
+ lines.push(`import type { Meta, StoryObj } from '@storybook/web-components';`);
3589
+ lines.push(`import { html } from 'lit';`);
3590
+ // Side-effect import to register custom element
3591
+ if (OPEN_CONTROLLED_COMPONENTS.has(name) && name !== 'Collapsible') {
3592
+ lines.push(`import './${name}';`);
3593
+ lines.push(`import '../../Button/core/Button';`);
3594
+ }
3595
+ else {
3596
+ lines.push(`import './${name}';`);
3597
+ }
3598
+ lines.push('');
3599
+ // Sample data for Combobox
3600
+ if (name === 'Combobox') {
3601
+ lines.push(`const FRUITS = [`);
3602
+ lines.push(` { value: 'apple', label: 'Apple' },`);
3603
+ lines.push(` { value: 'banana', label: 'Banana' },`);
3604
+ lines.push(` { value: 'cherry', label: 'Cherry' },`);
3605
+ lines.push(` { value: 'grape', label: 'Grape' },`);
3606
+ lines.push(` { value: 'mango', label: 'Mango' },`);
3607
+ lines.push(` { value: 'orange', label: 'Orange' },`);
3608
+ lines.push(`];`);
3609
+ lines.push('');
3610
+ }
3611
+ const argTypes = ARGTYPES[name] ?? '';
3612
+ // Build default args for Lit
3613
+ let litArgs = '';
3614
+ if (name === 'Button') {
3615
+ litArgs = ` args: {
3616
+ variant: '',
3617
+ size: 'md',
3618
+ shape: '',
3619
+ bordered: false,
3620
+ ghost: false,
3621
+ disabled: false,
3622
+ loading: false,
3623
+ label: 'Button',
3624
+ },`;
3625
+ }
3626
+ else if (name === 'Dialog') {
3627
+ litArgs = ` args: {
3628
+ open: false,
3629
+ heading: 'Dialog title',
3630
+ description: 'Supporting description text goes here.',
3631
+ showCloseButton: true,
3632
+ },`;
3633
+ }
3634
+ else if (name === 'Drawer') {
3635
+ litArgs = ` args: {
3636
+ open: false,
3637
+ heading: 'Drawer',
3638
+ showCloseButton: true,
3639
+ },`;
3640
+ }
3641
+ else if (name === 'Toast') {
3642
+ litArgs = ` args: {
3643
+ open: false,
3644
+ type: 'info',
3645
+ position: 'top-right',
3646
+ showCloseButton: true,
3647
+ },`;
3648
+ }
3649
+ else if (name === 'Combobox') {
3650
+ litArgs = ` args: {
3651
+ options: FRUITS,
3652
+ label: 'Select a fruit',
3653
+ placeholder: 'Choose...',
3654
+ },`;
3655
+ }
3656
+ else if (FX_COMPONENTS.has(name)) {
3657
+ const defaultLabel = name === 'BadgeFx' ? 'BadgeFx' : name === 'ButtonFx' ? 'Click me' : name;
3658
+ litArgs = ` args: {
3659
+ fx: 'bounce',
3660
+ label: '${defaultLabel}',
3661
+ },`;
3662
+ }
3663
+ else if (TEXT_CHILD_COMPONENTS.has(name)) {
3664
+ litArgs = ` args: {
3665
+ label: '${name}',
3666
+ },`;
3667
+ }
3668
+ // Build render for meta level (only simple/text-child components, not open-controlled)
3669
+ let litRender = '';
3670
+ if (!OPEN_CONTROLLED_COMPONENTS.has(name) && name !== 'Button' && name !== 'Combobox') {
3671
+ if (TEXT_CHILD_COMPONENTS.has(name)) {
3672
+ litRender = ` render: (args: any) => html\`<${tag}>\${args.label}</${tag}>\`,`;
3673
+ }
3674
+ else if (FX_COMPONENTS.has(name)) {
3675
+ litRender = ` render: (args: any) => html\`<${tag} fx=\${args.fx}>\${args.label}</${tag}>\`,`;
3676
+ }
3677
+ else {
3678
+ litRender = ` render: (args: any) => html\`<${tag}></${tag}>\`,`;
3679
+ }
3680
+ }
3681
+ else if (name === 'Combobox') {
3682
+ litRender = ` render: (args: any) => html\`
3683
+ <${tag}
3684
+ .options=\${args.options}
3685
+ label=\${args.label}
3686
+ placeholder=\${args.placeholder}
3687
+ ?clearable=\${args.clearable}
3688
+ ?disabled=\${args.disabled}
3689
+ ?multiple=\${args.multiple}
3690
+ ?invalid=\${args.invalid}
3691
+ ></${tag}>
3692
+ \`,`;
3693
+ }
3694
+ else if (name === 'Button') {
3695
+ litRender = ` render: (args: any) => html\`
3696
+ <ag-button
3697
+ variant=\${args.variant}
3698
+ size=\${args.size}
3699
+ shape=\${args.shape}
3700
+ ?bordered=\${args.bordered}
3701
+ ?ghost=\${args.ghost}
3702
+ ?disabled=\${args.disabled}
3703
+ ?loading=\${args.loading}
3704
+ >\${args.label}</ag-button>
3705
+ \`,`;
3706
+ }
3707
+ const metaParts = [];
3708
+ metaParts.push(` title: 'AgnosticUI/${name}',`);
3709
+ metaParts.push(` component: '${tag}',`);
3710
+ metaParts.push(` tags: ['autodocs'],`);
3711
+ const needsLabelArgType = TEXT_CHILD_COMPONENTS.has(name) || FX_COMPONENTS.has(name);
3712
+ if (argTypes) {
3713
+ // For Lit, add a label argType for TEXT_CHILD and FX components
3714
+ if (needsLabelArgType) {
3715
+ // Insert label control before the closing ' },' of the argTypes block
3716
+ const closingIdx = argTypes.lastIndexOf('\n },');
3717
+ if (closingIdx !== -1) {
3718
+ const modified = argTypes.slice(0, closingIdx) + '\n label: { control: \'text\' },' + argTypes.slice(closingIdx);
3719
+ metaParts.push(modified);
3720
+ }
3721
+ else {
3722
+ metaParts.push(argTypes);
3723
+ }
3724
+ }
3725
+ else {
3726
+ metaParts.push(argTypes);
3727
+ }
3728
+ }
3729
+ else if (needsLabelArgType) {
3730
+ metaParts.push(` argTypes: {\n label: { control: 'text' },\n },`);
3731
+ }
3732
+ if (litArgs) {
3733
+ metaParts.push(litArgs);
3734
+ }
3735
+ if (litRender) {
3736
+ metaParts.push(litRender);
3737
+ }
3738
+ lines.push(`const meta: Meta = {`);
3739
+ lines.push(metaParts.join('\n'));
3740
+ lines.push(`} satisfies Meta;`);
3741
+ lines.push('');
3742
+ lines.push(`export default meta;`);
3743
+ lines.push(`type Story = StoryObj;`);
3744
+ lines.push('');
3745
+ // Default story
3746
+ if (name === 'Dialog') {
3747
+ lines.push(`export const Default: Story = {`);
3748
+ lines.push(` render: (args: any) => html\``);
3749
+ lines.push(` <div>`);
3750
+ lines.push(` <ag-button`);
3751
+ lines.push(` @click=\${(e: Event) => {`);
3752
+ lines.push(` const dialog = (e.target as HTMLElement).parentElement?.querySelector('${tag}') as any;`);
3753
+ lines.push(` if (dialog) dialog.open = true;`);
3754
+ lines.push(` }}`);
3755
+ lines.push(` >Open Dialog</ag-button>`);
3756
+ lines.push(` <${tag}`);
3757
+ lines.push(` .open=\${args.open}`);
3758
+ lines.push(` .heading=\${args.heading}`);
3759
+ lines.push(` .showCloseButton=\${args.showCloseButton}`);
3760
+ lines.push(` @dialog-close=\${(e: Event) => { (e.target as any).open = false; }}`);
3761
+ lines.push(` @dialog-cancel=\${(e: Event) => { (e.target as any).open = false; }}`);
3762
+ lines.push(` >`);
3763
+ lines.push(` <p>Dialog content goes here.</p>`);
3764
+ lines.push(` </${tag}>`);
3765
+ lines.push(` </div>`);
3766
+ lines.push(` \`,`);
3767
+ lines.push(`};`);
3768
+ }
3769
+ else if (name === 'Drawer') {
3770
+ lines.push(`export const Default: Story = {`);
3771
+ lines.push(` render: (args: any) => html\``);
3772
+ lines.push(` <div>`);
3773
+ lines.push(` <ag-button`);
3774
+ lines.push(` @click=\${(e: Event) => {`);
3775
+ lines.push(` const drawer = (e.target as HTMLElement).parentElement?.querySelector('${tag}') as any;`);
3776
+ lines.push(` if (drawer) drawer.open = true;`);
3777
+ lines.push(` }}`);
3778
+ lines.push(` >Open Drawer</ag-button>`);
3779
+ lines.push(` <${tag}`);
3780
+ lines.push(` .open=\${args.open}`);
3781
+ lines.push(` .heading=\${args.heading}`);
3782
+ lines.push(` .showCloseButton=\${args.showCloseButton}`);
3783
+ lines.push(` @drawer-close=\${(e: Event) => { (e.target as any).open = false; }}`);
3784
+ lines.push(` @drawer-cancel=\${(e: Event) => { (e.target as any).open = false; }}`);
3785
+ lines.push(` >`);
3786
+ lines.push(` <div style="padding: 1rem">Drawer content goes here.</div>`);
3787
+ lines.push(` </${tag}>`);
3788
+ lines.push(` </div>`);
3789
+ lines.push(` \`,`);
3790
+ lines.push(`};`);
3791
+ }
3792
+ else if (name === 'Toast') {
3793
+ lines.push(`export const Default: Story = {`);
3794
+ lines.push(` render: (args: any) => html\``);
3795
+ lines.push(` <div>`);
3796
+ lines.push(` <ag-button`);
3797
+ lines.push(` @click=\${(e: Event) => {`);
3798
+ lines.push(` const toast = (e.target as HTMLElement).parentElement?.querySelector('${tag}') as any;`);
3799
+ lines.push(` if (toast) toast.open = true;`);
3800
+ lines.push(` }}`);
3801
+ lines.push(` >Show Toast</ag-button>`);
3802
+ lines.push(` <${tag}`);
3803
+ lines.push(` .open=\${args.open}`);
3804
+ lines.push(` type=\${args.type}`);
3805
+ lines.push(` position=\${args.position}`);
3806
+ lines.push(` ?showCloseButton=\${args.showCloseButton}`);
3807
+ lines.push(` @toast-close=\${(e: Event) => { (e.target as any).open = false; }}`);
3808
+ lines.push(` >`);
3809
+ lines.push(` <span>Toast notification message</span>`);
3810
+ lines.push(` </${tag}>`);
3811
+ lines.push(` </div>`);
3812
+ lines.push(` \`,`);
3813
+ lines.push(`};`);
3814
+ }
3815
+ else if (name === 'Collapsible') {
3816
+ lines.push(`export const Default: Story = {`);
3817
+ lines.push(` render: () => html\``);
3818
+ lines.push(` <${tag}>`);
3819
+ lines.push(` <span slot="summary">Toggle details</span>`);
3820
+ lines.push(` <div>Collapsible content goes here.</div>`);
3821
+ lines.push(` </${tag}>`);
3822
+ lines.push(` \`,`);
3823
+ lines.push(`};`);
3824
+ }
3825
+ else if (name === 'Button') {
3826
+ lines.push(`export const Default: Story = {};`);
3827
+ lines.push('');
3828
+ lines.push(`export const Variants: Story = {`);
3829
+ lines.push(` render: () => html\``);
3830
+ lines.push(` <div style="display:flex;gap:8px;flex-wrap:wrap">`);
3831
+ lines.push(` <ag-button>Default</ag-button>`);
3832
+ lines.push(` <ag-button variant="primary">Primary</ag-button>`);
3833
+ lines.push(` <ag-button variant="secondary">Secondary</ag-button>`);
3834
+ lines.push(` <ag-button variant="success">Success</ag-button>`);
3835
+ lines.push(` <ag-button variant="warning">Warning</ag-button>`);
3836
+ lines.push(` <ag-button variant="danger">Danger</ag-button>`);
3837
+ lines.push(` <ag-button variant="monochrome">Monochrome</ag-button>`);
3838
+ lines.push(` </div>`);
3839
+ lines.push(` \`,`);
3840
+ lines.push(`};`);
3841
+ lines.push('');
3842
+ lines.push(`export const Sizes: Story = {`);
3843
+ lines.push(` render: () => html\``);
3844
+ lines.push(` <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">`);
3845
+ lines.push(` <ag-button size="x-sm">x-sm</ag-button>`);
3846
+ lines.push(` <ag-button size="sm">sm</ag-button>`);
3847
+ lines.push(` <ag-button size="md">md</ag-button>`);
3848
+ lines.push(` <ag-button size="lg">lg</ag-button>`);
3849
+ lines.push(` <ag-button size="xl">xl</ag-button>`);
3850
+ lines.push(` </div>`);
3851
+ lines.push(` \`,`);
3852
+ lines.push(`};`);
3853
+ lines.push('');
3854
+ lines.push(`export const Bordered: Story = {`);
3855
+ lines.push(` render: () => html\``);
3856
+ lines.push(` <div style="display:flex;gap:8px;flex-wrap:wrap">`);
3857
+ lines.push(` <ag-button bordered>Default</ag-button>`);
3858
+ lines.push(` <ag-button variant="primary" bordered>Primary</ag-button>`);
3859
+ lines.push(` <ag-button variant="success" bordered>Success</ag-button>`);
3860
+ lines.push(` <ag-button variant="danger" bordered>Danger</ag-button>`);
3861
+ lines.push(` </div>`);
3862
+ lines.push(` \`,`);
3863
+ lines.push(`};`);
3864
+ lines.push('');
3865
+ lines.push(`export const Disabled: Story = { args: { variant: 'primary', disabled: true } };`);
3866
+ lines.push(`export const Loading: Story = { args: { variant: 'primary', loading: true } };`);
3867
+ }
3868
+ else {
3869
+ lines.push(`export const Default: Story = {};`);
3870
+ }
3871
+ return STORY_FILE_HEADER + lines.join('\n') + '\n';
660
3872
  }
661
3873
  //# sourceMappingURL=stories.js.map