@nexxtmove/ui 0.1.22 → 0.1.23

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 (66) hide show
  1. package/dist/index.d.ts +75 -8
  2. package/dist/index.js +2408 -252
  3. package/dist/nuxt.d.ts +1 -0
  4. package/dist/nuxt.js +49 -0
  5. package/package.json +23 -13
  6. package/src/assets/images/Template_aanvragen.jpg +0 -0
  7. package/src/assets/svg/type_post.svg +1 -0
  8. package/src/assets/svg/type_reel.svg +54 -0
  9. package/src/assets/svg/type_story.svg +46 -0
  10. package/src/assets/svg/type_tiktok.svg +1 -0
  11. package/src/assets/video/Template_aanvragen.mp4 +0 -0
  12. package/src/components/AnimatedNumber/AnimatedNumber.stories.ts +15 -0
  13. package/src/components/AnimatedNumber/AnimatedNumber.test.ts +56 -0
  14. package/src/components/AnimatedNumber/AnimatedNumber.vue +61 -0
  15. package/src/components/Button/Button.stories.ts +212 -0
  16. package/src/components/Button/Button.test.ts +318 -0
  17. package/src/components/Button/Button.vue +67 -0
  18. package/src/components/Calendar/Calendar.stories.ts +91 -0
  19. package/src/components/Calendar/Calendar.test.ts +269 -0
  20. package/src/components/Calendar/Calendar.vue +221 -0
  21. package/src/components/Calendar/_CalendarDayView.test.ts +145 -0
  22. package/src/components/Calendar/_CalendarDayView.vue +156 -0
  23. package/src/components/Calendar/_CalendarHeader.test.ts +86 -0
  24. package/src/components/Calendar/_CalendarHeader.vue +123 -0
  25. package/src/components/Calendar/_CalendarMonthView.test.ts +68 -0
  26. package/src/components/Calendar/_CalendarMonthView.vue +70 -0
  27. package/src/components/Calendar/_CalendarYearView.vue +77 -0
  28. package/src/components/Calendar/calendar.types.ts +10 -0
  29. package/src/components/Chip/Chip.stories.ts +42 -0
  30. package/src/components/Chip/Chip.test.ts +51 -0
  31. package/src/components/Chip/Chip.vue +37 -0
  32. package/src/components/DatePicker/DatePicker.stories.ts +149 -0
  33. package/src/components/DatePicker/DatePicker.test.ts +191 -0
  34. package/src/components/DatePicker/DatePicker.vue +142 -0
  35. package/src/components/Header/Header.stories.ts +48 -0
  36. package/src/components/Header/Header.test.ts +169 -0
  37. package/src/components/Header/Header.vue +42 -0
  38. package/src/components/Icon/Icon.stories.ts +50 -0
  39. package/src/components/Icon/Icon.test.ts +73 -0
  40. package/src/components/Icon/Icon.vue +20 -0
  41. package/src/components/InfoBlock/InfoBlock.stories.ts +90 -0
  42. package/src/components/InfoBlock/InfoBlock.test.ts +101 -0
  43. package/src/components/InfoBlock/InfoBlock.vue +70 -0
  44. package/src/components/ProgressBar/ProgressBar.stories.ts +30 -0
  45. package/src/components/ProgressBar/ProgressBar.test.ts +314 -0
  46. package/src/components/ProgressBar/ProgressBar.vue +102 -0
  47. package/src/components/SocialIcons/SocialIcons.stories.ts +34 -0
  48. package/src/components/SocialIcons/SocialIcons.test.ts +58 -0
  49. package/src/components/SocialIcons/SocialIcons.vue +58 -0
  50. package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
  51. package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +131 -0
  52. package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue +55 -0
  53. package/src/components/SocialMediaTemplate/SocialMediaTemplate.stories.ts +71 -0
  54. package/src/components/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
  55. package/src/components/SocialMediaTemplate/SocialMediaTemplate.vue +130 -0
  56. package/src/components/SocialMediaType/SocialMediaType.stories.ts +43 -0
  57. package/src/components/SocialMediaType/SocialMediaType.test.ts +126 -0
  58. package/src/components/SocialMediaType/SocialMediaType.vue +117 -0
  59. package/src/components/StepperHeader/StepperHeader.stories.ts +47 -0
  60. package/src/components/StepperHeader/StepperHeader.test.ts +244 -0
  61. package/src/components/StepperHeader/StepperHeader.vue +37 -0
  62. package/src/components.json +16 -0
  63. package/src/env.d.ts +23 -0
  64. package/src/index.css +2 -0
  65. package/src/index.ts +15 -0
  66. package/src/nuxt.ts +50 -0
