@polymarbot/nuxt-layer-shadcn-ui 0.3.9 → 0.4.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 (63) hide show
  1. package/app/components/ui/Accordion/index.stories.ts +43 -15
  2. package/app/components/ui/AdminLayout/index.stories.ts +6 -14
  3. package/app/components/ui/Alert/index.stories.ts +32 -2
  4. package/app/components/ui/AlertDialog/index.stories.ts +114 -5
  5. package/app/components/ui/AsyncDataTable/index.stories.ts +36 -2
  6. package/app/components/ui/Avatar/index.stories.ts +58 -4
  7. package/app/components/ui/Badge/index.stories.ts +48 -3
  8. package/app/components/ui/Breadcrumb/index.stories.ts +8 -19
  9. package/app/components/ui/Button/index.stories.ts +116 -7
  10. package/app/components/ui/ButtonGroup/index.stories.ts +63 -4
  11. package/app/components/ui/Card/index.stories.ts +40 -14
  12. package/app/components/ui/Checkbox/index.stories.ts +53 -3
  13. package/app/components/ui/CopyButton/index.stories.ts +77 -5
  14. package/app/components/ui/DataTable/index.stories.ts +184 -11
  15. package/app/components/ui/DatePicker/index.stories.ts +56 -7
  16. package/app/components/ui/DateRangePicker/index.stories.ts +40 -5
  17. package/app/components/ui/Divider/index.stories.ts +18 -15
  18. package/app/components/ui/Drawer/index.stories.ts +115 -16
  19. package/app/components/ui/Drawer/index.vue +27 -12
  20. package/app/components/ui/Dropdown/index.stories.ts +72 -54
  21. package/app/components/ui/Dropdown/index.vue +5 -8
  22. package/app/components/ui/Dropdown/types.ts +3 -8
  23. package/app/components/ui/FormItem/index.stories.ts +33 -45
  24. package/app/components/ui/Help/index.stories.ts +34 -2
  25. package/app/components/ui/Icon/index.stories.ts +41 -2
  26. package/app/components/ui/Input/index.stories.ts +73 -31
  27. package/app/components/ui/Input/index.vue +1 -0
  28. package/app/components/ui/InputCurrency/index.stories.ts +20 -65
  29. package/app/components/ui/InputNumber/index.stories.ts +31 -58
  30. package/app/components/ui/InputOtp/index.stories.ts +41 -9
  31. package/app/components/ui/InputPercent/index.stories.ts +3 -7
  32. package/app/components/ui/InputRange/index.stories.ts +51 -4
  33. package/app/components/ui/Loading/index.stories.ts +16 -1
  34. package/app/components/ui/Markdown/index.stories.ts +9 -0
  35. package/app/components/ui/Modal/index.stories.ts +133 -16
  36. package/app/components/ui/Modal/index.vue +27 -12
  37. package/app/components/ui/ModalContent/index.stories.ts +35 -11
  38. package/app/components/ui/PageCard/index.stories.ts +154 -56
  39. package/app/components/ui/Pagination/index.stories.ts +75 -41
  40. package/app/components/ui/Pagination/index.vue +4 -1
  41. package/app/components/ui/Popover/index.stories.ts +73 -27
  42. package/app/components/ui/Popover/index.vue +67 -4
  43. package/app/components/ui/Popover/types.ts +5 -2
  44. package/app/components/ui/Qrcode/index.stories.ts +32 -2
  45. package/app/components/ui/RadioCardGroup/index.stories.ts +45 -6
  46. package/app/components/ui/RadioGroup/index.stories.ts +64 -52
  47. package/app/components/ui/ScrollArea/index.stories.ts +21 -23
  48. package/app/components/ui/SearchSelect/index.stories.ts +73 -24
  49. package/app/components/ui/Select/index.stories.ts +121 -6
  50. package/app/components/ui/Select/index.vue +7 -6
  51. package/app/components/ui/Skeleton/index.stories.ts +34 -2
  52. package/app/components/ui/Slider/index.stories.ts +67 -4
  53. package/app/components/ui/Surface/index.stories.ts +86 -5
  54. package/app/components/ui/Surface/index.vue +115 -2
  55. package/app/components/ui/Surface/types.ts +2 -0
  56. package/app/components/ui/Switch/index.stories.ts +46 -0
  57. package/app/components/ui/Tabs/index.stories.ts +61 -64
  58. package/app/components/ui/Tag/index.stories.ts +45 -3
  59. package/app/components/ui/Textarea/index.stories.ts +61 -32
  60. package/app/components/ui/Toast/index.stories.ts +77 -3
  61. package/app/components/ui/Tooltip/index.stories.ts +60 -2
  62. package/app/components/ui/WebLink/index.stories.ts +53 -15
  63. package/package.json +2 -2
