@polymarbot/nuxt-layer-shadcn-ui 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  }
@@ -6,7 +6,7 @@ defineProps<BadgeProps>()
6
6
  </script>
7
7
 
8
8
  <template>
9
- <ShadcnBadge>
9
+ <ShadcnBadge :variant="variant">
10
10
  <slot />
11
11
  </ShadcnBadge>
12
12
  </template>
@@ -1,5 +1,7 @@
1
1
  import type { BadgeVariants } from '../../shadcn/badge'
2
2
 
3
- export type BadgeVariant = NonNullable<BadgeVariants['variant']>
3
+ export type BadgeVariant = BadgeVariants['variant']
4
4
 
5
- export interface BadgeProps extends /* @vue-ignore */ BadgeVariants {}
5
+ export interface BadgeProps {
6
+ variant?: BadgeVariant
7
+ }
@@ -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,9 +4,31 @@ 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 mergedClass = computed(() => cn('cursor-pointer', props.rounded && `
8
- rounded-full
9
- `, props.class))
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
+
26
+ const mergedClass = computed(() => cn(
27
+ 'cursor-pointer',
28
+ props.variant && variantClasses[props.variant],
29
+ props.rounded && 'rounded-full',
30
+ props.class,
31
+ ))
10
32
 
11
33
  const isLink = computed(() => !!props.href || !!props.to)
12
34
  const hasIcon = computed(() => !!$slots.icon || !!props.icon)
@@ -19,6 +41,8 @@ const $slots = defineSlots<{
19
41
 
20
42
  <template>
21
43
  <ShadcnButton
44
+ :variant="variant"
45
+ :size="size"
22
46
  :class="mergedClass"
23
47
  :asChild="isLink"
24
48
  :type="isLink ? undefined : 'button'"
@@ -1,10 +1,12 @@
1
1
  import type { ButtonVariants } from '../../shadcn/button'
2
2
  import type { RouteLocationRaw } from 'vue-router'
3
3
 
4
- export type ButtonVariant = NonNullable<ButtonVariants['variant']>
5
- export type ButtonSize = NonNullable<ButtonVariants['size']>
4
+ export type ButtonVariant = ButtonVariants['variant']
5
+ export type ButtonSize = ButtonVariants['size']
6
6
 
7
- export interface ButtonProps extends /* @vue-ignore */ ButtonVariants {
7
+ export interface ButtonProps {
8
+ variant?: ButtonVariant
9
+ size?: ButtonSize
8
10
  loading?: boolean
9
11
  disabled?: boolean
10
12
  rounded?: boolean
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymarbot/nuxt-layer-shadcn-ui",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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": "1cee179e4b0678c75dd8293d0274ec2afb8d9143"
45
+ "gitHead": "fc48f5b21d76aa2ff0badd6ac6d9ccc5be906f26"
46
46
  }