@polymarbot/nuxt-layer-shadcn-ui 0.5.3 → 0.6.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.
@@ -10,15 +10,19 @@ const meta = {
10
10
  argTypes: {
11
11
  type: { control: 'select', options: types },
12
12
  icon: { control: 'text' },
13
+ title: { control: 'text' },
14
+ description: { control: 'text' },
13
15
  },
14
16
  args: {
15
17
  type: 'default',
16
18
  icon: '',
19
+ title: 'Heads up!',
20
+ description: 'You can add components to your app using the cli.',
17
21
  },
18
22
  render: args => ({
19
23
  components: { Alert },
20
24
  setup: () => ({ args }),
21
- template: '<Alert v-bind="args">This is an alert message.</Alert>',
25
+ template: '<Alert v-bind="args" />',
22
26
  }),
23
27
  } satisfies Meta<typeof Alert>
24
28
 
@@ -37,9 +41,13 @@ export const Types: Story = {
37
41
  code: `
38
42
  <template>
39
43
  <div class="space-y-3">
40
- <Alert v-for="t in types" :key="t" :type="t">
41
- This is a <strong>{{ t }}</strong> alert message.
42
- </Alert>
44
+ <Alert
45
+ v-for="t in types"
46
+ :key="t"
47
+ :type="t"
48
+ :title="t"
49
+ description="This is an alert message."
50
+ />
43
51
  </div>
44
52
  </template>
45
53
  `.trim(),
@@ -51,14 +59,36 @@ export const Types: Story = {
51
59
  setup: () => ({ types }),
52
60
  template: `
53
61
  <div class="space-y-3">
54
- <Alert v-for="t in types" :key="t" :type="t">
55
- This is a <strong>{{ t }}</strong> alert message.
56
- </Alert>
62
+ <Alert
63
+ v-for="t in types"
64
+ :key="t"
65
+ :type="t"
66
+ :title="t"
67
+ description="This is an alert message."
68
+ />
57
69
  </div>
58
70
  `,
59
71
  }),
60
72
  }
61
73
 
