@polymarbot/nuxt-layer-shadcn-ui 0.1.10 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/app/assets/styles/colors.css +10 -10
  2. package/app/components/ui/Accordion/index.stories.ts +60 -56
  3. package/app/components/ui/Accordion/index.vue +1 -1
  4. package/app/components/ui/AdminLayout/SidebarMenus.vue +0 -2
  5. package/app/components/ui/AdminLayout/index.stories.ts +9 -8
  6. package/app/components/ui/Alert/index.stories.ts +28 -26
  7. package/app/components/ui/Alert/index.vue +6 -6
  8. package/app/components/ui/Alert/types.ts +2 -1
  9. package/app/components/ui/AlertDialog/index.stories.ts +85 -50
  10. package/app/components/ui/AsyncDataTable/index.stories.ts +53 -36
  11. package/app/components/ui/Avatar/index.stories.ts +56 -51
  12. package/app/components/ui/Avatar/index.vue +1 -1
  13. package/app/components/ui/Avatar/types.ts +5 -2
  14. package/app/components/ui/Badge/index.stories.ts +41 -41
  15. package/app/components/ui/Badge/index.vue +1 -1
  16. package/app/components/ui/Badge/types.ts +3 -1
  17. package/app/components/ui/Breadcrumb/index.stories.ts +48 -37
  18. package/app/components/ui/Breadcrumb/index.vue +1 -1
  19. package/app/components/ui/Button/index.stories.ts +94 -90
  20. package/app/components/ui/Button/index.vue +1 -1
  21. package/app/components/ui/Button/types.ts +4 -1
  22. package/app/components/ui/ButtonGroup/index.stories.ts +61 -49
  23. package/app/components/ui/Card/index.stories.ts +55 -47
  24. package/app/components/ui/Card/index.vue +1 -1
  25. package/app/components/ui/Checkbox/index.stories.ts +69 -46
  26. package/app/components/ui/Checkbox/index.vue +1 -1
  27. package/app/components/ui/CopyButton/index.stories.ts +89 -31
  28. package/app/components/ui/DataTable/index.stories.ts +218 -168
  29. package/app/components/ui/DataTable/index.vue +1 -1
  30. package/app/components/ui/DatePicker/index.stories.ts +131 -37
  31. package/app/components/ui/DateRangePicker/index.stories.ts +107 -33
  32. package/app/components/ui/Divider/index.stories.ts +46 -24
  33. package/app/components/ui/Divider/index.vue +1 -1
  34. package/app/components/ui/Drawer/index.stories.ts +131 -81
  35. package/app/components/ui/Drawer/index.vue +1 -1
  36. package/app/components/ui/Drawer/types.ts +1 -1
  37. package/app/components/ui/Dropdown/index.stories.ts +134 -89
  38. package/app/components/ui/Dropdown/index.vue +5 -1
  39. package/app/components/ui/Dropdown/types.ts +1 -1
  40. package/app/components/ui/FormItem/index.stories.ts +87 -43
  41. package/app/components/ui/FormItem/index.vue +1 -1
  42. package/app/components/ui/Help/index.stories.ts +46 -35
  43. package/app/components/ui/Icon/index.stories.ts +41 -43
  44. package/app/components/ui/Input/index.stories.ts +95 -41
  45. package/app/components/ui/Input/index.vue +1 -1
  46. package/app/components/ui/InputCurrency/index.stories.ts +89 -49
  47. package/app/components/ui/InputNumber/index.stories.ts +93 -29
  48. package/app/components/ui/InputNumber/index.vue +1 -1
  49. package/app/components/ui/InputOtp/index.stories.ts +6 -7
  50. package/app/components/ui/InputOtp/index.vue +1 -1
  51. package/app/components/ui/InputPercent/index.stories.ts +6 -7
  52. package/app/components/ui/InputRange/index.stories.ts +6 -7
  53. package/app/components/ui/Loading/index.stories.ts +19 -19
  54. package/app/components/ui/Markdown/index.stories.ts +7 -10
  55. package/app/components/ui/Modal/index.stories.ts +135 -80
  56. package/app/components/ui/Modal/index.vue +1 -1
  57. package/app/components/ui/Modal/types.ts +1 -1
  58. package/app/components/ui/ModalContent/index.stories.ts +54 -26
  59. package/app/components/ui/ModalContent/index.vue +2 -2
  60. package/app/components/ui/PageCard/index.stories.ts +177 -67
  61. package/app/components/ui/Pagination/index.stories.ts +68 -51
  62. package/app/components/ui/Pagination/index.vue +2 -2
  63. package/app/components/ui/Popover/index.stories.ts +47 -45
  64. package/app/components/ui/Popover/index.vue +1 -1
  65. package/app/components/ui/Qrcode/index.stories.ts +42 -34
  66. package/app/components/ui/RadioCardGroup/index.stories.ts +23 -32
  67. package/app/components/ui/RadioCardGroup/index.vue +1 -1
  68. package/app/components/ui/RadioGroup/index.stories.ts +123 -0
  69. package/app/components/ui/RadioGroup/index.vue +73 -0
  70. package/app/components/ui/RadioGroup/types.ts +13 -0
  71. package/app/components/ui/ScrollArea/index.stories.ts +69 -37
  72. package/app/components/ui/ScrollArea/index.vue +1 -1
  73. package/app/components/ui/SearchSelect/index.stories.ts +104 -66
  74. package/app/components/ui/Select/index.stories.ts +152 -98
  75. package/app/components/ui/Select/index.vue +3 -3
  76. package/app/components/ui/Skeleton/index.stories.ts +27 -30
  77. package/app/components/ui/Skeleton/index.vue +1 -1
  78. package/app/components/ui/Slider/index.stories.ts +73 -31
  79. package/app/components/ui/Slider/index.vue +1 -1
  80. package/app/components/ui/Surface/index.stories.ts +47 -21
  81. package/app/components/ui/Surface/index.vue +39 -28
  82. package/app/components/ui/Surface/types.ts +2 -2
  83. package/app/components/ui/Switch/index.stories.ts +6 -7
  84. package/app/components/ui/Switch/index.vue +1 -1
  85. package/app/components/ui/Tabs/index.stories.ts +103 -61
  86. package/app/components/ui/Tabs/index.vue +1 -1
  87. package/app/components/ui/Tag/index.stories.ts +42 -25
  88. package/app/components/ui/Tag/index.vue +39 -28
  89. package/app/components/ui/Tag/types.ts +2 -2
  90. package/app/components/ui/Textarea/index.stories.ts +73 -9
  91. package/app/components/ui/Textarea/index.vue +1 -1
  92. package/app/components/ui/Toast/index.stories.ts +71 -18
  93. package/app/components/ui/Toast/index.vue +1 -1
  94. package/app/components/ui/Tooltip/index.stories.ts +45 -38
  95. package/app/components/ui/Tooltip/index.vue +1 -1
  96. package/app/components/ui/WebLink/index.stories.ts +76 -41
  97. package/app/components/ui/WebLink/index.vue +1 -1
  98. package/package.json +2 -2
  99. package/app/components/ui/Radio/index.stories.ts +0 -71
  100. package/app/components/ui/Radio/index.vue +0 -10
  101. package/app/components/ui/Radio/types.ts +0 -3