@@ -6,15 +6,78 @@ import {
6
6
  } from '../../shadcn/popover'
7
7
  import type { PopoverProps } from './types'
8
8
 
9
- const props = defineProps<PopoverProps>()
9
+ defineOptions({ inheritAttrs: false })
10
+
11
+ const props = withDefaults(defineProps<PopoverProps>(), {
12
+ trigger: 'click',
13
+ class: undefined,
14
+ })
15
+
16
+ const { isMobile } = useDevice()
17
+
18
+ // Force click trigger on mobile devices for better touch experience
19
+ const effectiveTrigger = computed(() => isMobile.value ? 'click' : props.trigger)
20
+
21
+ const isOpen = ref(false)
22
+ let hideTimeout: ReturnType<typeof setTimeout> | null = null
23
+
24
+ const clearHideTimeout = () => {
25
+ if (hideTimeout) {
26
+ clearTimeout(hideTimeout)
27
+ hideTimeout = null
28
+ }
29
+ }
30
+
31
+ const hide = () => {
32
+ isOpen.value = false
33
+ }
34
+
35
+ const handleTriggerEnter = () => {
36
+ if (effectiveTrigger.value === 'hover') {
37
+ clearHideTimeout()
38
+ isOpen.value = true
39
+ }
40
+ }
41
+
42
+ const handleTriggerLeave = () => {
43
+ if (effectiveTrigger.value === 'hover') {
44
+ hideTimeout = setTimeout(hide, 100)
45
+ }
46
+ }
47
+
48
+ const handleContentEnter = () => {
49
+ if (effectiveTrigger.value === 'hover') {
50
+ clearHideTimeout()
51
+ }
52
+ }
53
+
54
+ const handleContentLeave = () => {
55
+ if (effectiveTrigger.value === 'hover') {
56
+ hideTimeout = setTimeout(hide, 100)
57
+ }
58
+ }
59
+
60
+ onBeforeUnmount(() => {
61
+ clearHideTimeout()
62
+ })
10
63
  </script>
11
64
 
12
65
  <template>
13
- <ShadcnPopover>
14
- <PopoverTrigger asChild>
66
+ <ShadcnPopover v-model:open="isOpen">
67
+ <PopoverTrigger
68
+ v-if="$slots.trigger"
69
+ asChild
70
+ @mouseenter="handleTriggerEnter"
71
+ @mouseleave="handleTriggerLeave"
72
+ >
15
73
  <slot name="trigger" />
16
74
  </PopoverTrigger>
17
- <PopoverContent :class="props.class">
75
+ <PopoverContent
76
+ v-bind="$attrs"
77
+ :class="props.class"
78
+ @mouseenter="handleContentEnter"
79
+ @mouseleave="handleContentLeave"
80
+ >
18
81
  <slot />
19
82
  </PopoverContent>
20
83
  </ShadcnPopover>
@@ -1,5 +1,8 @@
1
- import type { PopoverRootProps } from 'reka-ui'
1
+ import type { PopoverContentProps } from 'reka-ui'
2
2
 
