@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.
- package/dist/index.d.ts +75 -8
- package/dist/index.js +2408 -252
- package/dist/nuxt.d.ts +1 -0
- package/dist/nuxt.js +49 -0
- package/package.json +23 -13
- package/src/assets/images/Template_aanvragen.jpg +0 -0
- package/src/assets/svg/type_post.svg +1 -0
- package/src/assets/svg/type_reel.svg +54 -0
- package/src/assets/svg/type_story.svg +46 -0
- package/src/assets/svg/type_tiktok.svg +1 -0
- package/src/assets/video/Template_aanvragen.mp4 +0 -0
- package/src/components/AnimatedNumber/AnimatedNumber.stories.ts +15 -0
- package/src/components/AnimatedNumber/AnimatedNumber.test.ts +56 -0
- package/src/components/AnimatedNumber/AnimatedNumber.vue +61 -0
- package/src/components/Button/Button.stories.ts +212 -0
- package/src/components/Button/Button.test.ts +318 -0
- package/src/components/Button/Button.vue +67 -0
- package/src/components/Calendar/Calendar.stories.ts +91 -0
- package/src/components/Calendar/Calendar.test.ts +269 -0
- package/src/components/Calendar/Calendar.vue +221 -0
- package/src/components/Calendar/_CalendarDayView.test.ts +145 -0
- package/src/components/Calendar/_CalendarDayView.vue +156 -0
- package/src/components/Calendar/_CalendarHeader.test.ts +86 -0
- package/src/components/Calendar/_CalendarHeader.vue +123 -0
- package/src/components/Calendar/_CalendarMonthView.test.ts +68 -0
- package/src/components/Calendar/_CalendarMonthView.vue +70 -0
- package/src/components/Calendar/_CalendarYearView.vue +77 -0
- package/src/components/Calendar/calendar.types.ts +10 -0
- package/src/components/Chip/Chip.stories.ts +42 -0
- package/src/components/Chip/Chip.test.ts +51 -0
- package/src/components/Chip/Chip.vue +37 -0
- package/src/components/DatePicker/DatePicker.stories.ts +149 -0
- package/src/components/DatePicker/DatePicker.test.ts +191 -0
- package/src/components/DatePicker/DatePicker.vue +142 -0
- package/src/components/Header/Header.stories.ts +48 -0
- package/src/components/Header/Header.test.ts +169 -0
- package/src/components/Header/Header.vue +42 -0
- package/src/components/Icon/Icon.stories.ts +50 -0
- package/src/components/Icon/Icon.test.ts +73 -0
- package/src/components/Icon/Icon.vue +20 -0
- package/src/components/InfoBlock/InfoBlock.stories.ts +90 -0
- package/src/components/InfoBlock/InfoBlock.test.ts +101 -0
- package/src/components/InfoBlock/InfoBlock.vue +70 -0
- package/src/components/ProgressBar/ProgressBar.stories.ts +30 -0
- package/src/components/ProgressBar/ProgressBar.test.ts +314 -0
- package/src/components/ProgressBar/ProgressBar.vue +102 -0
- package/src/components/SocialIcons/SocialIcons.stories.ts +34 -0
- package/src/components/SocialIcons/SocialIcons.test.ts +58 -0
- package/src/components/SocialIcons/SocialIcons.vue +58 -0
- package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
- package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +131 -0
- package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue +55 -0
- package/src/components/SocialMediaTemplate/SocialMediaTemplate.stories.ts +71 -0
- package/src/components/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
- package/src/components/SocialMediaTemplate/SocialMediaTemplate.vue +130 -0
- package/src/components/SocialMediaType/SocialMediaType.stories.ts +43 -0
- package/src/components/SocialMediaType/SocialMediaType.test.ts +126 -0
- package/src/components/SocialMediaType/SocialMediaType.vue +117 -0
- package/src/components/StepperHeader/StepperHeader.stories.ts +47 -0
- package/src/components/StepperHeader/StepperHeader.test.ts +244 -0
- package/src/components/StepperHeader/StepperHeader.vue +37 -0
- package/src/components.json +16 -0
- package/src/env.d.ts +23 -0
- package/src/index.css +2 -0
- package/src/index.ts +15 -0
- package/src/nuxt.ts +50 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { describe, it, expect } from 'vitest'
|
|
3
|
+
import SocialMediaType from './SocialMediaType.vue'
|
|
4
|
+
import SocialIcons, { type NexxtSocialIconsProps } from '../SocialIcons/SocialIcons.vue'
|
|
5
|
+
|
|
6
|
+
type Variant = 'post' | 'reel' | 'story'
|
|
7
|
+
|
|
8
|
+
describe('SocialMediaType', () => {
|
|
9
|
+
it.each([
|
|
10
|
+
['post', 'Post', { facebook: true, instagram: true, linkedin: true, x: true, google: true }],
|
|
11
|
+
['reel', 'TikTok & Reel', { instagram: true, tiktok: true }],
|
|
12
|
+
['story', 'Story', { instagram: true }],
|
|
13
|
+
])('renders correctly for variant: %s', (variant, expectedLabel, expectedIcons) => {
|
|
14
|
+
const wrapper = mount(SocialMediaType, {
|
|
15
|
+
props: {
|
|
16
|
+
variant: variant as Variant,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Check label
|
|
21
|
+
expect(wrapper.text()).toContain(expectedLabel)
|
|
22
|
+
|
|
23
|
+
// Check SocialIcons props
|
|
24
|
+
const socialIcons = wrapper.findComponent(SocialIcons)
|
|
25
|
+
expect(socialIcons.exists()).toBe(true)
|
|
26
|
+
|
|
27
|
+
Object.entries(expectedIcons).forEach(([prop, value]) => {
|
|
28
|
+
expect(socialIcons.props(prop as keyof NexxtSocialIconsProps)).toBe(value)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('renders the chip when provided', () => {
|
|
33
|
+
const wrapper = mount(SocialMediaType, {
|
|
34
|
+
props: {
|
|
35
|
+
variant: 'post',
|
|
36
|
+
chip: 'Featured',
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
expect(wrapper.text()).toContain('Featured')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('emits click event when clicked', async () => {
|
|
43
|
+
const wrapper = mount(SocialMediaType, {
|
|
44
|
+
props: {
|
|
45
|
+
variant: 'reel',
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
await wrapper.trigger('click')
|
|
49
|
+
expect(wrapper.emitted('click')).toBeTruthy()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('normalizes twitter to x in custom icons', () => {
|
|
53
|
+
const wrapper = mount(SocialMediaType, {
|
|
54
|
+
props: {
|
|
55
|
+
variant: 'post',
|
|
56
|
+
icons: ['twitter', 'facebook'],
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const socialIcons = wrapper.findComponent(SocialIcons)
|
|
61
|
+
expect(socialIcons.props('x')).toBe(true)
|
|
62
|
+
expect(socialIcons.props('facebook')).toBe(true)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('handles custom icons array correctly', () => {
|
|
66
|
+
const wrapper = mount(SocialMediaType, {
|
|
67
|
+
props: {
|
|
68
|
+
variant: 'reel',
|
|
69
|
+
icons: ['instagram', 'tiktok', 'linkedin'],
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const socialIcons = wrapper.findComponent(SocialIcons)
|
|
74
|
+
expect(socialIcons.props('instagram')).toBe(true)
|
|
75
|
+
expect(socialIcons.props('tiktok')).toBe(true)
|
|
76
|
+
expect(socialIcons.props('linkedin')).toBe(true)
|
|
77
|
+
expect(socialIcons.props('facebook')).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('renders the selected state correctly', () => {
|
|
81
|
+
const wrapper = mount(SocialMediaType, {
|
|
82
|
+
props: {
|
|
83
|
+
variant: 'story',
|
|
84
|
+
selected: true,
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
expect(wrapper.classes()).toContain('bg-gray-50')
|
|
88
|
+
const icons = wrapper.findAllComponents({ name: 'NexxtIcon' })
|
|
89
|
+
const checkIcon = icons.find((icon) => icon.props('name') === 'check')
|
|
90
|
+
expect(checkIcon).toBeDefined()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('renders a custom label', () => {
|
|
94
|
+
const wrapper = mount(SocialMediaType, {
|
|
95
|
+
props: {
|
|
96
|
+
variant: 'post',
|
|
97
|
+
label: 'My Custom Post',
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
expect(wrapper.text()).toContain('My Custom Post')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('handles default branch for variant (if TS bypassed)', () => {
|
|
104
|
+
const wrapper = mount(SocialMediaType, {
|
|
105
|
+
props: {
|
|
106
|
+
// @ts-expect-error - testing invalid variant bypass
|
|
107
|
+
variant: 'invalid',
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
const socialIcons = wrapper.findComponent(SocialIcons)
|
|
111
|
+
// Should hit default: return {}
|
|
112
|
+
expect(socialIcons.exists()).toBe(true)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('handles empty icons array', () => {
|
|
116
|
+
const wrapper = mount(SocialMediaType, {
|
|
117
|
+
props: {
|
|
118
|
+
variant: 'post',
|
|
119
|
+
icons: [],
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
const socialIcons = wrapper.findComponent(SocialIcons)
|
|
123
|
+
// Should have no icons
|
|
124
|
+
expect(socialIcons.exists()).toBe(true)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import TiktokSvg from '@assets/svg/type_tiktok.svg'
|
|
4
|
+
import PostSvg from '@assets/svg/type_post.svg'
|
|
5
|
+
import StorySvg from '@assets/svg/type_story.svg'
|
|
6
|
+
import Chip from '../Chip/Chip.vue'
|
|
7
|
+
import SocialIcons from '../SocialIcons/SocialIcons.vue'
|
|
8
|
+
import Icon from '../Icon/Icon.vue'
|
|
9
|
+
|
|
10
|
+
type SocialPlatform = 'tiktok' | 'facebook' | 'instagram' | 'linkedin' | 'x' | 'twitter' | 'google'
|
|
11
|
+
|
|
12
|
+
interface NexxtSocialMediaTypeProps {
|
|
13
|
+
variant: 'post' | 'reel' | 'story'
|
|
14
|
+
label?: string
|
|
15
|
+
chip?: string
|
|
16
|
+
selected?: boolean
|
|
17
|
+
icons?: SocialPlatform[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
defineOptions({
|
|
21
|
+
name: 'NexxtSocialMediaType',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits(['click'])
|
|
25
|
+
|
|
26
|
+
const { variant, selected, icons, label } = defineProps<NexxtSocialMediaTypeProps>()
|
|
27
|
+
|
|
28
|
+
const labels = {
|
|
29
|
+
post: 'Post',
|
|
30
|
+
reel: 'TikTok & Reel',
|
|
31
|
+
story: 'Story',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const iconSrc = computed(() => {
|
|
35
|
+
const icons = {
|
|
36
|
+
post: PostSvg,
|
|
37
|
+
reel: TiktokSvg,
|
|
38
|
+
story: StorySvg,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return icons[variant]
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const computedIcons = computed(() => {
|
|
45
|
+
if (icons) {
|
|
46
|
+
// Normalize twitter to x and convert array to object
|
|
47
|
+
const normalized = icons.map((icon) => (icon === 'twitter' ? 'x' : icon))
|
|
48
|
+
return normalized.reduce(
|
|
49
|
+
(acc, icon) => {
|
|
50
|
+
acc[icon] = true
|
|
51
|
+
return acc
|
|
52
|
+
},
|
|
53
|
+
{} as Record<string, boolean>,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switch (variant) {
|
|
58
|
+
case 'post':
|
|
59
|
+
return {
|
|
60
|
+
facebook: true,
|
|
61
|
+
instagram: true,
|
|
62
|
+
linkedin: true,
|
|
63
|
+
x: true,
|
|
64
|
+
google: true,
|
|
65
|
+
}
|
|
66
|
+
case 'reel':
|
|
67
|
+
return {
|
|
68
|
+
instagram: true,
|
|
69
|
+
tiktok: true,
|
|
70
|
+
}
|
|
71
|
+
case 'story':
|
|
72
|
+
return {
|
|
73
|
+
instagram: true,
|
|
74
|
+
}
|
|
75
|
+
default:
|
|
76
|
+
return {}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
class="group inline-flex min-w-60 cursor-pointer flex-col items-start justify-start gap-2.5 overflow-hidden rounded-3xl pt-4 outline-1 -outline-offset-1 transition-colors hover:outline-cornflower-blue-600 focus-visible:outline-2 focus-visible:outline-cornflower-blue-600"
|
|
85
|
+
:class="selected ? 'bg-gray-50 outline-cornflower-blue-500' : 'bg-white outline-gray-200'"
|
|
86
|
+
@click="emit('click')"
|
|
87
|
+
>
|
|
88
|
+
<div class="flex items-center justify-end self-stretch px-4">
|
|
89
|
+
<Chip v-if="chip" variant="warning">{{ chip }}</Chip>
|
|
90
|
+
<SocialIcons class="ml-auto" v-bind="computedIcons" aria-hidden="true" />
|
|
91
|
+
</div>
|
|
92
|
+
<div class="flex justify-center self-stretch py-14">
|
|
93
|
+
<component
|
|
94
|
+
:is="iconSrc"
|
|
95
|
+
class="h-44 object-contain transition-transform group-hover:-translate-y-0.5"
|
|
96
|
+
aria-hidden="true"
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
<div
|
|
100
|
+
class="inline-flex w-full flex-col items-center justify-center gap-2.5 bg-cornflower-blue-500 py-3.5 transition-colors group-hover:bg-cornflower-blue-600"
|
|
101
|
+
:class="selected ? 'bg-cornflower-blue-600' : 'bg-cornflower-blue-500'"
|
|
102
|
+
>
|
|
103
|
+
<div class="flex w-full justify-center self-stretch text-center">
|
|
104
|
+
<span class="relative block max-w-full text-center body-semibold text-ellipsis text-white">
|
|
105
|
+
<Icon
|
|
106
|
+
v-if="selected"
|
|
107
|
+
name="check"
|
|
108
|
+
type="solid"
|
|
109
|
+
class="absolute top-1/2 -translate-x-[calc(100%+4px)] -translate-y-[calc(50%+1px)] text-white"
|
|
110
|
+
aria-hidden="true"
|
|
111
|
+
/>
|
|
112
|
+
{{ label ?? labels[variant] }}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</button>
|
|
117
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import StepperHeader from './StepperHeader.vue'
|
|
3
|
+
import Button from '../Button/Button.vue'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Molecules/Stepper Header',
|
|
7
|
+
component: StepperHeader,
|
|
8
|
+
parameters: {
|
|
9
|
+
design: {
|
|
10
|
+
type: 'figma',
|
|
11
|
+
url: 'https://www.figma.com/design/CdbFJ7qUga6mtjagcPDvYK/Design-System?node-id=442-1060&t=CbHvOQfEMIr7M5mO-11',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof StepperHeader>
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof StepperHeader>
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
title: 'Stepper Header Title',
|
|
21
|
+
steps: ['Soort', 'Type', 'Inhoud', 'Template', 'Bewerk', 'Inplannen'],
|
|
22
|
+
currentStep: 0,
|
|
23
|
+
backButtonText: 'Back',
|
|
24
|
+
backButtonAction: () => alert('Back button clicked'),
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const WithSlots: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
title: 'Stepper Header Title',
|
|
31
|
+
steps: ['Soort', 'Type', 'Inhoud', 'Template', 'Bewerk', 'Inplannen'],
|
|
32
|
+
currentStep: 0,
|
|
33
|
+
backButtonText: 'Back',
|
|
34
|
+
backButtonAction: () => alert('Back button clicked'),
|
|
35
|
+
},
|
|
36
|
+
render: (args) => ({
|
|
37
|
+
components: { StepperHeader, Button },
|
|
38
|
+
setup() {
|
|
39
|
+
return { args }
|
|
40
|
+
},
|
|
41
|
+
template: `
|
|
42
|
+
<StepperHeader v-bind="args">
|
|
43
|
+
<Button>Next</Button>
|
|
44
|
+
</StepperHeader>
|
|
45
|
+
`,
|
|
46
|
+
}),
|
|
47
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import StepperHeader from './StepperHeader.vue'
|
|
4
|
+
import Header from '../Header/Header.vue'
|
|
5
|
+
import ProgressBar from '../ProgressBar/ProgressBar.vue'
|
|
6
|
+
|
|
7
|
+
describe('StepperHeader', () => {
|
|
8
|
+
const defaultProps = {
|
|
9
|
+
title: 'Test Title',
|
|
10
|
+
currentStep: 0,
|
|
11
|
+
steps: ['Step 1', 'Step 2', 'Step 3'],
|
|
12
|
+
backButtonAction: vi.fn(),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
it('renders the header component', () => {
|
|
16
|
+
const wrapper = mount(StepperHeader, {
|
|
17
|
+
props: defaultProps,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const header = wrapper.findComponent(Header)
|
|
21
|
+
expect(header.exists()).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('passes title prop to Header component', () => {
|
|
25
|
+
const wrapper = mount(StepperHeader, {
|
|
26
|
+
props: defaultProps,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const header = wrapper.findComponent(Header)
|
|
30
|
+
expect(header.props('title')).toBe('Test Title')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('passes backButtonAction to Header component', () => {
|
|
34
|
+
const backButtonAction = vi.fn()
|
|
35
|
+
const wrapper = mount(StepperHeader, {
|
|
36
|
+
props: {
|
|
37
|
+
...defaultProps,
|
|
38
|
+
backButtonAction,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const header = wrapper.findComponent(Header)
|
|
43
|
+
expect(header.props('backButtonAction')).toBe(backButtonAction)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('passes backButtonText to Header component when provided', () => {
|
|
47
|
+
const wrapper = mount(StepperHeader, {
|
|
48
|
+
props: {
|
|
49
|
+
...defaultProps,
|
|
50
|
+
backButtonText: 'Go Back',
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const header = wrapper.findComponent(Header)
|
|
55
|
+
expect(header.props('backButtonText')).toBe('Go Back')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('renders ProgressBar component when steps are provided', () => {
|
|
59
|
+
const wrapper = mount(StepperHeader, {
|
|
60
|
+
props: defaultProps,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
64
|
+
expect(progressBar.exists()).toBe(true)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('passes correct props to ProgressBar component', () => {
|
|
68
|
+
const wrapper = mount(StepperHeader, {
|
|
69
|
+
props: {
|
|
70
|
+
...defaultProps,
|
|
71
|
+
currentStep: 2,
|
|
72
|
+
steps: ['Step 1', 'Step 2', 'Step 3', 'Step 4'],
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
77
|
+
expect(progressBar.props('steps')).toBe(4)
|
|
78
|
+
expect(progressBar.props('stepTitles')).toEqual(['Step 1', 'Step 2', 'Step 3', 'Step 4'])
|
|
79
|
+
expect(progressBar.props('currentStep')).toBe(2)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('does not render ProgressBar when steps is null or undefined', () => {
|
|
83
|
+
const wrapper = mount(StepperHeader, {
|
|
84
|
+
props: {
|
|
85
|
+
title: 'Test',
|
|
86
|
+
currentStep: 0,
|
|
87
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
88
|
+
steps: null as any,
|
|
89
|
+
backButtonAction: vi.fn(),
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
94
|
+
expect(progressBar.exists()).toBe(false)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('renders default slot content in Header right slot', () => {
|
|
98
|
+
const wrapper = mount(StepperHeader, {
|
|
99
|
+
props: defaultProps,
|
|
100
|
+
slots: {
|
|
101
|
+
default: '<button class="test-button">Next</button>',
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
expect(wrapper.find('.test-button').exists()).toBe(true)
|
|
106
|
+
expect(wrapper.find('.test-button').text()).toBe('Next')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('does not render right slot when default slot is empty', () => {
|
|
110
|
+
const wrapper = mount(StepperHeader, {
|
|
111
|
+
props: defaultProps,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// When no default slot is provided, the right slot in Header should not exist
|
|
115
|
+
expect(wrapper.find('.ml-auto').exists()).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('applies w-full class to ProgressBar', () => {
|
|
119
|
+
const wrapper = mount(StepperHeader, {
|
|
120
|
+
props: defaultProps,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
124
|
+
expect(progressBar.classes()).toContain('w-full')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('renders with all props and slots correctly', () => {
|
|
128
|
+
const backButtonAction = vi.fn()
|
|
129
|
+
const wrapper = mount(StepperHeader, {
|
|
130
|
+
props: {
|
|
131
|
+
title: 'Complete Header',
|
|
132
|
+
currentStep: 1,
|
|
133
|
+
steps: ['One', 'Two', 'Three'],
|
|
134
|
+
backButtonText: 'Previous',
|
|
135
|
+
backButtonAction,
|
|
136
|
+
},
|
|
137
|
+
slots: {
|
|
138
|
+
default: '<button>Continue</button>',
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const header = wrapper.findComponent(Header)
|
|
143
|
+
expect(header.props('title')).toBe('Complete Header')
|
|
144
|
+
expect(header.props('backButtonText')).toBe('Previous')
|
|
145
|
+
expect(header.props('backButtonAction')).toBe(backButtonAction)
|
|
146
|
+
|
|
147
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
148
|
+
expect(progressBar.props('currentStep')).toBe(1)
|
|
149
|
+
expect(progressBar.props('steps')).toBe(3)
|
|
150
|
+
expect(progressBar.props('stepTitles')).toEqual(['One', 'Two', 'Three'])
|
|
151
|
+
|
|
152
|
+
expect(wrapper.text()).toContain('Continue')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('renders without steps and without default slot', () => {
|
|
156
|
+
const wrapper = mount(StepperHeader, {
|
|
157
|
+
props: {
|
|
158
|
+
title: 'Test',
|
|
159
|
+
currentStep: 0,
|
|
160
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
161
|
+
steps: null as any,
|
|
162
|
+
backButtonAction: vi.fn(),
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
167
|
+
expect(progressBar.exists()).toBe(false)
|
|
168
|
+
expect(wrapper.find('.ml-auto').exists()).toBe(false)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('renders with steps but without default slot', () => {
|
|
172
|
+
const wrapper = mount(StepperHeader, {
|
|
173
|
+
props: defaultProps,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
177
|
+
expect(progressBar.exists()).toBe(true)
|
|
178
|
+
expect(wrapper.find('.ml-auto').exists()).toBe(false)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('renders without steps but with default slot', () => {
|
|
182
|
+
const wrapper = mount(StepperHeader, {
|
|
183
|
+
props: {
|
|
184
|
+
title: 'Test',
|
|
185
|
+
currentStep: 0,
|
|
186
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
187
|
+
steps: null as any,
|
|
188
|
+
backButtonAction: vi.fn(),
|
|
189
|
+
},
|
|
190
|
+
slots: {
|
|
191
|
+
default: '<button class="action-button">Action</button>',
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
196
|
+
expect(progressBar.exists()).toBe(false)
|
|
197
|
+
expect(wrapper.find('.action-button').exists()).toBe(true)
|
|
198
|
+
expect(wrapper.find('.ml-auto').exists()).toBe(true)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('renders with empty steps array', () => {
|
|
202
|
+
const wrapper = mount(StepperHeader, {
|
|
203
|
+
props: {
|
|
204
|
+
title: 'Test',
|
|
205
|
+
currentStep: 0,
|
|
206
|
+
steps: [],
|
|
207
|
+
backButtonAction: vi.fn(),
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// Empty array is truthy, so ProgressBar should still render
|
|
212
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
213
|
+
expect(progressBar.exists()).toBe(true)
|
|
214
|
+
expect(progressBar.props('steps')).toBe(0)
|
|
215
|
+
expect(progressBar.props('stepTitles')).toEqual([])
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('renders with undefined steps', () => {
|
|
219
|
+
const wrapper = mount(StepperHeader, {
|
|
220
|
+
props: {
|
|
221
|
+
title: 'Test',
|
|
222
|
+
currentStep: 0,
|
|
223
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
224
|
+
steps: undefined as any,
|
|
225
|
+
backButtonAction: vi.fn(),
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
230
|
+
expect(progressBar.exists()).toBe(false)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('emits update:currentStep when ProgressBar emits it', async () => {
|
|
234
|
+
const wrapper = mount(StepperHeader, {
|
|
235
|
+
props: defaultProps,
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const progressBar = wrapper.findComponent(ProgressBar)
|
|
239
|
+
await progressBar.vm.$emit('update:currentStep', 1)
|
|
240
|
+
|
|
241
|
+
expect(wrapper.emitted('update:currentStep')).toBeTruthy()
|
|
242
|
+
expect(wrapper.emitted('update:currentStep')![0]).toEqual([1])
|
|
243
|
+
})
|
|
244
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import Header from '../Header/Header.vue'
|
|
3
|
+
import ProgressBar from '../ProgressBar/ProgressBar.vue'
|
|
4
|
+
|
|
5
|
+
interface NexxtStepperHeaderProps {
|
|
6
|
+
title: string
|
|
7
|
+
currentStep: InstanceType<typeof ProgressBar>['currentStep']
|
|
8
|
+
steps: InstanceType<typeof ProgressBar>['stepTitles']
|
|
9
|
+
backButtonText?: InstanceType<typeof Header>['backButtonText']
|
|
10
|
+
backButtonAction: InstanceType<typeof Header>['backButtonAction']
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
defineOptions({
|
|
14
|
+
name: 'NexxtStepperHeader',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
defineEmits(['update:currentStep'])
|
|
18
|
+
|
|
19
|
+
defineProps<NexxtStepperHeaderProps>()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<Header :title="title" :back-button-action="backButtonAction" :back-button-text="backButtonText">
|
|
24
|
+
<template v-if="steps" #center>
|
|
25
|
+
<ProgressBar
|
|
26
|
+
class="w-full"
|
|
27
|
+
:steps="steps.length"
|
|
28
|
+
:step-titles="steps"
|
|
29
|
+
:current-step="currentStep"
|
|
30
|
+
@update:currentStep="$emit('update:currentStep', $event)"
|
|
31
|
+
/>
|
|
32
|
+
</template>
|
|
33
|
+
<template v-if="$slots.default" #right>
|
|
34
|
+
<slot />
|
|
35
|
+
</template>
|
|
36
|
+
</Header>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"NexxtAnimatedNumber": "components/AnimatedNumber/AnimatedNumber.vue",
|
|
3
|
+
"NexxtButton": "components/Button/Button.vue",
|
|
4
|
+
"NexxtCalendar": "components/Calendar/Calendar.vue",
|
|
5
|
+
"NexxtChip": "components/Chip/Chip.vue",
|
|
6
|
+
"NexxtDatePicker": "components/DatePicker/DatePicker.vue",
|
|
7
|
+
"NexxtHeader": "components/Header/Header.vue",
|
|
8
|
+
"NexxtIcon": "components/Icon/Icon.vue",
|
|
9
|
+
"NexxtInfoBlock": "components/InfoBlock/InfoBlock.vue",
|
|
10
|
+
"NexxtProgressBar": "components/ProgressBar/ProgressBar.vue",
|
|
11
|
+
"NexxtSocialIcons": "components/SocialIcons/SocialIcons.vue",
|
|
12
|
+
"NexxtSocialMediaCustomTemplate": "components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue",
|
|
13
|
+
"NexxtSocialMediaTemplate": "components/SocialMediaTemplate/SocialMediaTemplate.vue",
|
|
14
|
+
"NexxtSocialMediaType": "components/SocialMediaType/SocialMediaType.vue",
|
|
15
|
+
"NexxtStepperHeader": "components/StepperHeader/StepperHeader.vue"
|
|
16
|
+
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
declare module '*.svg' {
|
|
4
|
+
import type { DefineComponent } from 'vue'
|
|
5
|
+
const component: DefineComponent<object, object, unknown>
|
|
6
|
+
export default component
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module '*.svg?component' {
|
|
10
|
+
import type { DefineComponent } from 'vue'
|
|
11
|
+
const component: DefineComponent<object, object, unknown>
|
|
12
|
+
export default component
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module '*.svg?url' {
|
|
16
|
+
const content: string
|
|
17
|
+
export default content
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare module '*.svg?raw' {
|
|
21
|
+
const content: string
|
|
22
|
+
export default content
|
|
23
|
+
}
|
package/src/index.css
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// This file is auto-generated by scripts/generate-index.ts
|
|
2
|
+
export { default as NexxtAnimatedNumber } from './components/AnimatedNumber/AnimatedNumber.vue'
|
|
3
|
+
export { default as NexxtButton } from './components/Button/Button.vue'
|
|
4
|
+
export { default as NexxtCalendar } from './components/Calendar/Calendar.vue'
|
|
5
|
+
export { default as NexxtChip } from './components/Chip/Chip.vue'
|
|
6
|
+
export { default as NexxtDatePicker } from './components/DatePicker/DatePicker.vue'
|
|
7
|
+
export { default as NexxtHeader } from './components/Header/Header.vue'
|
|
8
|
+
export { default as NexxtIcon } from './components/Icon/Icon.vue'
|
|
9
|
+
export { default as NexxtInfoBlock } from './components/InfoBlock/InfoBlock.vue'
|
|
10
|
+
export { default as NexxtProgressBar } from './components/ProgressBar/ProgressBar.vue'
|
|
11
|
+
export { default as NexxtSocialIcons } from './components/SocialIcons/SocialIcons.vue'
|
|
12
|
+
export { default as NexxtSocialMediaCustomTemplate } from './components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue'
|
|
13
|
+
export { default as NexxtSocialMediaTemplate } from './components/SocialMediaTemplate/SocialMediaTemplate.vue'
|
|
14
|
+
export { default as NexxtSocialMediaType } from './components/SocialMediaType/SocialMediaType.vue'
|
|
15
|
+
export { default as NexxtStepperHeader } from './components/StepperHeader/StepperHeader.vue'
|
package/src/nuxt.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineNuxtModule, addComponent, addTemplate } from '@nuxt/kit'
|
|
2
|
+
import type { NuxtModule } from '@nuxt/schema'
|
|
3
|
+
import components from './components.json'
|
|
4
|
+
|
|
5
|
+
export interface ModuleOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Whether to automatically add the theme CSS.
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
addCSS?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const module: NuxtModule<ModuleOptions> = defineNuxtModule<ModuleOptions>({
|
|
14
|
+
meta: {
|
|
15
|
+
name: '@nexxtmove/ui',
|
|
16
|
+
configKey: 'nexxtmoveUi',
|
|
17
|
+
compatibility: {
|
|
18
|
+
nuxt: '^3.0.0 || ^4.0.0',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaults: {
|
|
22
|
+
addCSS: true,
|
|
23
|
+
},
|
|
24
|
+
setup(options, nuxt) {
|
|
25
|
+
for (const [name, path] of Object.entries(components)) {
|
|
26
|
+
addComponent({
|
|
27
|
+
name,
|
|
28
|
+
export: 'default',
|
|
29
|
+
filePath: `@nexxtmove/ui/${path}`,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Add CSS
|
|
34
|
+
if (options.addCSS) {
|
|
35
|
+
addTemplate({
|
|
36
|
+
filename: 'nexxtmove-ui.css',
|
|
37
|
+
getContents: () => `
|
|
38
|
+
@import "tailwindcss";
|
|
39
|
+
@import "@nexxtmove/ui/theme";
|
|
40
|
+
|
|
41
|
+
@source "../../node_modules/@nexxtmove/ui/dist/*.js";
|
|
42
|
+
`,
|
|
43
|
+
write: true,
|
|
44
|
+
})
|
|
45
|
+
nuxt.options.css.push('#build/nexxtmove-ui.css')
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export default module
|