@@ -24,11 +24,11 @@
24
24
 
25
25
  /* Status */
26
26
  --status-foreground: oklch(0.985 0 0);
27
- --success: oklch(0.58 0.17 163.225);
28
- --info: oklch(0.58 0.15 221.723);
29
- --help: oklch(0.53 0.21 293.541);
30
- --warn: oklch(0.55 0.18 62.823);
31
- --danger: oklch(0.56 0.25 27.325);
27
+ --success: oklch(0.58 0.19 150.81);
28
+ --info: oklch(0.58 0.195 253.83);
29
+ --help: oklch(0.58 0.21 293.541);
30
+ --warn: oklch(0.58 0.158 72.33);
31
+ --danger: oklch(0.58 0.233 25.74);
32
32
  }
33
33
 
34
34
  .dark {
@@ -55,11 +55,11 @@
55
55
 
56
56
  /* Status */
57
57
  --status-foreground: oklch(0.145 0 0);
58
- --success: oklch(0.696 0.17 162.48);
59
- --info: oklch(0.685 0.121 220.7);
60
- --help: oklch(0.627 0.22 293.541);
61
- --warn: oklch(0.762 0.148 62.823);
62
- --danger: oklch(0.637 0.237 25.331);
58
+ --success: oklch(0.73 0.19 150.81);
59
+ --info: oklch(0.72 0.19 253.83);
60
+ --help: oklch(0.72 0.22 293.541);
61
+ --warn: oklch(0.78 0.14 72.33);
62
+ --danger: oklch(0.70 0.22 25.74);
63
63
  }