@@ -0,0 +1,37 @@
1
+ <script lang="ts" setup>
2
+ import { computed } from 'vue'
3
+
4
+ interface NexxtChipProps {
5
+ variant?: 'default' | 'warning' | 'success' | 'danger'
6
+ }
7
+
8
+ defineOptions({
9
+ name: 'NexxtChip',
10
+ })
11
+
12
+ const { variant = 'default' } = defineProps<NexxtChipProps>()
13
+
14
+ const backgroundColor = computed(() => {
15
+ switch (variant) {
16
+ case 'warning':
17
+ return 'bg-orange-500'
18
+ case 'success':
19
+ return 'bg-green-500'
20
+ case 'danger':
21
+ return 'bg-brick-500'
22
+ default:
23
+ return 'bg-cornflower-blue-600'
24
+ }
25
+ })
26
+ </script>
27
+
28
+ <template>
29
+ <span
30
+ class="inline-flex h-5 flex-col items-center justify-center gap-2.5 rounded-[200px] px-4"
31
+ :class="backgroundColor"
32
+ >
33
+ <span class="justify-start text-center extra-small-semibold text-white">
34
+ <slot>Chip</slot>
35
+ </span>
36
+ </span>
37
+ </template>
@@ -0,0 +1,149 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import { ref } from 'vue'
3
+ import DatePicker from './DatePicker.vue'
4
+
5
+ export default {
6
+ title: 'Components/Molecules/DatePicker',
7
+ component: DatePicker,
8
+ decorators: [
9
+ () => ({ template: '<div style="min-height: 380px; padding: 16px;"><story /></div>' }),
10
+ ],
11
+ argTypes: {
12
+ anchor: {
13
+ control: 'select',
14
+ options: ['bottom-right', 'bottom-left', 'top-right', 'top-left'],
15
+ },
16
+ monthYearPicker: { control: 'boolean' },
17
+ markedDates: { control: 'object' },
18
+ disabledDates: { control: 'object' },
19
+ minDate: { control: 'date' },
20
+ maxDate: { control: 'date' },
21
+ locale: { control: false },
22
+ },
23
+ parameters: {
24
+ design: {
25
+ type: 'figma',
26
+ url: 'https://www.figma.com/design/CdbFJ7qUga6mtjagcPDvYK/Design-System?node-id=713-355&t=CbHvOQfEMIr7M5mO-4',
27
+ },
28
+ },
29
+ } satisfies Meta<typeof DatePicker>
30
+
31
+ type Story = StoryObj<typeof DatePicker>
32
+
33
+ export const Default: Story = {
34
+ args: {
35
+ placeholder: 'Selecteer datum',
36
+ },
37
+ }
38
+
39
+ export const AnchorBottomRight: Story = {
40
+ render: (args) => ({
41
+ components: { DatePicker },
42
+ setup() {
43
+ return { args }
44
+ },
45
+ template: `
46
+ <div style="min-height: 380px; display: flex; justify-content: flex-end; align-items: flex-start; padding: 16px;">
47
+ <DatePicker v-bind="args" />
48
+ </div>
49
+ `,
50
+ }),
51
+ args: {
52
+ placeholder: 'Selecteer datum',
53
+ anchor: 'bottom-right',
54
+ },
55
+ }
56
+
57
+ export const AnchorTopLeft: Story = {
58
+ render: (args) => ({
59
+ components: { DatePicker },
60
+ setup() {
61
+ return { args }
62
+ },
63
+ template: `
64
+ <div style="min-height: 380px; display: flex; align-items: flex-end; padding: 16px;">
65
+ <DatePicker v-bind="args" />
66
+ </div>
67
+ `,
68
+ }),
69
+ args: {
70
+ placeholder: 'Selecteer datum',
71
+ anchor: 'top-left',
72
+ },
73
+ }
74
+
75
+ export const AnchorTopRight: Story = {
76
+ render: (args) => ({
77
+ components: { DatePicker },
78
+ setup() {
79
+ return { args }
80
+ },
81
+ template: `
82
+ <div style="min-height: 380px; display: flex; justify-content: flex-end; align-items: flex-end; padding: 16px;">
83
+ <DatePicker v-bind="args" />
84
+ </div>
85
+ `,
86
+ }),
87
+ args: {
88
+ placeholder: 'Selecteer datum',
89
+ anchor: 'top-right',
90
+ },
91
+ }
92
+
93
+ export const WithoutAutoClose: Story = {
94
+ args: {
95
+ placeholder: 'Selecteer datum',
96
+ autoClose: false,
97
+ },
98
+ }
99
+
100
+ export const CustomTrigger: Story = {
101
+ render: (args) => ({
102
+ components: { DatePicker },
103
+ setup() {
104
+ const selected = ref(args.modelValue)
105
+ return { args, selected }
106
+ },
107
+ template: `
108
+ <div style="min-height: 380px; padding: 16px;">
109
+ <p style="font-size: 13px; color: #555; margin-bottom: 12px;">Selected: {{ selected ? selected.toLocaleDateString() : 'None' }}</p>
110
+ <DatePicker v-bind="args" v-model="selected">
111
+ <template #trigger="{ toggle, isOpen }">
112
+ <button
113
+ type="button"
114
+ @click="toggle"
115
+ class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors shadow-sm font-medium"
116
+ >
117
+ {{ isOpen ? 'Sluiten' : 'Kies een datum uit de lijst' }}
118
+ </button>
119
+ </template>
120
+ </DatePicker>
121
+ </div>
122
+ `,
123
+ }),
124
+ args: {
125
+ modelValue: null,
126
+ },
127
+ }
128
+
129
+ export const Playground: Story = {
130
+ render: (args) => ({
131
+ components: { DatePicker },
132
+ setup() {
133
+ const selected = ref<Date | null>(null)
134
+ return { args, selected }
135
+ },
136
+ template: `
137
+ <div style="min-height: 380px; padding: 16px;">
138
+ <DatePicker v-bind="args" v-model="selected" />
139
+ <p v-if="selected" style="margin-top: 12px; font-size: 13px; color: #555;">
140
+ Geselecteerd: {{ selected.toLocaleDateString('nl-NL') }}
141
+ </p>
142
+ </div>
143
+ `,
144
+ }),
145
+ args: {
146
+ placeholder: 'Selecteer datum',
147
+ monthYearPicker: true,
148
+ },
149
+ }
@@ -0,0 +1,191 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { mount, type VueWrapper } from '@vue/test-utils'
3
+ import { nextTick } from 'vue'
4
+ import DatePicker from './DatePicker.vue'
5
+
6
+ const YEAR = 2026
7
+ const MONTH = 2 // March (0-indexed)
8
+ const TODAY = new Date(YEAR, MONTH, 20)
9
+
10
+ const mountDatePicker = (props = {}) => mount(DatePicker, { props, attachTo: document.body })
11
+
12
+ const openPicker = async (wrapper: VueWrapper) => {
13
+ await wrapper.find('button').trigger('click')
14
+ await nextTick()
15
+ }
16
+
17
+ describe('DatePicker', () => {
18
+ let wrapper: VueWrapper
19
+
20
+ afterEach(() => {
21
+ wrapper?.unmount()
22
+ })
23
+
24
+ // ── Trigger button ───────────────────────────────────────────────────────
25
+
26
+ it('renders trigger button with placeholder', () => {
27
+ wrapper = mountDatePicker({ placeholder: 'Kies een datum' })
28
+ expect(wrapper.text()).toContain('Kies een datum')
29
+ })
30
+
31
+ it('uses default placeholder when none provided', () => {
32
+ wrapper = mountDatePicker()
33
+ expect(wrapper.text()).toContain('Selecteer datum')
34
+ })
35
+
36
+ // ── Open / close ─────────────────────────────────────────────────────────
37
+
38
+ it('calendar is closed by default', () => {
39
+ wrapper = mountDatePicker()
40
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(false)
41
+ })
42
+
43
+ it('opens when trigger button is clicked', async () => {
44
+ wrapper = mountDatePicker()
45
+ await openPicker(wrapper)
46
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(true)
47
+ })
48
+
49
+ it('closes when trigger button is clicked again', async () => {
50
+ wrapper = mountDatePicker()
51
+ await openPicker(wrapper)
52
+ await wrapper.find('button').trigger('click')
53
+ await nextTick()
54
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(false)
55
+ })
56
+
57
+ it('closes when clicking outside', async () => {
58
+ wrapper = mountDatePicker()
59
+ await openPicker(wrapper)
60
+ document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
61
+ await nextTick()
62
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(false)
63
+ })
64
+
65
+ it('closes on Escape key', async () => {
66
+ wrapper = mountDatePicker()
67
+ await openPicker(wrapper)
68
+ await wrapper.trigger('keydown', { key: 'Escape' })
69
+ await nextTick()
70
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(false)
71
+ })
72
+
73
+ // ── Day selection + autoClose ─────────────────────────────────────────────
74
+
75
+ it('closes after selecting a day (autoClose default)', async () => {
76
+ wrapper = mountDatePicker({ modelValue: TODAY })
77
+ await openPicker(wrapper)
78
+
79
+ const dayButtons = wrapper
80
+ .findAll('button[type="button"]')
81
+ .filter((b) => /^\d{1,2}$/.test(b.text().trim()))
82
+ await dayButtons[10]!.trigger('click')
83
+ await nextTick()
84
+
85
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(false)
86
+ })
87
+
88
+ it('stays open after selecting a day if autoClose is false', async () => {
89
+ wrapper = mountDatePicker({ modelValue: TODAY, autoClose: false })
90
+ await openPicker(wrapper)
91
+
92
+ const dayButtons = wrapper
93
+ .findAll('button[type="button"]')
94
+ .filter((b) => /^\d{1,2}$/.test(b.text().trim()))
95
+ await dayButtons[10]!.trigger('click')
96
+ await nextTick()
97
+
98
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(true)
99
+ })
100
+
101
+ it('emits update:modelValue with the clicked date', async () => {
102
+ wrapper = mountDatePicker({ modelValue: TODAY })
103
+ await openPicker(wrapper)
104
+
105
+ const dayButtons = wrapper
106
+ .findAll('button[type="button"]')
107
+ .filter((b) => /^\d{1,2}$/.test(b.text().trim()))
108
+ await dayButtons[10]!.trigger('click')
109
+ await nextTick()
110
+
111
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
112
+ expect(wrapper.emitted('update:modelValue')![0]![0]).toBeInstanceOf(Date)
113
+ })
114
+
115
+ // ── Resets to days view on reopen ─────────────────────────────────────────
116
+
117
+ it('resets to days view when closed and reopened', async () => {
118
+ wrapper = mountDatePicker({ modelValue: TODAY, monthYearPicker: true })
119
+ await openPicker(wrapper)
120
+
121
+ // open month picker
122
+ const header = wrapper.find('[class*="cornflower-blue-600"]')
123
+ await header.find('button').trigger('click')
124
+ await nextTick()
125
+ expect(wrapper.find('[class*="grid-cols-3"]').exists()).toBe(true)
126
+
127
+ // close and reopen (v-if remounts Calendar with fresh pickerView = 'days')
128
+ await wrapper.find('button').trigger('click')
129
+ await nextTick()
130
+ await openPicker(wrapper)
131
+ expect(wrapper.find('[class*="grid-cols-7"]').exists()).toBe(true)
132
+ expect(wrapper.find('[class*="grid-cols-3"]').exists()).toBe(false)
133
+ })
134
+
135
+ // ── Custom trigger slot ───────────────────────────────────────────────────
136
+
137
+ it('renders custom trigger through slot', () => {
138
+ wrapper = mount(DatePicker, {
139
+ slots: {
140
+ trigger: `
141
+ <template #trigger="{ toggle }">
142
+ <button class="custom-trigger" @click="toggle">Custom Button</button>
143
+ </template>
144
+ `,
145
+ },
146
+ })
147
+
148
+ expect(wrapper.find('.custom-trigger').exists()).toBe(true)
149
+ expect(wrapper.text()).toContain('Custom Button')
150
+ })
151
+
152
+ it('toggles picker via custom trigger toggle function', async () => {
153
+ wrapper = mount(DatePicker, {
154
+ slots: {
155
+ trigger: `
156
+ <template #trigger="{ toggle }">
157
+ <button class="custom-trigger" @click="toggle">Toggle</button>
158
+ </template>
159
+ `,
160
+ },
161
+ })
162
+
163
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(false)
164
+
165
+ await wrapper.find('.custom-trigger').trigger('click')
166
+ await nextTick()
167
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(true)
168
+
169
+ await wrapper.find('.custom-trigger').trigger('click')
170
+ await nextTick()
171
+ expect(wrapper.find('[class*="rounded-xl"]').exists()).toBe(false)
172
+ })
173
+
174
+ it('provides isOpen state to custom trigger', async () => {
175
+ wrapper = mount(DatePicker, {
176
+ slots: {
177
+ trigger: `
178
+ <template #trigger="{ isOpen, toggle }">
179
+ <button class="custom-trigger" @click="toggle">{{ isOpen ? 'OPEN' : 'CLOSED' }}</button>
180
+ </template>
181
+ `,
182
+ },
183
+ })
184
+
185
+ expect(wrapper.find('.custom-trigger').text()).toBe('CLOSED')
186
+
187
+ await wrapper.find('.custom-trigger').trigger('click')
188
+ await nextTick()
189
+ expect(wrapper.find('.custom-trigger').text()).toBe('OPEN')
190
+ })
191
+ })
@@ -0,0 +1,142 @@
1
+ <script lang="ts" setup>
2
+ import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
3
+ import NexxtButton from '../Button/Button.vue'
4
+ import NexxtCalendar from '../Calendar/Calendar.vue'
5
+ import type { NexxtCalendarProps } from '../Calendar/calendar.types'
6
+
7
+ export type CalendarAnchor = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
8
+
9
+ interface NexxtDatePickerProps extends NexxtCalendarProps {
10
+ placeholder?: string
11
+ anchor?: CalendarAnchor
12
+ autoClose?: boolean
13
+ }
14
+
15
+ defineOptions({ name: 'NexxtDatePicker' })
16
+
17
+ const model = defineModel<Date | null>({ default: null })
18
+
19
+ const props = withDefaults(defineProps<NexxtDatePickerProps>(), {
20
+ placeholder: 'Selecteer datum',
21
+ anchor: 'bottom-left',
22
+ autoClose: true,
23
+ })
24
+
25
+ const PICKER_ONLY_KEYS = new Set(['placeholder', 'anchor', 'autoClose'])
26
+
27
+ const calendarProps = computed(
28
+ (): NexxtCalendarProps =>
29
+ Object.fromEntries(
30
+ Object.entries(props).filter(([key]) => !PICKER_ONLY_KEYS.has(key)),
31
+ ) as NexxtCalendarProps,
32
+ )
33
+
34
+ // ── State ─────────────────────────────────────────────────────────────────────
35
+
36
+ const isOpen = ref(false)
37
+ const wrapperRef = ref<HTMLElement | null>(null)
38
+ const triggerRef = ref<HTMLElement | null>(null)
39
+ const popoverRef = ref<HTMLElement | null>(null)
40
+
41
+ // ── Watchers ──────────────────────────────────────────────────────────────────
42
+
43
+ watch(isOpen, async (val) => {
44
+ if (!val) {
45
+ await nextTick()
46
+ const focusable = triggerRef.value?.querySelector<HTMLElement>(
47
+ 'button, input, [tabindex]:not([tabindex="-1"])',
48
+ )
49
+ focusable?.focus()
50
+ } else {
51
+ await nextTick()
52
+ popoverRef.value?.focus()
53
+ }
54
+ })
55
+
56
+ // ── Handlers ──────────────────────────────────────────────────────────────────
57
+
58
+ const handleSelect = () => {
59
+ if (props.autoClose) {
60
+ isOpen.value = false
61
+ }
62
+ }
63
+
64
+ const handleKeydown = (event: KeyboardEvent) => {
65
+ if (!isOpen.value) return
66
+ if (event.key === 'Escape') {
67
+ isOpen.value = false
68
+ }
69
+ }
70
+
71
+ const handleClickOutside = (event: MouseEvent) => {
72
+ if (wrapperRef.value && !wrapperRef.value.contains(event.target as Node)) {
73
+ isOpen.value = false
74
+ }
75
+ }
76
+
77
+ onMounted(() => document.addEventListener('mousedown', handleClickOutside))
78
+ onUnmounted(() => document.removeEventListener('mousedown', handleClickOutside))
79
+
80
+ // ── Popover placement ─────────────────────────────────────────────────────────
81
+
82
+ const popoverClasses = computed(() => {
83
+ const map: Record<CalendarAnchor, string> = {
84
+ 'bottom-right': 'top-full right-0 mt-2 origin-top-right',
85
+ 'bottom-left': 'top-full left-0 mt-2 origin-top-left',
86
+ 'top-right': 'bottom-full right-0 mb-2 origin-bottom-right',
87
+ 'top-left': 'bottom-full left-0 mb-2 origin-bottom-left',
88
+ }
89
+ return map[props.anchor]
90
+ })
91
+
92
+ const transitionClasses = computed(() => {
93
+ const isBottom = props.anchor.startsWith('bottom')
94
+
95
+ const origins: Record<CalendarAnchor, string> = {
96
+ 'bottom-left': 'origin-top-left',
97
+ 'bottom-right': 'origin-top-right',
98
+ 'top-left': 'origin-bottom-left',
99
+ 'top-right': 'origin-bottom-right',
100
+ }
101
+
102
+ const translateClass = isBottom ? '-translate-y-3' : 'translate-y-3'
103
+
104
+ return {
105
+ enterActiveClass: `transition-all duration-200 ease-out ${origins[props.anchor]}`,
106
+ leaveActiveClass: `transition-all duration-150 ease-in ${origins[props.anchor]}`,
107
+ enterFromClass: `opacity-0 scale-95 ${translateClass}`,
108
+ leaveToClass: `opacity-0 scale-95 ${translateClass}`,
109
+ enterToClass: 'opacity-100 scale-100 translate-y-0',
110
+ leaveFromClass: 'opacity-100 scale-100 translate-y-0',
111
+ }
112
+ })
113
+ </script>
114
+
115
+ <template>
116
+ <div ref="wrapperRef" class="relative inline-block" @keydown="handleKeydown">
117
+ <span ref="triggerRef">
118
+ <slot name="trigger" :is-open="isOpen" :toggle="() => (isOpen = !isOpen)">
119
+ <NexxtButton
120
+ icon="calendar"
121
+ variant="secondary"
122
+ :aria-expanded="isOpen"
123
+ aria-haspopup="grid"
124
+ @click="isOpen = !isOpen"
125
+ >
126
+ {{ props.placeholder }}
127
+ </NexxtButton>
128
+ </slot>
129
+ </span>
130
+
131
+ <Transition v-bind="transitionClasses">
132
+ <div
133
+ v-if="isOpen"
134
+ ref="popoverRef"
135
+ tabindex="-1"
136
+ :class="['absolute z-50 outline-none', popoverClasses]"
137
+ >
138
+ <NexxtCalendar v-model="model" v-bind="calendarProps" @select="handleSelect" />
139
+ </div>
140
+ </Transition>
141
+ </div>
142
+ </template>
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import Header from './Header.vue'
3
+ import Button from '../Button/Button.vue'
4
+
5
+ export default {
6
+ title: 'Components/Atoms/Header',
7
+ component: Header,
8
+ parameters: {
9
+ design: {
10
+ type: 'figma',
11
+ url: 'https://www.figma.com/design/CdbFJ7qUga6mtjagcPDvYK/Design-System?node-id=441-280&t=CbHvOQfEMIr7M5mO-4',
12
+ },
13
+ },
14
+ } satisfies Meta<typeof Header>
15
+
16
+ type Story = StoryObj<typeof Header>
17
+
18
+ export const Default: Story = {
19
+ args: {
20
+ title: 'Header Title',
21
+ backButtonText: 'Back',
22
+ backButtonAction: () => console.log('Back button clicked'),
23
+ },
24
+ }
25
+
26
+ export const WithSlots: Story = {
27
+ args: {
28
+ title: 'Header Title',
29
+ backButtonText: 'Go Back',
30
+ backButtonAction: () => console.log('Back clicked'),
31
+ },
32
+ render: (args) => ({
33
+ components: { Header, Button },
34
+ setup() {
35
+ return { args }
36
+ },
37
+ template: `
38
+ <Header v-bind="args">
39
+ <template #center>
40
+ <span class="font-bold">Centered Content</span>
41
+ </template>
42
+ <template #right>
43
+ <Button variant="primary" icon="arrow-right" :icon-right="true">Next</Button>
44
+ </template>
45
+ </Header>
46
+ `,
47
+ }),
48
+ }