74
+ export const TitleOnly: Story = {
75
+ parameters: noControls,
76
+ args: {
77
+ type: 'info',
78
+ title: 'A short title without description.',
79
+ description: '',
80
+ },
81
+ }
82
+
83
+ export const DescriptionOnly: Story = {
84
+ parameters: noControls,
85
+ args: {
86
+ type: 'info',
87
+ title: '',
88
+ description: 'A standalone description without a title.',
89
+ },
90
+ }
91
+
62
92
  export const WithIcons: Story = {
63
93
  parameters: {
64
94
  ...noControls,
@@ -67,9 +97,9 @@ export const WithIcons: Story = {
67
97
  code: `
68
98
  <template>
69
99
  <div class="space-y-3">
70
- <Alert type="info" icon="bell">Alert with a custom bell icon.</Alert>
71
- <Alert type="success" icon="sparkles">Alert with a custom sparkles icon.</Alert>
72
- <Alert type="warn" icon="flag">Alert with a custom flag icon.</Alert>
100
+ <Alert type="info" icon="bell" title="Custom bell icon" description="Override the default icon for this type." />
101
+ <Alert type="success" icon="sparkles" title="Custom sparkles icon" description="Pass any lucide icon name." />
102
+ <Alert type="warn" icon="flag" title="Custom flag icon" description="Works for every type." />
73
103
  </div>
74
104
  </template>
75
105
  `.trim(),
@@ -80,10 +110,68 @@ export const WithIcons: Story = {
80
110
  components: { Alert },
81
111
  template: `
82
112
  <div class="space-y-3">
83
- <Alert type="info" icon="bell">Alert with a custom bell icon.</Alert>
84
- <Alert type="success" icon="sparkles">Alert with a custom sparkles icon.</Alert>
85
- <Alert type="warn" icon="flag">Alert with a custom flag icon.</Alert>
113
+ <Alert type="info" icon="bell" title="Custom bell icon" description="Override the default icon for this type." />
114
+ <Alert type="success" icon="sparkles" title="Custom sparkles icon" description="Pass any lucide icon name." />
115
+ <Alert type="warn" icon="flag" title="Custom flag icon" description="Works for every type." />
86
116
  </div>
87
117
  `,
88
118
  }),
89
119
  }
120
+
121
+ export const HiddenIcon: Story = {
122
+ parameters: {
123
+ ...noControls,
124
+ docs: {
125
+ source: {
126
+ code: `
127
+ <template>
128
+ <Alert type="success" :icon="null" title="Icon hidden" description="Pass icon=\\"null\\" to suppress the type's default icon." />
129
+ </template>
130
+ `.trim(),
131
+ },
132
+ },
133
+ },
134
+ render: () => ({
135
+ components: { Alert },
136
+ template: `
137
+ <Alert type="success" :icon="null" title="Icon hidden" description="Pass icon=&quot;null&quot; to suppress the type's default icon." />
138
+ `,
139
+ }),
140
+ }
141
+
142
+ export const CustomSlots: Story = {
143
+ parameters: {
144
+ ...noControls,
145
+ docs: {
146
+ source: {
147
+ code: `
148
+ <template>
149
+ <Alert type="info">
150
+ <template #icon>
151
+ <Icon name="rocket" />
152
+ </template>
153
+ <template #title>
154
+ <span class="underline">Custom title slot</span>
155
+ </template>
156
+ Description rendered via the default slot. You can put <strong>rich content</strong> here.
157
+ </Alert>
158
+ </template>
159
+ `.trim(),
160
+ },
161
+ },
162
+ },
163
+ render: () => ({
164
+ components: { Alert },
165
+ template: `
166
+ <Alert type="info">
167
+ <template #icon>
168
+ <Icon name="rocket" />
169
+ </template>
170
+ <template #title>
171
+ <span class="underline">Custom title slot</span>
172
+ </template>
173
+ Description rendered via the default slot. You can put <strong>rich content</strong> here.
174
+ </Alert>
175
+ `,
176
+ }),
177
+ }
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { Alert as ShadcnAlert } from '../../shadcn/alert'
2
+ import { Alert as ShadcnAlert, AlertDescription, AlertTitle } from '../../shadcn/alert'
3
3
  import type { AlertProps, AlertType } from './types'
4
4
 
5
5
  const typeIconNameMap: Partial<Record<AlertType, string>> = {
@@ -22,9 +22,17 @@ const typeClasses: Record<AlertType, string> = {
22
22
  const props = withDefaults(defineProps<AlertProps>(), {
23
23
  type: 'default',
24
24
  icon: undefined,
25
+ title: undefined,
26
+ description: undefined,
25
27
  class: undefined,
26
28
  })
27
29
 
30
+ const slots = defineSlots<{
31
+ default?: () => any
32
+ title?: () => any
33
+ icon?: () => any
34
+ }>()
35
+
28
36
  const defaultIconName = computed(() => {
29
37
  // null explicitly hides the icon; any truthy value is an explicit icon.
30
38
  if (props.icon || props.icon === null) return undefined
@@ -37,25 +45,39 @@ const mergedClass = computed(() =>
37
45
  props.class,
38
46
  ),
39
47
  )
48
+
49
+ const hasTitle = computed(() => Boolean(slots.title || props.title))
50
+ const hasDescription = computed(() => Boolean(slots.default || props.description))
40
51
  </script>
41
52
 
42
53
  <template>
43
54
  <ShadcnAlert :class="mergedClass">
44
- <Icon
45
- v-if="typeof icon === 'string' && icon"
46
- :name="icon"
47
- />
48
- <component
49
- :is="icon"
50
- v-else-if="icon"
51
- class="size-4"
52
- />
53
- <Icon
54
- v-else-if="defaultIconName"
55
- :name="defaultIconName"
56
- />
57
- <div class="col-start-2">
58
- <slot />
59
- </div>
55
+ <slot name="icon">
56
+ <Icon
57
+ v-if="typeof icon === 'string' && icon"
58
+ :name="icon"
59
+ />
60
+ <component
61
+ :is="icon"
62
+ v-else-if="icon"
63
+ />
64
+ <Icon
65
+ v-else-if="defaultIconName"
66
+ :name="defaultIconName"
67
+ />
68
+ </slot>
69
+ <AlertTitle v-if="hasTitle">
70
+ <slot name="title">
71
+ {{ title }}
72
+ </slot>
73
+ </AlertTitle>
74
+ <AlertDescription
75
+ v-if="hasDescription"
76
+ class="text-current/80"
77
+ >
78
+ <slot>
79
+ {{ description }}
80
+ </slot>
81
+ </AlertDescription>
60
82
  </ShadcnAlert>
61
83
  </template>
@@ -6,5 +6,9 @@ export interface AlertProps {
6
6
  type?: AlertType
7
7
  /** Pass `null` to explicitly hide the icon; leave undefined to use the type's default. */
8
8
  icon?: string | Component | null
9
+ /** Bold heading line. Slot `#title` takes precedence. */
10
+ title?: string
11
+ /** Description text. Default slot takes precedence. */
12
+ description?: string
9
13
  class?: ClassValue
10
14
  }
@@ -1,13 +1,15 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import type { SurfaceColor } from '../Surface/types'
2
3
  import type { ButtonSize, ButtonVariant } from './types'
3
4
  import Icon from '../Icon/index.vue'
5
+ import Surface from '../Surface/index.vue'
4
6
  import Button from './index.vue'
5
7
 
6
8
  const variants = [
7
9
  'default',
10
+ 'secondary',
8
11
  'destructive',
9
12
  'outline',
10
- 'secondary',
11
13
  'ghost',
12
14
  'link',
13
15
  ] as const satisfies readonly ButtonVariant[]
@@ -21,6 +23,16 @@ const sizes = [
21
23
  'icon-lg',
22
24
  ] as const satisfies readonly ButtonSize[]
23
25
 
26
+ const surfaceColors = [
27
+ 'default',
28
+ 'primary',
29
+ 'success',
30
+ 'info',
31
+ 'help',
32
+ 'warn',
33
+ 'danger',
34
+ ] as const satisfies readonly SurfaceColor[]
35
+
24
36
  const meta = {
25
37
  title: 'UI/Button',
26
38
  component: Button,
@@ -70,9 +82,9 @@ export const Variants: Story = {
70
82
  code: `
71
83
  <template>
72
84
  <Button variant="default">default</Button>
85
+ <Button variant="secondary">secondary</Button>
73
86
  <Button variant="destructive">destructive</Button>
74
87
  <Button variant="outline">outline</Button>
75
- <Button variant="secondary">secondary</Button>
76
88
  <Button variant="ghost">ghost</Button>
77
89
  <Button variant="link">link</Button>
78
90
  </template>
@@ -265,3 +277,43 @@ export const LinkButtons: Story = {
265
277
  `,
266
278
  }),
267
279
  }
280
+
281
+ export const InheritedHoverColor: Story = {
282
+ parameters: {
283
+ ...noControls,
284
+ docs: {
285
+ source: {
286
+ code: `
287
+ <template>
288
+ <!-- outline / ghost / link inherit the parent's currentColor -->
289
+ <Surface variant="soft" color="success">
290
+ <Button variant="outline">Outline</Button>
291
+ <Button variant="ghost">Ghost</Button>
292
+ <Button variant="link">Link</Button>
293
+ </Surface>
294
+ </template>
295
+ `.trim(),
296
+ },
297
+ },
298
+ },
299
+ render: () => ({
300
+ components: { Button, Surface },
301
+ setup: () => ({ surfaceColors }),
302
+ template: `
303
+ <div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
304
+ <Surface
305
+ v-for="c in surfaceColors"
306
+ :key="c"
307
+ variant="soft"
308
+ :color="c"
309
+ class="p-3 flex items-center gap-2"
310
+ >
311
+ <span class="mr-auto text-sm font-medium capitalize">{{ c }}</span>
312
+ <Button variant="outline" size="sm">Outline</Button>
313
+ <Button variant="ghost" size="sm">Ghost</Button>
314
+ <Button variant="link" size="sm">Link</Button>
315
+ </Surface>
316
+ </div>
317
+ `,
318
+ }),
319
+ }
@@ -4,10 +4,28 @@ import WebLink from '@polymarbot/nuxt-layer-shadcn-ui/app/components/ui/WebLink/
4
4
  import type { ButtonProps } from './types'
5
5
 
6
6
  const props = defineProps<ButtonProps>()
7
- const isTransparentHover = computed(() => props.variant === 'outline' || props.variant === 'ghost')
7
+
8
+ const variantClasses: Partial<Record<NonNullable<ButtonVariant>, string>> = {
9
+ outline: `
10
+ bg-transparent
11
+ dark:bg-transparent
12
+ border-current/20
13
+ dark:border-current/20
14
+ hover:bg-current/10
15
+ dark:hover:bg-current/10
16
+ hover:text-current
17
+ `,
18
+ ghost: `
19
+ hover:bg-current/10
20
+ dark:hover:bg-current/10
21
+ hover:text-current
22
+ `,
23
+ link: 'text-current',
24
+ }
25
+
8
26
  const mergedClass = computed(() => cn(
9
27
  'cursor-pointer',
10
- isTransparentHover.value && 'hover:bg-accent/50',
28
+ props.variant && variantClasses[props.variant],
11
29
  props.rounded && 'rounded-full',
12
30
  props.class,
13
31
  ))
@@ -28,11 +28,11 @@ const meta = {
28
28
  showTime: false,
29
29
  disabled: false,
30
30
  readonly: false,
31
- placeholder: '',
31
+ placeholder: undefined,
32
32
  minDate: undefined,
33
33
  maxDate: undefined,
34
- valueFormat: '',
35
- autoApply: false,
34
+ valueFormat: undefined,
35
+ autoApply: true,
36
36
  class: '',
37
37
  },
38
38
  render: args => ({
@@ -43,7 +43,7 @@ const meta = {
43
43
  },
44
44
  template: `
45
45
  <div class="max-w-xs">
46
- <DatePicker v-model="value" v-bind="args" />
46
+ <DatePicker v-bind="args" v-model="value" />
47
47
  <div class="mt-2 text-sm text-muted-foreground">Value: {{ value }}</div>
48
48
  </div>
49
49
  `,
@@ -1,48 +1,53 @@
1
1
  import type { Meta, StoryObj } from '@storybook/vue3'
2
- import type { DateRangePickerValue } from './types'
3
2
  import DateRangePicker from './index.vue'
4
3
 
5
4
  const meta = {
6
5
  title: 'UI/DateRangePicker',
7
6
  component: DateRangePicker,
8
7
  argTypes: {
9
- modelValue: { control: 'object' },
8
+ start: { control: 'date' },
9
+ end: { control: 'date' },
10
+ minDate: { control: 'date' },
11
+ maxDate: { control: 'date' },
10
12
  showTime: { control: 'boolean' },
11
13
  disabled: { control: 'boolean' },
12
14
  readonly: { control: 'boolean' },
13
15
  startPlaceholder: { control: 'text' },
14
16
  endPlaceholder: { control: 'text' },
15
- minDate: { control: 'date' },
16
- maxDate: { control: 'date' },
17
17
  maxSpanDays: { control: 'number' },
18
18
  valueFormat: { control: 'text' },
19
19
  autoApply: { control: 'boolean' },
20
20
  class: { control: 'text' },
21
21
  },
22
22
  args: {
23
- modelValue: [ null, null ],
23
+ start: null,
24
+ end: null,
25
+ minDate: undefined,
26
+ maxDate: undefined,
24
27
  showTime: false,
25
28
  disabled: false,
26
29
  readonly: false,
27
- startPlaceholder: '',
28
- endPlaceholder: '',
29
- minDate: undefined,
30
- maxDate: undefined,
30
+ startPlaceholder: undefined,
31
+ endPlaceholder: undefined,
31
32
  maxSpanDays: undefined,
32
- valueFormat: '',
33
- autoApply: false,
33
+ valueFormat: undefined,
34
+ autoApply: true,
34
35
  class: '',
35
36
  },
36
37
  render: args => ({
37
38
  components: { DateRangePicker },
38
39
  setup () {
39
- const range = ref<DateRangePickerValue>([ null, null ])
40
- return { args, range }
40
+ const start = ref<Date | string | null>(args.start ?? null)
41
+ const end = ref<Date | string | null>(args.end ?? null)
42
+ return { args, start, end }
41
43
  },
42
44
  template: `
43
45
  <div class="max-w-lg">
44
- <DateRangePicker v-model="range" v-bind="args" />
45
- <div class="mt-2 text-sm text-muted-foreground">Value: {{ range }}</div>
46
+ <DateRangePicker v-bind="args" v-model:start="start" v-model:end="end" />
47
+ <div class="mt-2 text-sm text-muted-foreground">
48
+ <div>Start: {{ start }}</div>
49
+ <div>End: {{ end }}</div>
50
+ </div>
46
51
  </div>
47
52
  `,
48
53
  }),
@@ -60,20 +65,24 @@ export const WithTime: Story = {
60
65
  ...noControls,
61
66
  docs: {
62
67
  source: {
63
- code: '<DateRangePicker v-model="withTime" showTime />',
68
+ code: '<DateRangePicker v-model:start="start" v-model:end="end" showTime />',
64
69
  },
65
70
  },
66
71
  },
67
72
  render: () => ({
68
73
  components: { DateRangePicker },
69
74
  setup () {
70
- const withTime = ref<DateRangePickerValue>([ null, null ])
71
- return { withTime }
75
+ const start = ref<Date | string | null>(null)
76
+ const end = ref<Date | string | null>(null)
77
+ return { start, end }
72
78
  },
73
79
  template: `
74
80
  <div class="max-w-lg">
75
- <DateRangePicker v-model="withTime" showTime />
76
- <div class="mt-2 text-sm text-muted-foreground">Value: {{ withTime }}</div>
81
+ <DateRangePicker v-model:start="start" v-model:end="end" showTime />
82
+ <div class="mt-2 text-sm text-muted-foreground">
83
+ <div>Start: {{ start }}</div>
84
+ <div>End: {{ end }}</div>
85
+ </div>
77
86
  </div>
78
87
  `,
79
88
  }),
@@ -84,20 +93,24 @@ export const MaxSpanDays: Story = {
84
93
  ...noControls,
85
94
  docs: {
86
95
  source: {
87
- code: '<DateRangePicker v-model="maxSpan" :maxSpanDays="7" />',
96
+ code: '<DateRangePicker v-model:start="start" v-model:end="end" :maxSpanDays="7" />',
88
97
  },
89
98
  },
90
99
  },
91
100
  render: () => ({
92
101
  components: { DateRangePicker },
93
102
  setup () {
94
- const maxSpan = ref<DateRangePickerValue>([ null, null ])
95
- return { maxSpan }
103
+ const start = ref<Date | string | null>(null)
104
+ const end = ref<Date | string | null>(null)
105
+ return { start, end }
96
106
  },
97
107
  template: `
98
108
  <div class="max-w-lg">
99
- <DateRangePicker v-model="maxSpan" :maxSpanDays="7" />
100
- <div class="mt-2 text-sm text-muted-foreground">Value: {{ maxSpan }}</div>
109
+ <DateRangePicker v-model:start="start" v-model:end="end" :maxSpanDays="7" />
110
+ <div class="mt-2 text-sm text-muted-foreground">
111
+ <div>Start: {{ start }}</div>
112
+ <div>End: {{ end }}</div>
113
+ </div>
101
114
  </div>
102
115
  `,
103
116
  }),
@@ -108,23 +121,24 @@ export const Preselected: Story = {
108
121
  ...noControls,
109
122
  docs: {
110
123
  source: {
111
- code: '<DateRangePicker v-model="preselected" />',
124
+ code: '<DateRangePicker v-model:start="start" v-model:end="end" />',
112
125
  },
113
126
  },
114
127
  },
115
128
  render: () => ({
116
129
  components: { DateRangePicker },
117
130
  setup () {
118
- const preselected = ref<DateRangePickerValue>([
119
- new Date(2025, 5, 1),
120
- new Date(2025, 5, 15),
121
- ])
122
- return { preselected }
131
+ const start = ref<Date | string | null>(new Date(2025, 5, 1))
132
+ const end = ref<Date | string | null>(new Date(2025, 5, 15))
133
+ return { start, end }
123
134
  },
124
135
  template: `
125
136
  <div class="max-w-lg">
126
- <DateRangePicker v-model="preselected" />
127
- <div class="mt-2 text-sm text-muted-foreground">Value: {{ preselected }}</div>
137
+ <DateRangePicker v-model:start="start" v-model:end="end" />
138
+ <div class="mt-2 text-sm text-muted-foreground">
139
+ <div>Start: {{ start }}</div>
140
+ <div>End: {{ end }}</div>
141
+ </div>
128
142
  </div>
129
143
  `,
130
144
  }),
@@ -135,22 +149,20 @@ export const Disabled: Story = {
135
149
  ...noControls,
136
150
  docs: {
137
151
  source: {
138
- code: '<DateRangePicker v-model="range" disabled />',
152
+ code: '<DateRangePicker v-model:start="start" v-model:end="end" disabled />',
139
153
  },
140
154
  },
141
155
  },
142
156
  render: () => ({
143
157
  components: { DateRangePicker },
144
158
  setup () {
145
- const range = ref<DateRangePickerValue>([
146
- new Date(2025, 5, 1),
147
- new Date(2025, 5, 15),
148
- ])
149
- return { range }
159
+ const start = ref<Date | string | null>(new Date(2025, 5, 1))
160
+ const end = ref<Date | string | null>(new Date(2025, 5, 15))
161
+ return { start, end }
150
162
  },
151
163
  template: `
152
164
  <div class="max-w-lg">
153
- <DateRangePicker v-model="range" disabled />
165
+ <DateRangePicker v-model:start="start" v-model:end="end" disabled />
154
166
  </div>
155
167
  `,
156
168
  }),
@@ -161,22 +173,20 @@ export const Readonly: Story = {
161
173
  ...noControls,
162
174
  docs: {
163
175
  source: {
164
- code: '<DateRangePicker v-model="range" readonly />',
176
+ code: '<DateRangePicker v-model:start="start" v-model:end="end" readonly />',
165
177
  },
166
178
  },
167
179
  },
168
180
  render: () => ({
169
181
  components: { DateRangePicker },
170
182
  setup () {
171
- const range = ref<DateRangePickerValue>([
172
- new Date(2025, 5, 1),
173
- new Date(2025, 5, 15),
174
- ])
175
- return { range }
183
+ const start = ref<Date | string | null>(new Date(2025, 5, 1))
184
+ const end = ref<Date | string | null>(new Date(2025, 5, 15))
185
+ return { start, end }
176
186
  },
177
187
  template: `
178
188
  <div class="max-w-lg">
179
- <DateRangePicker v-model="range" readonly />
189
+ <DateRangePicker v-model:start="start" v-model:end="end" readonly />
180
190
  </div>
181
191
  `,
182
192
  }),
@@ -1,10 +1,11 @@
1
1
  <script setup lang="ts">
2
- import type { DateRangePickerProps, DateRangePickerValue } from './types'
2
+ import type { DateRangePickerProps } from './types'
3
3
 
4
4
  defineOptions({ inheritAttrs: false })
5
5
 
6
6
  const props = withDefaults(defineProps<DateRangePickerProps>(), {
7
- modelValue: () => [ null, null ],
7
+ start: null,
8
+ end: null,
8
9
  showTime: false,
9
10
  disabled: false,
10
11
  readonly: false,
@@ -19,81 +20,69 @@ const props = withDefaults(defineProps<DateRangePickerProps>(), {
19
20
  })
20
21
 
21
22
  const emit = defineEmits<{
22
- 'update:modelValue': [value: DateRangePickerValue]
23
+ 'update:start': [value: Date | string | null]
24
+ 'update:end': [value: Date | string | null]
23
25
  }>()
24
26
 
25
27
  const T = useTranslations('components.ui.DateRangePicker')
26
28
 
27
- const startDate = ref<Date | string | null>(props.modelValue?.[0] ?? null)
28
- const endDate = ref<Date | string | null>(props.modelValue?.[1] ?? null)
29
-
30
- watch(() => props.modelValue, val => {
31
- startDate.value = val?.[0] ?? null
32
- endDate.value = val?.[1] ?? null
29
+ const start = computed({
30
+ get: () => props.start,
31
+ set: value => emit('update:start', value),
33
32
  })
34
33
 
35
- function emitRange () {
36
- emit('update:modelValue', [ startDate.value, endDate.value ])
37
- }
38
-
39
- function handleStartUpdate (value: Date | string | null) {
40
- startDate.value = value
41
- emitRange()
42
- }
43
-
44
- function handleEndUpdate (value: Date | string | null) {
45
- // If time is disabled, set end time to end of day
46
- if (value instanceof Date && !props.showTime) {
47
- const adjusted = new Date(value)
48
- adjusted.setHours(23, 59, 59, 999)
49
- endDate.value = adjusted
50
- } else {
51
- endDate.value = value
52
- }
53
- emitRange()
54
- }
34
+ const end = computed({
35
+ get: () => props.end,
36
+ set: value => {
37
+ // When time is disabled, normalize end to end of day so range is inclusive
38
+ if (value instanceof Date && !props.showTime) {
39
+ const adjusted = new Date(value)
40
+ adjusted.setHours(23, 59, 59, 999)
41
+ emit('update:end', adjusted)
42
+ } else {
43
+ emit('update:end', value)
44
+ }
45
+ },
46
+ })
55
47
 
56
- // Helper functions for date constraints
57
48
  function addDays (date: Date, days: number): Date {
58
49
  const result = new Date(date)
59
50
  result.setDate(result.getDate() + days)
60
51
  return result
61
52
  }
62
53
 
63
- function toDate (value: Date | string | null): Date | undefined {
54
+ function toDate (value: Date | string | null | undefined): Date | undefined {
64
55
  if (!value) return undefined
65
56
  return value instanceof Date ? value : new Date(value)
66
57
  }
67
58
 
68
- // Start picker constraints
69
59
  const startMinDate = computed(() => {
70
60
  const min = props.minDate
71
- const spanLimit = props.maxSpanDays && endDate.value
72
- ? addDays(toDate(endDate.value)!, -(props.maxSpanDays - 1))
61
+ const spanLimit = props.maxSpanDays && props.end
62
+ ? addDays(toDate(props.end)!, -(props.maxSpanDays - 1))
73
63
  : undefined
74
64
  if (min && spanLimit) return new Date(Math.max(+new Date(min), +spanLimit))
75
65
  return min ?? spanLimit
76
66
  })
77
67
 
78
68
  const startMaxDate = computed(() => {
79
- const end = toDate(endDate.value)
69
+ const endDate = toDate(props.end)
80
70
  const max = props.maxDate
81
- if (end && max) return new Date(Math.min(+end, +new Date(max)))
82
- return end ?? max
71
+ if (endDate && max) return new Date(Math.min(+endDate, +new Date(max)))
72
+ return endDate ?? max
83
73
  })
84
74
 
85
- // End picker constraints
86
75
  const endMinDate = computed(() => {
87
- const start = toDate(startDate.value)
76
+ const startDate = toDate(props.start)
88
77
  const min = props.minDate
89
- if (start && min) return new Date(Math.max(+start, +new Date(min)))
90
- return start ?? min
78
+ if (startDate && min) return new Date(Math.max(+startDate, +new Date(min)))
79
+ return startDate ?? min
91
80
  })
92
81
 
93
82
  const endMaxDate = computed(() => {
94
83
  const max = props.maxDate
95
- const spanLimit = props.maxSpanDays && startDate.value
96
- ? addDays(toDate(startDate.value)!, props.maxSpanDays - 1)
84
+ const spanLimit = props.maxSpanDays && props.start
85
+ ? addDays(toDate(props.start)!, props.maxSpanDays - 1)
97
86
  : undefined
98
87
  if (max && spanLimit) return new Date(Math.min(+new Date(max), +spanLimit))
99
88
  return max ?? spanLimit
@@ -103,7 +92,7 @@ const endMaxDate = computed(() => {
103
92
  <template>
104
93
  <div :class="cn('gap-2 flex items-center', props.class)">
105
94
  <DatePicker
106
- :modelValue="startDate"
95
+ v-model="start"
107
96
  :showTime="showTime"
108
97
  :disabled="disabled"
109
98
  :readonly="readonly"
@@ -113,13 +102,12 @@ const endMaxDate = computed(() => {
113
102
  :valueFormat="valueFormat"
114
103
  :autoApply="autoApply"
115
104
  v-bind="$attrs"
116
- @update:modelValue="handleStartUpdate"
117
105
  />
118
106
  <span class="text-muted-foreground shrink-0">
119
107
  {{ T('to') }}
120
108
  </span>
121
109
  <DatePicker
122
- :modelValue="endDate"
110
+ v-model="end"
123
111
  :showTime="showTime"
124
112
  :disabled="disabled"
125
113
  :readonly="readonly"
@@ -129,7 +117,6 @@ const endMaxDate = computed(() => {
129
117
  :valueFormat="valueFormat"
130
118
  :autoApply="autoApply"
131
119
  v-bind="$attrs"
132
- @update:modelValue="handleEndUpdate"
133
120
  />
134
121
  </div>
135
122
  </template>
@@ -1,12 +1,12 @@
1
1
  import type { DatePickerTimeConfig } from '../DatePicker/types'
2
2
 
3
- export type DateRangePickerValue = [
4
- start: Date | string | null,
5
- end: Date | string | null,
6
- ]
7
-
8
3
  export interface DateRangePickerProps {
9
- modelValue?: DateRangePickerValue
4
+ start?: Date | string | null
5
+ end?: Date | string | null
6
+ /** Minimum selectable date */
7
+ minDate?: Date | string
8
+ /** Maximum selectable date */
9
+ maxDate?: Date | string
10
10
  /** Enable time selection, or pass DatePickerTimeConfig for fine-grained control */
11
11
  showTime?: boolean | DatePickerTimeConfig
12
12
  /** Disable the date range picker */
@@ -17,10 +17,6 @@ export interface DateRangePickerProps {
17
17
  startPlaceholder?: string
18
18
  /** Placeholder for end date input */
19
19
  endPlaceholder?: string
20
- /** Minimum selectable date */
21
- minDate?: Date | string
22
- /** Maximum selectable date */
23
- maxDate?: Date | string
24
20
  /** Maximum span in days between start and end date */
25
21
  maxSpanDays?: number
26
22
  /** v-model output format (e.g. 'yyyy-MM-dd', 'timestamp', 'iso') */
@@ -57,7 +57,7 @@ const meta = {
57
57
  template: `
58
58
  <div>
59
59
  <Button @click="visible = true">Open Drawer</Button>
60
- <Drawer v-model:visible="visible" v-bind="args">
60
+ <Drawer v-bind="args" v-model:visible="visible">
61
61
  <p>This is the drawer content.</p>
62
62
  <Input class="mt-4" placeholder="Try interacting with this input" />
63
63
  </Drawer>
@@ -22,7 +22,7 @@ const meta = {
22
22
  },
23
23
  template: `
24
24
  <div class="max-w-xs">
25
- <InputCurrency v-model="value" v-bind="args" />
25
+ <InputCurrency v-bind="args" v-model="value" />
26
26
  <div class="mt-2 text-sm text-muted-foreground">Value: {{ value }}</div>
27
27
  </div>
28
28
  `,
@@ -31,7 +31,7 @@ const meta = {
31
31
  },
32
32
  template: `
33
33
  <div class="max-w-xs">
34
- <InputNumber v-model="value" v-bind="args" />
34
+ <InputNumber v-bind="args" v-model="value" />
35
35
  <div class="mt-2 text-sm text-muted-foreground">Value: {{ value }}</div>
36
36
  </div>
37
37
  `,
@@ -23,7 +23,7 @@ const meta = {
23
23
  },
24
24
  template: `
25
25
  <div class="space-y-4">
26
- <InputOtp v-model="otp" v-bind="args" />
26
+ <InputOtp v-bind="args" v-model="otp" />
27
27
  <div class="text-sm text-muted-foreground">Value: {{ otp }}</div>
28
28
  </div>
29
29
  `,
@@ -14,7 +14,7 @@ const meta = {
14
14
  },
15
15
  template: `
16
16
  <div class="max-w-xs space-y-4">
17
- <InputPercent v-model="percent" v-bind="args" />
17
+ <InputPercent v-bind="args" v-model="percent" />
18
18
  <div class="text-sm text-muted-foreground">Value: {{ percent }}</div>
19
19
  </div>
20
20
  `,
@@ -28,7 +28,7 @@ const meta = {
28
28
  },
29
29
  template: `
30
30
  <div class="max-w-md">
31
- <InputRange v-model:start="start" v-model:end="end" v-bind="args" />
31
+ <InputRange v-bind="args" v-model:start="start" v-model:end="end" />
32
32
  <div class="mt-2 text-sm text-muted-foreground">Start: {{ start }}, End: {{ end }}</div>
33
33
  </div>
34
34
  `,
@@ -7,7 +7,7 @@ const props = withDefaults(defineProps<InputRangeProps>(), {
7
7
  start: undefined,
8
8
  end: undefined,
9
9
  min: 0,
10
- max: 100,
10
+ max: undefined,
11
11
  })
12
12
 
13
13
  const emit = defineEmits<{
@@ -27,7 +27,7 @@ const end = computed({
27
27
  </script>
28
28
 
29
29
  <template>
30
- <div class="flex items-center gap-2">
30
+ <div class="gap-2 flex items-center">
31
31
  <InputNumber
32
32
  v-model="start"
33
33
  v-bind="$attrs"
@@ -51,7 +51,7 @@ const meta = {
51
51
  template: `
52
52
  <div>
53
53
  <Button @click="visible = true">Open Modal</Button>
54
- <Modal v-model:visible="visible" v-bind="args">
54
+ <Modal v-bind="args" v-model:visible="visible">
55
55
  <p>This is the modal content.</p>
56
56
  <Input class="mt-4" placeholder="Try interacting with this input" />
57
57
  </Modal>
@@ -57,7 +57,7 @@ const meta = {
57
57
  },
58
58
  template: `
59
59
  <div class="max-w-sm">
60
- <Select v-model="value" :options="frameworks" v-bind="args" />
60
+ <Select v-bind="args" v-model="value" :options="frameworks" />
61
61
  <div class="mt-2 text-sm text-muted-foreground">Selected: {{ value ?? 'none' }}</div>
62
62
  </div>
63
63
  `,
@@ -27,7 +27,7 @@ const meta = {
27
27
  },
28
28
  template: `
29
29
  <div class="max-w-sm">
30
- <Slider v-model="value" v-bind="args" />
30
+ <Slider v-bind="args" v-model="value" />
31
31
  <div class="mt-2 text-sm text-muted-foreground">Value: {{ value }}</div>
32
32
  </div>
33
33
  `,
@@ -19,7 +19,7 @@ const meta = {
19
19
  },
20
20
  template: `
21
21
  <div class="flex items-center gap-4">
22
- <Switch v-model="on" v-bind="args" />
22
+ <Switch v-bind="args" v-model="on" />
23
23
  <div class="text-sm text-muted-foreground">Value: {{ on }}</div>
24
24
  </div>
25
25
  `,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymarbot/nuxt-layer-shadcn-ui",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Nuxt layer providing shadcn-vue based UI components",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -42,5 +42,5 @@
42
42
  "vue-i18n": "^11",
43
43
  "vue-router": "^4 || ^5"
44
44
  },
45
- "gitHead": "79d1ad5f798390469ac3cdfa7aed7ddc530a6c1e"
45
+ "gitHead": "750dccbf085f045771b445f9ac7fe0d722cd1a4c"
46
46
  }