64
64
 
65
65
  /* Register colors in Tailwind v4 theme (supports opacity modifier) */
@@ -1,7 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import type { AccordionItem } from './types'
2
3
  import Icon from '../Icon/index.vue'
3
4
  import Surface from '../Surface/index.vue'
4
- import type { AccordionItem } from './types'
5
5
  import Accordion from './index.vue'
6
6
 
7
7
  const items: AccordionItem[] = [
@@ -38,71 +38,75 @@ const meta = {
38
38
  collapsible: true,
39
39
  disabled: false,
40
40
  },
41
+ render: args => ({
42
+ components: { Accordion, Surface },
43
+ setup: () => ({ args, items }),
44
+ template: `
45
+ <Surface variant="bordered" class="max-w-md px-4">
46
+ <Accordion v-bind="args" :items="items" default-value="shipping" />
47
+ </Surface>
48
+ `,
49
+ }),
41
50
  } satisfies Meta
42
51
 
43
52
  export default meta
44
53
  type Story = StoryObj<typeof meta>
45
54
 
46
- export const Default: Story = {
47
- render: args => ({
48
- components: { Accordion, Surface, Icon },
55
+ export const Default: Story = {}
56
+
57
+ export const Multiple: Story = {
58
+ render: () => ({
59
+ components: { Accordion, Surface },
49
60
  setup () {
50
- const singleValue = ref<string>('shipping')
51
- const multipleValue = ref<string[]>([ 'shipping', 'returns' ])
52
- const slotValue = ref<string>('shipping')
53
- return { args, items, singleValue, multipleValue, slotValue }
61
+ const value = ref<string[]>([ 'shipping', 'returns' ])
62
+ return { items, value }
54
63
  },
55
64
  template: `
56
- <div class="max-w-md space-y-10">
57
- <!-- Controlled -->
58
- <section>
59
- <h3 class="mb-4 text-lg font-medium">Controlled</h3>
60
- <Surface variant="bordered" class="px-4">
61
- <Accordion
62
- v-model="singleValue"
63
- v-bind="args"
64
- :items="items"
65
- />
66
- </Surface>
67
- <div class="mt-3 text-sm text-muted-foreground">Open: {{ singleValue || '(none)' }}</div>
68
- </section>
65
+ <div class="max-w-md">
66
+ <Surface variant="bordered" class="px-4">
67
+ <Accordion v-model="value" type="multiple" :items="items" />
68
+ </Surface>
69
+ <div class="mt-3 text-sm text-muted-foreground">Open: {{ value.join(', ') || '(none)' }}</div>
70
+ </div>
71
+ `,
72
+ }),
73
+ }
69
74
 
70
- <!-- Multiple -->
71
- <section>
72
- <h3 class="mb-4 text-lg font-medium">Multiple</h3>
73
- <Surface variant="bordered" class="px-4">
74
- <Accordion
75
- v-model="multipleValue"
76
- type="multiple"
77
- :items="items"
78
- />
79
- </Surface>
80
- <div class="mt-3 text-sm text-muted-foreground">Open: {{ multipleValue.join(', ') || '(none)' }}</div>
81
- </section>
75
+ export const Disabled: Story = {
76
+ render: () => ({
77
+ components: { Accordion, Surface },
78
+ setup: () => ({ items }),
79
+ template: `
80
+ <Surface variant="bordered" class="max-w-md px-4">
81
+ <Accordion disabled :items="items" default-value="shipping" />
82
+ </Surface>
83
+ `,
84
+ }),
85
+ }
82
86
 
83
- <!-- Custom Slots -->
84
- <section>
85
- <h3 class="mb-4 text-lg font-medium">Custom Slots</h3>
86
- <Surface variant="bordered" class="px-4">
87
- <Accordion
88
- v-model="slotValue"
89
- :items="items"
90
- >
91
- <template #title="{ item, open }">
92
- <span class="flex items-center gap-2">
93
- <span class="inline-flex size-5 items-center justify-center rounded-full bg-primary/15 text-primary">
94
- <Icon :name="open ? 'minus' : 'plus'" class="size-3" />
95
- </span>
96
- {{ item.title }}
97
- </span>
98
- </template>
99
- <template #content="{ item }">
100
- <p class="text-muted-foreground">{{ item.content }}</p>
101
- </template>
102
- </Accordion>
103
- </Surface>
104
- </section>
105
- </div>
87
+ export const CustomSlots: Story = {
88
+ render: () => ({
89
+ components: { Accordion, Surface, Icon },
90
+ setup () {
91
+ const value = ref<string>('shipping')
92
+ return { items, value }
93
+ },
94
+ template: `
95
+ <Surface variant="bordered" class="max-w-md px-4">
96
+ <Accordion v-model="value" :items="items">
97
+ <template #title="{ item, open }">
98
+ <span class="flex items-center gap-2">
99
+ <span class="inline-flex size-5 items-center justify-center rounded-full bg-primary/15 text-primary">
100
+ <Icon :name="open ? 'minus' : 'plus'" class="size-3" />
101
+ </span>
102
+ {{ item.title }}
103
+ </span>
104
+ </template>
105
+ <template #content="{ item }">
106
+ <p class="text-muted-foreground">{{ item.content }}</p>
107
+ </template>
108
+ </Accordion>
109
+ </Surface>
106
110
  `,
107
111
  }),
108
112
  }
@@ -4,7 +4,7 @@ import {
4
4
  AccordionContent,
5
5
  AccordionItem as ShadcnAccordionItem,
6
6
  AccordionTrigger,
7
- } from '@polymarbot/nuxt-layer-shadcn-ui/app/components/shadcn/accordion'
7
+ } from '../../shadcn/accordion'
8
8
  import type { AccordionRootProps } from 'reka-ui'
9
9
  import type { AccordionItem, AccordionProps } from './types'
10
10
 
@@ -140,7 +140,6 @@ function hasActiveChild (item: AdminLayoutSidebarMenuItem): boolean {
140
140
  <WebLink
141
141
  v-if="isLink(item)"
142
142
  :href="item.href"
143
- :externalIcon="false"
144
143
  unstyled
145
144
  >
146
145
  <Icon
@@ -215,7 +214,6 @@ function hasActiveChild (item: AdminLayoutSidebarMenuItem): boolean {
215
214
  <WebLink
216
215
  v-if="isLink(item)"
217
216
  :href="item.href"
218
- :externalIcon="false"
219
217
  unstyled
220
218
  >
221
219
  <Icon
@@ -6,6 +6,9 @@ import Breadcrumb from '../Breadcrumb/index.vue'
6
6
  import Button from '../Button/index.vue'
7
7
  import Card from '../Card/index.vue'
8
8
 
9
+ const variants = [ 'sidebar', 'floating', 'inset' ] as const
10
+ const collapsibles = [ 'icon', 'offcanvas', 'none' ] as const
11
+
9
12
  const menus: AdminLayoutSidebarMenuItem[] = [
10
13
  {
11
14
  label: 'Dashboard',
@@ -91,17 +94,15 @@ const meta = {
91
94
  title: 'UI/AdminLayout',
92
95
  component: AdminLayout,
93
96
  argTypes: {
94
- variant: {
95
- control: 'select',
96
- options: [ 'sidebar', 'floating', 'inset' ],
97
- },
98
- collapsible: {
99
- control: 'select',
100
- options: [ 'icon', 'offcanvas', 'none' ],
101
- },
97
+ menus: { control: 'object' },
98
+ headerDropdown: { control: 'object' },
99
+ footerDropdown: { control: 'object' },
100
+ variant: { control: 'select', options: variants },
101
+ collapsible: { control: 'select', options: collapsibles },
102
102
  },
103
103
  args: {
104
104
  menus,
105
+ headerDropdown: undefined,
105
106
  footerDropdown: { profile, menuItems },
106
107
  variant: 'sidebar',
107
108
  collapsible: 'icon',
@@ -1,8 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import type { AlertType } from './types'
2
3
  import Alert from './index.vue'
3
4
 
4
- type AlertType = 'default' | 'success' | 'info' | 'help' | 'warn' | 'danger'
5
-
6
5
  const types: AlertType[] = [ 'default', 'success', 'info', 'help', 'warn', 'danger' ]
7
6
 
8
7
  const meta = {
@@ -14,39 +13,42 @@ const meta = {
14
13
  },
15
14
  args: {
16
15
  type: 'default',
16
+ icon: '',
17
17
  },
18
+ render: args => ({
19
+ components: { Alert },
20
+ setup: () => ({ args }),
21
+ template: '<Alert v-bind="args">This is an alert message.</Alert>',
22
+ }),
18
23
  } satisfies Meta<typeof Alert>
19
24
 
20
25
  export default meta
21
26
  type Story = StoryObj<typeof meta>
22
27
 
23
- export const Default: Story = {
24
- render: args => ({
28
+ export const Default: Story = {}
29
+
30
+ export const Types: Story = {
31
+ render: () => ({
25
32
  components: { Alert },
26
- setup: () => ({ args, types }),
33
+ setup: () => ({ types }),
27
34
  template: `
28
- <div class="space-y-10">
29
- <!-- Controlled -->
30
- <section>
31
- <h3 class="mb-4 text-lg font-medium">Controlled</h3>
32
- <Alert v-bind="args">This is a controlled alert message.</Alert>
33
- </section>
34
-
35
- <!-- All Types -->
36
- <section>
37
- <h3 class="mb-4 text-lg font-medium">All Types</h3>
38
- <div class="space-y-3">
39
- <Alert v-for="t in types" :key="t" :type="t">
40
- This is a <strong>{{ t }}</strong> alert message.
41
- </Alert>
42
- </div>
43
- </section>
35
+ <div class="space-y-3">
36
+ <Alert v-for="t in types" :key="t" :type="t">
37
+ This is a <strong>{{ t }}</strong> alert message.
38
+ </Alert>
39
+ </div>
40
+ `,
41
+ }),
42
+ }
44
43
 
45
- <!-- Custom Icon -->
46
- <section>
47
- <h3 class="mb-4 text-lg font-medium">Custom Icon</h3>
48
- <Alert type="info" icon="bell">Alert with a custom bell icon.</Alert>
49
- </section>
44
+ export const WithIcons: Story = {
45
+ render: () => ({
46
+ components: { Alert },
47
+ template: `
48
+ <div class="space-y-3">
49
+ <Alert type="info" icon="bell">Alert with a custom bell icon.</Alert>
50
+ <Alert type="success" icon="sparkles">Alert with a custom sparkles icon.</Alert>
51
+ <Alert type="warn" icon="flag">Alert with a custom flag icon.</Alert>
50
52
  </div>
51
53
  `,
52
54
  }),
@@ -1,9 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { Alert as ShadcnAlert } from '@polymarbot/nuxt-layer-shadcn-ui/app/components/shadcn/alert'
2
+ import { Alert as ShadcnAlert } from '../../shadcn/alert'
3
3
  import type { AlertProps, AlertType } from './types'
4
4
 
5
- const typeIconNameMap: Record<AlertType, string> = {
6
- default: 'info',
5
+ const typeIconNameMap: Partial<Record<AlertType, string>> = {
7
6
  success: 'circle-check',
8
7
  info: 'info',
9
8
  help: 'circle-question-mark',
@@ -27,7 +26,8 @@ const props = withDefaults(defineProps<AlertProps>(), {
27
26
  })
28
27
 
29
28
  const defaultIconName = computed(() => {
30
- if (props.icon) return undefined
29
+ // null explicitly hides the icon; any truthy value is an explicit icon.
30
+ if (props.icon || props.icon === null) return undefined
31
31
  return typeIconNameMap[props.type]
32
32
  })
33
33
 
@@ -42,7 +42,7 @@ const mergedClass = computed(() =>
42
42
  <template>
43
43
  <ShadcnAlert :class="mergedClass">
44
44
  <Icon
45
- v-if="typeof icon === 'string'"
45
+ v-if="typeof icon === 'string' && icon"
46
46
  :name="icon"
47
47
  />
48
48
  <component
@@ -54,7 +54,7 @@ const mergedClass = computed(() =>
54
54
  v-else-if="defaultIconName"
55
55
  :name="defaultIconName"
56
56
  />
57
- <div>
57
+ <div class="col-start-2">
58
58
  <slot />
59
59
  </div>
60
60
  </ShadcnAlert>
@@ -4,6 +4,7 @@ export type AlertType = 'default' | 'success' | 'info' | 'help' | 'warn' | 'dang
4
4
 
5
5
  export interface AlertProps {
6
6
  type?: AlertType
7
- icon?: string | Component
7
+ /** Pass `null` to explicitly hide the icon; leave undefined to use the type's default. */
8
+ icon?: string | Component | null
8
9
  class?: ClassValue
9
10
  }
@@ -12,30 +12,79 @@ const meta = {
12
12
  template: '<div><story /><AlertDialog /></div>',
13
13
  }),
14
14
  ],
15
+ render: () => ({
16
+ components: { Button },
17
+ setup () {
18
+ const { confirm } = useDialog()
19
+ const result = ref('')
20
+ async function handleConfirm () {
21
+ const ok = await confirm({ title: 'Confirm', message: 'Do you want to proceed?' })
22
+ result.value = `confirm → ${ok}`
23
+ }
24
+ return { handleConfirm, result }
25
+ },
26
+ template: `
27
+ <div class="space-y-4">
28
+ <Button @click="handleConfirm">Confirm</Button>
29
+ <div v-if="result" class="rounded-md bg-muted p-4 text-sm">Result: {{ result }}</div>
30
+ </div>
31
+ `,
32
+ }),
15
33
  } satisfies Meta<typeof AlertDialog>
16
34
 
17
35
  export default meta
18
36
  type Story = StoryObj<typeof meta>
19
37
 
20
- export const Default: Story = {
38
+ export const Default: Story = {}
39
+
40
+ export const Alert: Story = {
21
41
  render: () => ({
22
42
  components: { Button },
23
43
  setup () {
24
- const { confirm, alert, destroy } = useDialog()
44
+ const { alert } = useDialog()
25
45
  const result = ref('')
26
-
27
- async function handleConfirm () {
28
- const ok = await confirm({ title: 'Confirm', message: 'Do you want to proceed?' })
29
- result.value = `confirm → ${ok}`
30
- }
31
46
  async function handleAlert () {
32
47
  await alert({ title: 'Error', type: 'error', message: 'Something went wrong.' })
33
48
  result.value = 'alert → closed'
34
49
  }
50
+ return { handleAlert, result }
51
+ },
52
+ template: `
53
+ <div class="space-y-4">
54
+ <Button variant="outline" @click="handleAlert">Alert</Button>
55
+ <div v-if="result" class="rounded-md bg-muted p-4 text-sm">Result: {{ result }}</div>
56
+ </div>
57
+ `,
58
+ }),
59
+ }
60
+
61
+ export const Destroy: Story = {
62
+ render: () => ({
63
+ components: { Button },
64
+ setup () {
65
+ const { destroy } = useDialog()
66
+ const result = ref('')
35
67
  async function handleDestroy () {
36
68
  const ok = await destroy({ title: 'Delete Item', message: 'This action cannot be undone.' })
37
69
  result.value = `destroy → ${ok}`
38
70
  }
71
+ return { handleDestroy, result }
72
+ },
73
+ template: `
74
+ <div class="space-y-4">
75
+ <Button variant="destructive" @click="handleDestroy">Destroy</Button>
76
+ <div v-if="result" class="rounded-md bg-muted p-4 text-sm">Result: {{ result }}</div>
77
+ </div>
78
+ `,
79
+ }),
80
+ }
81
+
82
+ export const MultiDialog: Story = {
83
+ render: () => ({
84
+ components: { Button },
85
+ setup () {
86
+ const { confirm, alert, destroy } = useDialog()
87
+ const result = ref('')
39
88
  async function handleMulti () {
40
89
  result.value = 'waiting...'
41
90
  const r1 = confirm({ title: 'Dialog 1', message: 'First confirm dialog' })
@@ -44,6 +93,23 @@ export const Default: Story = {
44
93
  const [ v1, , v3 ] = await Promise.all([ r1, r2, r3 ])
45
94
  result.value = `multi → confirm: ${v1}, alert: done, destroy: ${v3}`
46
95
  }
96
+ return { handleMulti, result }
97
+ },
98
+ template: `
99
+ <div class="space-y-4">
100
+ <Button variant="secondary" @click="handleMulti">Multi Dialog</Button>
101
+ <div v-if="result" class="rounded-md bg-muted p-4 text-sm">Result: {{ result }}</div>
102
+ </div>
103
+ `,
104
+ }),
105
+ }
106
+
107
+ export const WithTypes: Story = {
108
+ render: () => ({
109
+ components: { Button },
110
+ setup () {
111
+ const { confirm, alert, destroy } = useDialog()
112
+ const result = ref('')
47
113
  async function handleWithType () {
48
114
  const ok = await confirm({ title: 'Warning', type: 'warn', message: 'This operation may affect your data. Do you want to continue?' })
49
115
  result.value = `typed confirm → ${ok}`
@@ -52,55 +118,24 @@ export const Default: Story = {
52
118
  await alert({ title: 'Information', type: 'info', message: 'Your changes have been saved successfully.' })
53
119
  result.value = 'typed alert → closed'
54
120
  }
55
- async function handleDestroyDanger () {
56
- const ok = await destroy({ title: 'Delete Account', message: 'All data will be permanently removed. This action cannot be undone.' })
57
- result.value = `typed destroy → ${ok}`
58
- }
59
121
  async function handleSuccess () {
60
122
  await alert({ title: 'Success', type: 'success', message: 'Payment completed successfully.' })
61
123
  result.value = 'success alert → closed'
62
124
  }
63
-
64
- return { handleConfirm, handleAlert, handleDestroy, handleMulti, handleWithType, handleAlertInfo, handleDestroyDanger, handleSuccess, result }
125
+ async function handleDestroyDanger () {
126
+ const ok = await destroy({ title: 'Delete Account', message: 'All data will be permanently removed. This action cannot be undone.' })
127
+ result.value = `typed destroy → ${ok}`
128
+ }
129
+ return { handleWithType, handleAlertInfo, handleSuccess, handleDestroyDanger, result }
65
130
  },
66
131
  template: `
67
- <div class="space-y-10">
68
- <!-- Confirm -->
69
- <section>
70
- <h3 class="mb-4 text-lg font-medium">Confirm</h3>
71
- <Button @click="handleConfirm">Confirm</Button>
72
- </section>
73
-
74
- <!-- Alert -->
75
- <section>
76
- <h3 class="mb-4 text-lg font-medium">Alert</h3>
77
- <Button variant="outline" @click="handleAlert">Alert</Button>
78
- </section>
79
-
80
- <!-- Destroy -->
81
- <section>
82
- <h3 class="mb-4 text-lg font-medium">Destroy</h3>
83
- <Button variant="destructive" @click="handleDestroy">Destroy</Button>
84
- </section>
85
-
86
- <!-- Multi Dialog -->
87
- <section>
88
- <h3 class="mb-4 text-lg font-medium">Multi Dialog</h3>
89
- <Button variant="secondary" @click="handleMulti">Multi Dialog</Button>
90
- </section>
91
-
92
- <!-- With Type (ModalContent) -->
93
- <section>
94
- <h3 class="mb-4 text-lg font-medium">With Type (ModalContent)</h3>
95
- <div class="flex gap-2">
96
- <Button variant="outline" @click="handleWithType">Warn Confirm</Button>
97
- <Button variant="outline" @click="handleAlertInfo">Info Alert</Button>
98
- <Button variant="outline" @click="handleSuccess">Success Alert</Button>
99
- <Button variant="destructive" @click="handleDestroyDanger">Danger Destroy</Button>
100
- </div>
101
- </section>
102
-
103
- <!-- Result -->
132
+ <div class="space-y-4">
133
+ <div class="flex gap-2">
134
+ <Button variant="outline" @click="handleWithType">Warn Confirm</Button>
135
+ <Button variant="outline" @click="handleAlertInfo">Info Alert</Button>
136
+ <Button variant="outline" @click="handleSuccess">Success Alert</Button>
137
+ <Button variant="destructive" @click="handleDestroyDanger">Danger Destroy</Button>
138
+ </div>
104
139
  <div v-if="result" class="rounded-md bg-muted p-4 text-sm">Result: {{ result }}</div>
105
140
  </div>
106
141
  `,
@@ -1,10 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
2
  import type { AsyncDataTableBatchAction, AsyncDataTableFetchParams, AsyncDataTableFetchResult } from './types'
3
3
  import type { DataTableColumn } from '../DataTable/types'
4
- import AsyncDataTableRaw from './index.vue'
5
-
6
- // Cast generic component for Storybook compatibility
7
- const AsyncDataTable = AsyncDataTableRaw as any
4
+ import AsyncDataTable from './index.vue'
8
5
 
9
6
  interface User {
10
7
  id: number
@@ -59,50 +56,70 @@ function mockFetch (params: AsyncDataTableFetchParams): Promise<AsyncDataTableFe
59
56
  })
60
57
  }
61
58
 
59
+ const batchActions: AsyncDataTableBatchAction<User>[] = [
60
+ {
61
+ label: 'Delete',
62
+ icon: 'trash-2',
63
+ color: 'danger',
64
+ action: items => console.debug(`Delete ${items.length} items`),
65
+ },
66
+ {
67
+ label: 'Export',
68
+ icon: 'download',
69
+ action: items => console.debug(`Export ${items.length} items`),
70
+ },
71
+ ]
72
+
62
73
  const meta = {
63
74
  title: 'UI/AsyncDataTable',
64
- component: AsyncDataTable,
65
- } satisfies Meta
75
+ component: AsyncDataTable as any,
76
+ argTypes: {
77
+ columns: { control: 'object' },
78
+ fetchMethod: { control: false },
79
+ autoFetch: { control: 'boolean' },
80
+ data: { control: 'object' },
81
+ filters: { control: 'object' },
82
+ showTopToolbar: { control: 'boolean' },
83
+ showBottomToolbar: { control: 'boolean' },
84
+ pageSizeOptions: { control: 'object' },
85
+ showPagination: { control: 'boolean' },
86
+ selectable: { control: 'boolean' },
87
+ batchActions: { control: 'object' },
88
+ selection: { control: 'object' },
89
+ },
90
+ args: {
91
+ columns,
92
+ fetchMethod: mockFetch,
93
+ autoFetch: true,
94
+ data: [],
95
+ filters: undefined,
96
+ showTopToolbar: undefined,
97
+ showBottomToolbar: true,
98
+ pageSizeOptions: [ 10, 20, 50 ],
99
+ showPagination: true,
100
+ selectable: false,
101
+ batchActions: [],
102
+ selection: [],
103
+ },
104
+ render: args => ({
105
+ components: { AsyncDataTable },
106
+ setup: () => ({ args }),
107
+ template: '<AsyncDataTable v-bind="args" />',
108
+ }),
109
+ } satisfies Meta<typeof AsyncDataTable>
66
110
 
67
111
  export default meta
68
112
  type Story = StoryObj<typeof meta>
69
113
 
70
114
  /** Async fetch with pagination, sorting, page size selector */
71
- export const Default: Story = {
72
- render: () => ({
73
- components: { AsyncDataTable },
74
- setup: () => ({ columns, mockFetch }),
75
- template: `
76
- <AsyncDataTable
77
- :columns="columns"
78
- :fetchMethod="mockFetch"
79
- :pageSizeOptions="[10, 20, 50]"
80
- />
81
- `,
82
- }),
83
- }
115
+ export const Default: Story = {}
84
116
 
85
117
  /** Batch actions with row selection and dual toolbars */
86
- export const BatchActions: Story = {
118
+ export const WithBatchActions: Story = {
87
119
  render: () => ({
88
120
  components: { AsyncDataTable },
89
121
  setup () {
90
122
  const selection = ref<User[]>([])
91
-
92
- const batchActions: AsyncDataTableBatchAction<User>[] = [
93
- {
94
- label: 'Delete',
95
- icon: 'trash-2',
96
- color: 'danger',
97
- action: items => console.debug(`Delete ${items.length} items`),
98
- },
99
- {
100
- label: 'Export',
101
- icon: 'download',
102
- action: items => console.debug(`Export ${items.length} items`),
103
- },
104
- ]
105
-
106
123
  return { columns, mockFetch, selection, batchActions }
107
124
  },
108
125
  template: `
@@ -121,7 +138,7 @@ export const BatchActions: Story = {
121
138
  }
122
139
 
123
140
  /** Custom toolbar slot with action button */
124
- export const CustomToolbar: Story = {
141
+ export const WithCustomToolbar: Story = {
125
142
  render: () => ({
126
143
  components: { AsyncDataTable },
127
144
  setup: () => ({ columns, mockFetch }),