3
- export interface PopoverProps extends /* @vue-ignore */ PopoverRootProps {
3
+ export interface PopoverProps extends /* @vue-ignore */ PopoverContentProps {
4
+ /** Trigger mode for showing the popover. Defaults to 'click'. */
5
+ trigger?: 'click' | 'hover'
6
+ /** Extra class for the popover content container. */
4
7
  class?: ClassValue
5
8
  }
@@ -29,7 +29,21 @@ const noControls = { controls: { disable: true }} satisfies Story['parameters']
29
29
  export const Default: Story = {}
30
30
 
31
31
  export const DynamicContent: Story = {
32
- parameters: noControls,
32
+ parameters: {
33
+ ...noControls,
34
+ docs: {
35
+ source: {
36
+ code: `
37
+ <template>
38
+ <input v-model="url" placeholder="Enter URL or text" />
39
+ <div class="size-48">
40
+ <Qrcode :content="url" />
41
+ </div>
42
+ </template>
43
+ `.trim(),
44
+ },
45
+ },
46
+ },
33
47
  render: () => ({
34
48
  components: { Qrcode },
35
49
  setup () {
@@ -52,7 +66,23 @@ export const DynamicContent: Story = {
52
66
  }
53
67
 
54
68
  export const Sizes: Story = {
55
- parameters: noControls,
69
+ parameters: {
70
+ ...noControls,
71
+ docs: {
72
+ source: {
73
+ code: `
74
+ <template>
75
+ <div class="size-24">
76
+ <Qrcode content="https://example.com/small" />
77
+ </div>
78
+ <div class="size-48">
79
+ <Qrcode content="https://example.com/medium" />
80
+ </div>
81
+ </template>
82
+ `.trim(),
83
+ },
84
+ },
85
+ },
56
86
  render: () => ({
57
87
  components: { Qrcode },
58
88
  template: `
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import EventLog from '#storybook/EventLog.vue'
2
3
  import type { RadioCardGroupOption } from './types'
3
4
  import RadioCardGroup from './index.vue'
4
5
 
@@ -70,7 +71,18 @@ const noControls = { controls: { disable: true }} satisfies Story['parameters']
70
71
  export const Default: Story = {}
71
72
 
72
73
  export const WithDisabledOption: Story = {
73
- parameters: noControls,
74
+ parameters: {
75
+ ...noControls,
76
+ docs: {
77
+ source: {
78
+ code: `
79
+ <template>
80
+ <RadioCardGroup v-model="selected" :options="planOptions" />
81
+ </template>
82
+ `.trim(),
83
+ },
84
+ },
85
+ },
74
86
  render: () => ({
75
87
  components: { RadioCardGroup },
76
88
  setup () {
@@ -88,17 +100,44 @@ export const WithDisabledOption: Story = {
88
100
 
89
101
  export const Disabled: Story = {
90
102
  parameters: noControls,
103
+ args: {
104
+ disabled: true,
105
+ },
106
+ }
107
+
108
+ export const EventHandling: Story = {
109
+ parameters: {
110
+ ...noControls,
111
+ docs: {
112
+ source: {
113
+ code: `
114
+ <template>
115
+ <RadioCardGroup
116
+ v-model="selected"
117
+ :options="options"
118
+ @update:modelValue="onUpdate"
119
+ />
120
+ </template>
121
+ `.trim(),
122
+ },
123
+ },
124
+ },
91
125
  render: () => ({
92
- components: { RadioCardGroup },
126
+ components: { RadioCardGroup, EventLog },
93
127
  setup () {
94
128
  const selected = ref('current')
95
129
  return { selected, options }
96
130
  },
97
131
  template: `
98
- <div class="max-w-md space-y-4">
99
- <RadioCardGroup v-model="selected" :options="options" disabled />
100
- <div class="text-sm text-muted-foreground">Selected: {{ selected }}</div>
101
- </div>
132
+ <EventLog v-slot="{ record }">
133
+ <div class="max-w-md">
134
+ <RadioCardGroup
135
+ v-model="selected"
136
+ :options="options"
137
+ @update:modelValue="(v) => record('update:modelValue', v)"
138
+ />
139
+ </div>
140
+ </EventLog>
102
141
  `,
103
142
  }),
104
143
  }
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import EventLog from '#storybook/EventLog.vue'
2
3
  import type { RadioGroupItem } from './types'
3
4
  import RadioGroup from './index.vue'
4
5
 
@@ -43,71 +44,47 @@ const noControls = { controls: { disable: true }} satisfies Story['parameters']
43
44
 
44
45
  export const Default: Story = {}
45
46
 
46
- export const Controlled: Story = {
47
- parameters: noControls,
48
- render: () => ({
49
- components: { RadioGroup },
50
- setup () {
51
- const selected = ref('option1')
52
- return { options, selected }
53
- },
54
- template: `
55
- <div class="space-y-2">
56
- <RadioGroup v-model="selected" :items="options" />
57
- <div class="text-sm text-muted-foreground">Selected: {{ selected }}</div>
58
- </div>
59
- `,
60
- }),
61
- }
62
-
63
47
  export const Horizontal: Story = {
64
48
  parameters: noControls,
65
- render: () => ({
66
- components: { RadioGroup },
67
- setup () {
68
- const selected = ref('option1')
69
- return { options, selected }
70
- },
71
- template: `
72
- <div class="space-y-2">
73
- <RadioGroup v-model="selected" :items="options" orientation="horizontal" />
74
- <div class="text-sm text-muted-foreground">Selected: {{ selected }}</div>
75
- </div>
76
- `,
77
- }),
49
+ args: {
50
+ orientation: 'horizontal',
51
+ },
78
52
  }
79
53
 
80
54
  export const WithDisabledItem: Story = {
81
55
  parameters: noControls,
82
- render: () => ({
83
- components: { RadioGroup },
84
- setup () {
85
- const plan = ref('pro')
86
- return { plans, plan }
87
- },
88
- template: `
89
- <div class="space-y-2">
90
- <RadioGroup v-model="plan" :items="plans" />
91
- <div class="text-sm text-muted-foreground">Plan: {{ plan }}</div>
92
- </div>
93
- `,
94
- }),
56
+ args: {
57
+ items: plans,
58
+ modelValue: 'pro',
59
+ },
95
60
  }
96
61
 
97
62
  export const Disabled: Story = {
98
63
  parameters: noControls,
99
- render: () => ({
100
- components: { RadioGroup },
101
- setup () {
102
- const selected = ref('option1')
103
- return { options, selected }
104
- },
105
- template: '<RadioGroup v-model="selected" :items="options" disabled />',
106
- }),
64
+ args: {
65
+ disabled: true,
66
+ },
107
67
  }
108
68
 
109
69
  export const CustomSlots: Story = {
110
- parameters: noControls,
70
+ parameters: {
71
+ ...noControls,
72
+ docs: {
73
+ source: {
74
+ code: `
75
+ <template>
76
+ <RadioGroup v-model="plan" :items="plans">
77
+ <template #label="{ item, checked }">
78
+ <span :class="checked ? 'font-semibold text-primary' : 'text-foreground'">
79
+ {{ item.label }}
80
+ </span>
81
+ </template>
82
+ </RadioGroup>
83
+ </template>
84
+ `.trim(),
85
+ },
86
+ },
87
+ },
111
88
  render: () => ({
112
89
  components: { RadioGroup },
113
90
  setup () {
@@ -128,3 +105,38 @@ export const CustomSlots: Story = {
128
105
  `,
129
106
  }),
130
107
  }
108
+
109
+ export const EventHandling: Story = {
110
+ parameters: {
111
+ ...noControls,
112
+ docs: {
113
+ source: {
114
+ code: `
115
+ <template>
116
+ <RadioGroup
117
+ v-model="selected"
118
+ :items="options"
119
+ @update:modelValue="onUpdate"
120
+ />
121
+ </template>
122
+ `.trim(),
123
+ },
124
+ },
125
+ },
126
+ render: () => ({
127
+ components: { RadioGroup, EventLog },
128
+ setup () {
129
+ const selected = ref('option1')
130
+ return { options, selected }
131
+ },
132
+ template: `
133
+ <EventLog v-slot="{ record }">
134
+ <RadioGroup
135
+ v-model="selected"
136
+ :items="options"
137
+ @update:modelValue="(v) => record('update:modelValue', v)"
138
+ />
139
+ </EventLog>
140
+ `,
141
+ }),
142
+ }
@@ -51,32 +51,30 @@ export const Default: Story = {}
51
51
 
52
52
  export const FadeMask: Story = {
53
53
  parameters: noControls,
54
- render: () => ({
55
- components: { ScrollArea },
56
- setup () {
57
- const items = Array.from({ length: 30 }, (_, i) => `Item ${i + 1}`)
58
- return { items }
59
- },
60
- template: `
61
- <div class="h-[250px] w-[250px] rounded-md border bg-card">
62
- <ScrollArea class="h-full" fadeMask>
63
- <div class="p-4 space-y-2">
64
- <div
65
- v-for="item in items"
66
- :key="item"
67
- class="rounded-md border bg-muted px-3 py-2 text-sm"
68
- >
69
- {{ item }}
70
- </div>
71
- </div>
72
- </ScrollArea>
73
- </div>
74
- `,
75
- }),
54
+ args: {
55
+ fadeMask: true,
56
+ },
76
57
  }
77
58
 
78
59
  export const NoOverflow: Story = {
79
- parameters: noControls,
60
+ parameters: {
61
+ ...noControls,
62
+ docs: {
63
+ source: {
64
+ code: `
65
+ <template>
66
+ <ScrollArea class="h-full" fadeMask>
67
+ <div class="p-4 space-y-2">
68
+ <div v-for="item in fewItems" :key="item" class="rounded-md border bg-muted px-3 py-2 text-sm">
69
+ {{ item }}
70
+ </div>
71
+ </div>
72
+ </ScrollArea>
73
+ </template>
74
+ `.trim(),
75
+ },
76
+ },
77
+ },
80
78
  render: () => ({
81
79
  components: { ScrollArea },
82
80
  setup () {
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import EventLog from '#storybook/EventLog.vue'
2
3
  import type { SearchSelectLoadMethodParams, SearchSelectLoadMethodResult } from './types'
3
4
  import SearchSelect from './index.vue'
4
5
 
@@ -88,7 +89,23 @@ const noControls = { controls: { disable: true }} satisfies Story['parameters']
88
89
  export const Default: Story = {}
89
90
 
90
91
  export const Preselected: Story = {
91
- parameters: noControls,
92
+ parameters: {
93
+ ...noControls,
94
+ docs: {
95
+ source: {
96
+ code: `
97
+ <template>
98
+ <SearchSelect
99
+ v-model="value"
100
+ :loadMethod="loadMethod"
101
+ :loadValueOptionMethod="loadValueOptionMethod"
102
+ autoLoad
103
+ />
104
+ </template>
105
+ `.trim(),
106
+ },
107
+ },
108
+ },
92
109
  render: () => ({
93
110
  components: { SearchSelect },
94
111
  setup () {
@@ -110,7 +127,23 @@ export const Preselected: Story = {
110
127
  }
111
128
 
112
129
  export const WithDefaultOptions: Story = {
113
- parameters: noControls,
130
+ parameters: {
131
+ ...noControls,
132
+ docs: {
133
+ source: {
134
+ code: `
135
+ <template>
136
+ <SearchSelect
137
+ v-model="value"
138
+ :loadMethod="loadMethod"
139
+ :defaultOptions="defaultOptions"
140
+ autoLoad
141
+ />
142
+ </template>
143
+ `.trim(),
144
+ },
145
+ },
146
+ },
114
147
  render: () => ({
115
148
  components: { SearchSelect },
116
149
  setup () {
@@ -133,38 +166,54 @@ export const WithDefaultOptions: Story = {
133
166
 
134
167
  export const WithCreateNew: Story = {
135
168
  parameters: noControls,
136
- render: () => ({
137
- components: { SearchSelect },
138
- setup () {
139
- const value = ref<string>()
140
- return { value, mockLoadMethod }
141
- },
142
- template: `
143
- <div class="max-w-sm">
144
- <SearchSelect
145
- v-model="value"
146
- :loadMethod="mockLoadMethod"
147
- createNewTo="/create"
148
- autoLoad
149
- />
150
- <div class="mt-2 text-sm text-muted-foreground">Selected: {{ value ?? 'none' }}</div>
151
- </div>
152
- `,
153
- }),
169
+ args: {
170
+ createNewTo: '/create',
171
+ autoLoad: true,
172
+ },
154
173
  }
155
174
 
156
175
  export const Disabled: Story = {
157
176
  parameters: noControls,
177
+ args: {
178
+ disabled: true,
179
+ },
180
+ }
181
+
182
+ export const EventHandling: Story = {
183
+ parameters: {
184
+ ...noControls,
185
+ docs: {
186
+ source: {
187
+ code: `
188
+ <template>
189
+ <SearchSelect
190
+ v-model="value"
191
+ :loadMethod="loadMethod"
192
+ autoLoad
193
+ @update:modelValue="onUpdate"
194
+ />
195
+ </template>
196
+ `.trim(),
197
+ },
198
+ },
199
+ },
158
200
  render: () => ({
159
- components: { SearchSelect },
201
+ components: { SearchSelect, EventLog },
160
202
  setup () {
161
203
  const value = ref<string>()
162
204
  return { value, mockLoadMethod }
163
205
  },
164
206
  template: `
165
- <div class="max-w-sm">
166
- <SearchSelect v-model="value" :loadMethod="mockLoadMethod" disabled />
167
- </div>
207
+ <EventLog v-slot="{ record }">
208
+ <div class="max-w-sm">
209
+ <SearchSelect
210
+ v-model="value"
211
+ :loadMethod="mockLoadMethod"
212
+ autoLoad
213
+ @update:modelValue="(v) => record('update:modelValue', v)"
214
+ />
215
+ </div>
216
+ </EventLog>
168
217
  `,
169
218
  }),
170
219
  }