@nexxtmove/ui 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/dist/components/editor/molecules/EditorBorder/EditorBorder.vue.d.ts +12 -0
  2. package/dist/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue.d.ts +16 -0
  3. package/dist/index.js +1094 -1135
  4. package/dist/nuxt.js +0 -1
  5. package/package.json +38 -36
  6. package/src/attribute-contract.test.ts +151 -0
  7. package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
  8. package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
  9. package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
  10. package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
  11. package/src/components/atoms/Button/Button.stories.ts +290 -0
  12. package/src/components/atoms/Button/Button.test.ts +406 -0
  13. package/src/components/atoms/Card/Card.stories.ts +45 -0
  14. package/src/components/atoms/Card/Card.test.ts +46 -0
  15. package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
  16. package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
  17. package/src/components/atoms/Chip/Chip.stories.ts +274 -0
  18. package/src/components/atoms/Chip/Chip.test.ts +230 -0
  19. package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
  20. package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
  21. package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
  22. package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
  23. package/src/components/atoms/Icon/Icon.stories.ts +63 -0
  24. package/src/components/atoms/Icon/Icon.test.ts +89 -0
  25. package/src/components/atoms/Loader/Loader.stories.ts +36 -0
  26. package/src/components/atoms/Loader/Loader.test.ts +40 -0
  27. package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
  28. package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
  29. package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
  30. package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
  31. package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
  32. package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
  33. package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
  34. package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
  35. package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
  36. package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
  37. package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
  38. package/src/components/atoms/Slider/Slider.stories.ts +151 -0
  39. package/src/components/atoms/Slider/Slider.test.ts +191 -0
  40. package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
  41. package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
  42. package/src/components/atoms/Switch/Switch.stories.ts +91 -0
  43. package/src/components/atoms/Switch/Switch.test.ts +183 -0
  44. package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
  45. package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
  46. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
  47. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
  48. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
  49. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
  50. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
  51. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
  52. package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
  53. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
  54. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
  55. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
  56. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
  57. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
  58. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
  59. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
  60. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
  61. package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
  62. package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
  63. package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
  64. package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
  65. package/src/components/editor/molecules/EditorBorder/EditorBorder.vue +58 -2
  66. package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +563 -0
  67. package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +689 -0
  68. package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +793 -0
  69. package/src/components/editor/molecules/EditorColor/_ColorField.vue +7 -29
  70. package/src/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue +57 -0
  71. package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
  72. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
  73. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
  74. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
  75. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
  76. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
  77. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
  78. package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
  79. package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
  80. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
  81. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
  82. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
  83. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
  84. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
  85. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
  86. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
  87. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
  88. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
  89. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
  90. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
  91. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
  92. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
  93. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
  94. package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
  95. package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
  96. package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
  97. package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
  98. package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
  99. package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
  100. package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
  101. package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
  102. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
  103. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
  104. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
  105. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
  106. package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
  107. package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
  108. package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
  109. package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
  110. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
  111. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
  112. package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
  113. package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
  114. package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
  115. package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
  116. package/src/components/molecules/InputField/InputField.stories.ts +117 -0
  117. package/src/components/molecules/InputField/InputField.test.ts +138 -0
  118. package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
  119. package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
  120. package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
  121. package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
  122. package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
  123. package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
  124. package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
  125. package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
  126. package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
  127. package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
  128. package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
  129. package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
  130. package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
  131. package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
  132. package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
  133. package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
  134. package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
  135. package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
  136. package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
  137. package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
  138. package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
  139. package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
  140. package/src/components/molecules/Select/Select.stories.ts +264 -0
  141. package/src/components/molecules/Select/Select.test.ts +315 -0
  142. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
  143. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
  144. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
  145. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
  146. package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
  147. package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
  148. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
  149. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
  150. package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
  151. package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
  152. package/src/components/molecules/Source/Source.stories.ts +35 -0
  153. package/src/components/molecules/Source/Source.test.ts +45 -0
  154. package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
  155. package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
  156. package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
  157. package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
  158. package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
  159. package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
  160. package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
  161. package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
  162. package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
  163. package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
  164. package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
  165. package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
  166. package/src/components/molecules/Toast/Toast.stories.ts +110 -0
  167. package/src/components/molecules/Toast/Toast.test.ts +103 -0
  168. package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
  169. package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
  170. package/src/components/molecules/Tree/Tree.stories.ts +884 -0
  171. package/src/components/molecules/Tree/Tree.test.ts +1247 -0
  172. package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
  173. package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
  174. package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
  175. package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
  176. package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
  177. package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
  178. package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
  179. package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
  180. package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
  181. package/src/components/organisms/ColorPicker/ColorPicker.test.ts +638 -0
  182. package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
  183. package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
  184. package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
  185. package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
  186. package/src/components/organisms/Combobox/Combobox.vue +1 -1
  187. package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
  188. package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
  189. package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
  190. package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
  191. package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
  192. package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
  193. package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
  194. package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
  195. package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
  196. package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
  197. package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
  198. package/src/components/organisms/Header/Header.stories.ts +75 -0
  199. package/src/components/organisms/Header/Header.test.ts +233 -0
  200. package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
  201. package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
  202. package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
  203. package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
  204. package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
  205. package/src/components/organisms/Modal/Modal.stories.ts +350 -0
  206. package/src/components/organisms/Modal/Modal.test.ts +275 -0
  207. package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
  208. package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
  209. package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
  210. package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
  211. package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
  212. package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
  213. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
  214. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
  215. package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
  216. package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
  217. package/src/components/organisms/Table/Table.stories.ts +332 -0
  218. package/src/components/organisms/Table/Table.test.ts +320 -0
  219. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
  220. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
  221. package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
  222. package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
  223. package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
  224. package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
  225. package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
  226. package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
  227. package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
  228. package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
@@ -0,0 +1,417 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { nextTick, ref } from 'vue'
3
+ import { mount } from '@vue/test-utils'
4
+ import ProgressBar from './ProgressBar.vue'
5
+
6
+ vi.mock('@floating-ui/vue', async (importOriginal) => {
7
+ const actual = await importOriginal<typeof import('@floating-ui/vue')>()
8
+ return {
9
+ ...actual,
10
+ useFloating: () => ({
11
+ floatingStyles: ref({}),
12
+ placement: ref('bottom-start'),
13
+ isPositioned: ref(true),
14
+ }),
15
+ autoUpdate: () => () => {},
16
+ }
17
+ })
18
+
19
+ describe('ProgressBar', () => {
20
+ beforeEach(() => {
21
+ vi.clearAllMocks()
22
+ vi.stubGlobal('console', { warn: vi.fn() })
23
+ })
24
+
25
+ it('renders correct percentage for first step', () => {
26
+ const wrapper = mount(ProgressBar, {
27
+ props: {
28
+ steps: 5,
29
+ currentStep: 0,
30
+ },
31
+ })
32
+
33
+ const progressbar = wrapper.find('[role="progressbar"]')
34
+ expect(progressbar.attributes('aria-valuenow')).toBe('0')
35
+
36
+ const innerBar = progressbar.find('div')
37
+ expect(innerBar.attributes('style')).toContain('width: 0%')
38
+ })
39
+
40
+ it('renders correct percentage for middle step', () => {
41
+ const wrapper = mount(ProgressBar, {
42
+ props: {
43
+ steps: 5,
44
+ currentStep: 2,
45
+ },
46
+ })
47
+
48
+ const progressbar = wrapper.find('[role="progressbar"]')
49
+ expect(progressbar.attributes('aria-valuenow')).toBe('50')
50
+ expect(progressbar.find('div').attributes('style')).toContain('width: 50%')
51
+ })
52
+
53
+ it('renders correct percentage for last step', () => {
54
+ const wrapper = mount(ProgressBar, {
55
+ props: {
56
+ steps: 5,
57
+ currentStep: 4,
58
+ },
59
+ })
60
+
61
+ const progressbar = wrapper.find('[role="progressbar"]')
62
+ expect(progressbar.attributes('aria-valuenow')).toBe('100')
63
+ expect(progressbar.find('div').attributes('style')).toContain('width: 100%')
64
+ })
65
+
66
+ it('clamps currentStep within range', () => {
67
+ const wrapperLow = mount(ProgressBar, {
68
+ props: { steps: 5, currentStep: -1 },
69
+ })
70
+ expect(wrapperLow.find('[role="progressbar"]').attributes('aria-valuenow')).toBe('0')
71
+
72
+ const wrapperHigh = mount(ProgressBar, {
73
+ props: { steps: 5, currentStep: 10 },
74
+ })
75
+ expect(wrapperHigh.find('[role="progressbar"]').attributes('aria-valuenow')).toBe('100')
76
+ })
77
+
78
+ it('handles edge case of 0 or 1 steps', () => {
79
+ const wrapperZero = mount(ProgressBar, {
80
+ props: { steps: 0, currentStep: 0 },
81
+ })
82
+ expect(wrapperZero.find('[role="progressbar"]').attributes('aria-valuenow')).toBe('0')
83
+
84
+ const wrapperOne = mount(ProgressBar, {
85
+ props: { steps: 1, currentStep: 0 },
86
+ })
87
+ expect(wrapperOne.find('[role="progressbar"]').attributes('aria-valuenow')).toBe('100')
88
+ })
89
+
90
+ it('handles empty stepTitles array', () => {
91
+ const wrapper = mount(ProgressBar, {
92
+ props: { steps: 5, currentStep: 0, stepTitles: [] },
93
+ })
94
+ expect(wrapper.find('ol').exists()).toBe(false)
95
+ expect(wrapper.find('.tabular-nums').exists()).toBe(false)
96
+ })
97
+
98
+ it('displays correct step text without titles', () => {
99
+ const wrapper = mount(ProgressBar, {
100
+ props: { steps: 5, currentStep: 2 },
101
+ })
102
+
103
+ expect(wrapper.find('[role="progressbar"]').attributes('aria-valuetext')).toBe('Step 3 of 5')
104
+ })
105
+
106
+ it('displays correct step text with titles', () => {
107
+ const stepTitles = ['Start', 'Middle', 'End']
108
+ const wrapper = mount(ProgressBar, {
109
+ props: {
110
+ steps: 3,
111
+ currentStep: 1,
112
+ stepTitles,
113
+ },
114
+ })
115
+
116
+ expect(wrapper.find('[role="progressbar"]').attributes('aria-valuetext')).toBe(
117
+ 'Step 2 of 3: Middle',
118
+ )
119
+ })
120
+
121
+ it('renders step titles and marks current step', () => {
122
+ const stepTitles = ['Step 1', 'Step 2', 'Step 3']
123
+ const wrapper = mount(ProgressBar, {
124
+ props: {
125
+ steps: 3,
126
+ currentStep: 1,
127
+ stepTitles,
128
+ },
129
+ })
130
+
131
+ const items = wrapper.findAll('li')
132
+ expect(items).toHaveLength(3)
133
+ expect(items[1]!.attributes('aria-current')).toBe('step')
134
+ expect(items[1]!.classes()).toContain('text-sapphire-500')
135
+ expect(items[0]!.attributes('aria-current')).toBeUndefined()
136
+ })
137
+
138
+ it('uses default progress label', () => {
139
+ const wrapper = mount(ProgressBar, {
140
+ props: {
141
+ steps: 5,
142
+ currentStep: 0,
143
+ },
144
+ })
145
+ expect(wrapper.find('[role="progressbar"]').attributes('aria-label')).toBe('Voortgang')
146
+ })
147
+
148
+ it('uses custom progress label', () => {
149
+ const wrapper = mount(ProgressBar, {
150
+ props: {
151
+ steps: 5,
152
+ currentStep: 0,
153
+ progressLabel: 'My Progress',
154
+ },
155
+ })
156
+ expect(wrapper.find('[role="progressbar"]').attributes('aria-label')).toBe('My Progress')
157
+ })
158
+
159
+ it('warns when stepTitles length mismatch', () => {
160
+ const spy = vi.spyOn(console, 'warn')
161
+ mount(ProgressBar, {
162
+ props: {
163
+ steps: 5,
164
+ currentStep: 0,
165
+ stepTitles: ['Only one'],
166
+ },
167
+ })
168
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('[NexxtProgressBar]'))
169
+ })
170
+
171
+ it('handles missing title in titles array gracefully', () => {
172
+ const wrapper = mount(ProgressBar, {
173
+ props: {
174
+ steps: 3,
175
+ currentStep: 2, // Last step
176
+ stepTitles: ['Only First'], // Only index 0 exists
177
+ },
178
+ })
179
+ // clampedStep is 2, stepTitles[2] is undefined
180
+ expect(wrapper.find('[role="progressbar"]').attributes('aria-valuetext')).toBe('Step 3 of 3')
181
+ })
182
+
183
+ it('has responsive container query classes', () => {
184
+ const wrapper = mount(ProgressBar, {
185
+ props: {
186
+ steps: 5,
187
+ currentStep: 0,
188
+ stepTitles: ['Step 1', 'Step 2', 'Step 3', 'Step 4', 'Step 5'],
189
+ },
190
+ })
191
+
192
+ // Verify the root is a container
193
+ expect(wrapper.classes()).toContain('@container')
194
+
195
+ // Verify the digit counter hides when the container is large
196
+ const counter = wrapper.find('.tabular-nums')
197
+ expect(counter.classes()).toContain('@md:hidden')
198
+
199
+ // Verify non-current list items hide their titles when the container is small
200
+ const nonCurrentTitle = wrapper.findAll('li')[1]!
201
+ expect(nonCurrentTitle.classes()).toContain('@max-md:hidden')
202
+ })
203
+
204
+ describe('compact mode (many steps)', () => {
205
+ const manyTitles = [
206
+ 'Step 1',
207
+ 'Step 2',
208
+ 'Step 3',
209
+ 'Step 4',
210
+ 'Step 5',
211
+ 'Step 6',
212
+ 'Step 7',
213
+ 'Step 8',
214
+ ]
215
+
216
+ it('collapses the label row and always shows the counter above the threshold', () => {
217
+ const wrapper = mount(ProgressBar, {
218
+ props: { steps: manyTitles.length, currentStep: 3, stepTitles: manyTitles },
219
+ })
220
+
221
+ // Counter is no longer hidden on large containers
222
+ const counter = wrapper.find('.tabular-nums')
223
+ expect(counter.classes()).not.toContain('@md:hidden')
224
+
225
+ // The inline label row is replaced by a dropdown trigger for the current step
226
+ expect(wrapper.find('ol').exists()).toBe(false)
227
+ const trigger = wrapper.find('button[aria-haspopup="menu"]')
228
+ expect(trigger.exists()).toBe(true)
229
+ expect(trigger.text()).toContain('Step 4')
230
+ })
231
+
232
+ it('keeps the full responsive layout at or below the threshold', () => {
233
+ const titles = manyTitles.slice(0, 7)
234
+ const wrapper = mount(ProgressBar, {
235
+ props: { steps: titles.length, currentStep: 0, stepTitles: titles },
236
+ })
237
+
238
+ expect(wrapper.find('.tabular-nums').classes()).toContain('@md:hidden')
239
+ // A non-current step keeps the responsive hide-on-small behavior
240
+ expect(wrapper.findAll('li')[1]!.classes()).toContain('@max-md:hidden')
241
+ })
242
+
243
+ it('respects a custom maxInlineSteps threshold', () => {
244
+ const titles = manyTitles.slice(0, 4)
245
+ const wrapper = mount(ProgressBar, {
246
+ props: { steps: titles.length, currentStep: 0, stepTitles: titles, maxInlineSteps: 3 },
247
+ })
248
+
249
+ expect(wrapper.find('.tabular-nums').classes()).not.toContain('@md:hidden')
250
+ expect(wrapper.find('button[aria-haspopup="menu"]').exists()).toBe(true)
251
+ })
252
+
253
+ it('opens a menu with every step and emits when a previous step is picked', async () => {
254
+ const wrapper = mount(ProgressBar, {
255
+ props: { steps: manyTitles.length, currentStep: 3, stepTitles: manyTitles },
256
+ })
257
+
258
+ await wrapper.find('button[aria-haspopup="menu"]').trigger('click')
259
+ await nextTick()
260
+ await nextTick()
261
+
262
+ const items = wrapper.findAll('[role="menuitem"]')
263
+ expect(items).toHaveLength(manyTitles.length)
264
+ // Steps after the current one cannot be selected
265
+ expect(items[4]!.attributes('disabled')).toBeDefined()
266
+ expect(items[3]!.attributes('aria-current')).toBe('step')
267
+
268
+ await items[1]!.trigger('click')
269
+ expect(wrapper.emitted('update:currentStep')).toEqual([[1]])
270
+ })
271
+
272
+ it('does not emit when the current step is picked from the menu', async () => {
273
+ const wrapper = mount(ProgressBar, {
274
+ props: { steps: manyTitles.length, currentStep: 3, stepTitles: manyTitles },
275
+ })
276
+
277
+ await wrapper.find('button[aria-haspopup="menu"]').trigger('click')
278
+ await nextTick()
279
+ await nextTick()
280
+
281
+ await wrapper.findAll('[role="menuitem"]')[3]!.trigger('click')
282
+ expect(wrapper.emitted('update:currentStep')).toBeUndefined()
283
+ })
284
+ })
285
+
286
+ it('emits update:currentStep when a previous step is clicked', async () => {
287
+ const stepTitles = ['Step 1', 'Step 2', 'Step 3']
288
+ const wrapper = mount(ProgressBar, {
289
+ props: {
290
+ steps: 3,
291
+ currentStep: 2,
292
+ stepTitles,
293
+ },
294
+ })
295
+
296
+ const items = wrapper.findAll('li')
297
+
298
+ // Click first step (index 0, previous)
299
+ await items[0]!.trigger('click')
300
+ expect(wrapper.emitted('update:currentStep')).toBeTruthy()
301
+ expect(wrapper.emitted('update:currentStep')![0]).toEqual([0])
302
+
303
+ // Click second step (index 1, previous)
304
+ await items[1]!.trigger('click')
305
+ expect(wrapper.emitted('update:currentStep')![1]).toEqual([1])
306
+ })
307
+
308
+ it('does not emit update:currentStep when the current or a future step is clicked', async () => {
309
+ const stepTitles = ['Step 1', 'Step 2', 'Step 3']
310
+ const wrapper = mount(ProgressBar, {
311
+ props: {
312
+ steps: 3,
313
+ currentStep: 1,
314
+ stepTitles,
315
+ },
316
+ })
317
+
318
+ const items = wrapper.findAll('li')
319
+
320
+ // Click current step (index 1)
321
+ await items[1]!.trigger('click')
322
+ expect(wrapper.emitted('update:currentStep')).toBeFalsy()
323
+
324
+ // Click future step (index 2)
325
+ await items[2]!.trigger('click')
326
+ expect(wrapper.emitted('update:currentStep')).toBeFalsy()
327
+ })
328
+
329
+ it('applies interactive classes only to previous steps', () => {
330
+ const stepTitles = ['Step 1', 'Step 2', 'Step 3']
331
+ const wrapper = mount(ProgressBar, {
332
+ props: {
333
+ steps: 3,
334
+ currentStep: 1,
335
+ stepTitles,
336
+ },
337
+ })
338
+
339
+ const items = wrapper.findAll('li')
340
+
341
+ // Previous step
342
+ expect(items[0]!.classes()).toContain('cursor-pointer')
343
+ expect(items[0]!.classes()).toContain('hover:text-sapphire-400')
344
+ expect(items[0]!.attributes('role')).toBe('button')
345
+ expect(items[0]!.attributes('tabindex')).toBe('0')
346
+ expect(items[0]!.attributes('aria-label')).toBe('Ga naar Step 1')
347
+
348
+ // Current step
349
+ expect(items[1]!.classes()).not.toContain('cursor-pointer')
350
+ expect(items[1]!.attributes('role')).toBeUndefined()
351
+ expect(items[1]!.attributes('tabindex')).toBeUndefined()
352
+
353
+ // Future step
354
+ expect(items[2]!.classes()).not.toContain('cursor-pointer')
355
+ expect(items[2]!.attributes('role')).toBeUndefined()
356
+ expect(items[2]!.attributes('tabindex')).toBeUndefined()
357
+ })
358
+
359
+ it('emits update:currentStep when Enter or Space is pressed on a previous step', async () => {
360
+ const stepTitles = ['Step 1', 'Step 2', 'Step 3']
361
+ const wrapper = mount(ProgressBar, {
362
+ props: {
363
+ steps: 3,
364
+ currentStep: 2,
365
+ stepTitles,
366
+ },
367
+ })
368
+
369
+ const items = wrapper.findAll('li')
370
+
371
+ // Press Enter on first step
372
+ await items[0]!.trigger('keydown.enter')
373
+ expect(wrapper.emitted('update:currentStep')![0]).toEqual([0])
374
+
375
+ // Press Space on second step
376
+ await items[1]!.trigger('keydown.space')
377
+ expect(wrapper.emitted('update:currentStep')![1]).toEqual([1])
378
+ })
379
+
380
+ describe('percentage variant', () => {
381
+ it('renders a progress bar with the given percentage', () => {
382
+ const wrapper = mount(ProgressBar, {
383
+ props: { variant: 'percentage', steps: 100, currentStep: 75 },
384
+ })
385
+
386
+ const progressbar = wrapper.find('[role="progressbar"]')
387
+ expect(progressbar.attributes('aria-valuenow')).toBe('75')
388
+ expect(progressbar.find('.progress-bar-progress').attributes('style')).toContain('width: 75%')
389
+ })
390
+
391
+ it('shows percentage text', () => {
392
+ const wrapper = mount(ProgressBar, {
393
+ props: { variant: 'percentage', steps: 100, currentStep: 90 },
394
+ })
395
+
396
+ expect(wrapper.find('span[aria-hidden="true"]').text()).toBe('90%')
397
+ })
398
+
399
+ it('does not render stepper elements', () => {
400
+ const wrapper = mount(ProgressBar, {
401
+ props: { variant: 'percentage', steps: 101, currentStep: 50 },
402
+ })
403
+
404
+ expect(wrapper.find('ol').exists()).toBe(false)
405
+ expect(wrapper.find('li').exists()).toBe(false)
406
+ })
407
+
408
+ it('has taller bar than stepper variant', () => {
409
+ const wrapper = mount(ProgressBar, {
410
+ props: { variant: 'percentage', steps: 101, currentStep: 50 },
411
+ })
412
+
413
+ const progressbar = wrapper.find('[role="progressbar"]')
414
+ expect(progressbar.classes()).toContain('h-6')
415
+ })
416
+ })
417
+ })
@@ -0,0 +1,191 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import { ref } from 'vue'
3
+ import ReferenceCard, { type ProposalReference } from './ReferenceCard.vue'
4
+
5
+ const BASE_REFERENCE: ProposalReference = {
6
+ index: 1,
7
+ postalCode: '3811 KV',
8
+ streetName: 'Amersfoortsestraat',
9
+ houseNumber: 12,
10
+ houseType: 'Tussenwoning',
11
+ city: 'Amersfoort',
12
+ mainImageUrl: 'https://images.unsplash.com/photo-1568605114967-8130f3a36994?w=800',
13
+ latitude: 52.1561,
14
+ longitude: 5.3878,
15
+ useSurface: 124,
16
+ parcelSurface: 210,
17
+ buildYear: 1998,
18
+ volume: 420,
19
+ energyLabel: 'A',
20
+ garage: 'Aangebouwde stenen garage',
21
+ insulation: 'Volledig geïsoleerd',
22
+ shedStorageType: 'Vrijstaand houten berging',
23
+ saleDate: '2025-03-14',
24
+ dateOnOfferSince: '2025-01-08',
25
+ transportDate: '2025-05-01',
26
+ salePrice: '485000',
27
+ indexedSalePrice: '502000',
28
+ pricePerSquareMeter: '3911',
29
+ }
30
+
31
+ export default {
32
+ title: 'Components/Organisms/Reference Card',
33
+ component: ReferenceCard,
34
+ } satisfies Meta<typeof ReferenceCard>
35
+
36
+ type Story = StoryObj<typeof ReferenceCard>
37
+
38
+ const render: Story['render'] = (args) => ({
39
+ components: { ReferenceCard },
40
+ setup() {
41
+ const selected = ref(args.modelValue ?? false)
42
+ return { args, selected }
43
+ },
44
+ template: '<ReferenceCard v-bind="args" v-model="selected" />',
45
+ })
46
+
47
+ export const Default: Story = {
48
+ args: { reference: BASE_REFERENCE },
49
+ render,
50
+ }
51
+
52
+ export const Selected: Story = {
53
+ args: { reference: BASE_REFERENCE, modelValue: true },
54
+ render,
55
+ }
56
+
57
+ export const NarrowContainer: Story = {
58
+ args: { reference: BASE_REFERENCE },
59
+ render: (args) => ({
60
+ components: { ReferenceCard },
61
+ setup() {
62
+ const selected = ref(args.modelValue ?? false)
63
+ return { args, selected }
64
+ },
65
+ template: `
66
+ <div class="w-80">
67
+ <ReferenceCard v-bind="args" v-model="selected" />
68
+ </div>
69
+ `,
70
+ }),
71
+ }
72
+
73
+ export const CustomCurrency: Story = {
74
+ args: {
75
+ reference: BASE_REFERENCE,
76
+ locale: 'en-US',
77
+ currency: 'USD',
78
+ },
79
+ render,
80
+ }
81
+
82
+ export const CustomMessages: Story = {
83
+ args: {
84
+ reference: BASE_REFERENCE,
85
+ messages: {
86
+ selected: 'Included in advice',
87
+ unselected: 'Select this property',
88
+ details: 'More details',
89
+ energyLabel: 'Energy label',
90
+ },
91
+ },
92
+ render,
93
+ }
94
+
95
+ export const List: Story = {
96
+ args: { reference: BASE_REFERENCE },
97
+ render: (args) => ({
98
+ components: { ReferenceCard },
99
+ setup() {
100
+ const references = [
101
+ BASE_REFERENCE,
102
+ {
103
+ ...BASE_REFERENCE,
104
+ index: 2,
105
+ streetName: 'Klaverkamp',
106
+ houseNumber: 22,
107
+ postalCode: '7905 BK',
108
+ city: 'Zwolle',
109
+ energyLabel: 'C',
110
+ salePrice: '465000',
111
+ saleDate: '2024-03-18',
112
+ },
113
+ ]
114
+ const selected = ref([true, false])
115
+ return { args, references, selected }
116
+ },
117
+ template: `
118
+ <div class="flex flex-col gap-4">
119
+ <ReferenceCard
120
+ v-for="(reference, index) in references"
121
+ :key="reference.index"
122
+ v-bind="args"
123
+ v-model="selected[index]"
124
+ :reference="reference"
125
+ />
126
+ </div>
127
+ `,
128
+ }),
129
+ }
130
+
131
+ export const MillionPlusPrice: Story = {
132
+ args: {
133
+ reference: {
134
+ ...BASE_REFERENCE,
135
+ salePrice: '1485000',
136
+ indexedSalePrice: '1602000',
137
+ pricePerSquareMeter: '11975',
138
+ },
139
+ },
140
+ render,
141
+ }
142
+
143
+ export const WithoutImage: Story = {
144
+ args: {
145
+ reference: { ...BASE_REFERENCE, mainImageUrl: null },
146
+ },
147
+ render,
148
+ }
149
+
150
+ export const WithoutPrice: Story = {
151
+ args: {
152
+ reference: { ...BASE_REFERENCE, salePrice: null },
153
+ },
154
+ render,
155
+ }
156
+
157
+ export const MinimalData: Story = {
158
+ args: {
159
+ reference: {
160
+ ...BASE_REFERENCE,
161
+ mainImageUrl: null,
162
+ useSurface: null,
163
+ parcelSurface: null,
164
+ buildYear: null,
165
+ volume: null,
166
+ energyLabel: null,
167
+ garage: null,
168
+ insulation: null,
169
+ shedStorageType: null,
170
+ saleDate: null,
171
+ dateOnOfferSince: null,
172
+ transportDate: null,
173
+ salePrice: null,
174
+ indexedSalePrice: null,
175
+ pricePerSquareMeter: null,
176
+ },
177
+ },
178
+ render,
179
+ }
180
+
181
+ export const OverflowingText: Story = {
182
+ args: {
183
+ reference: {
184
+ ...BASE_REFERENCE,
185
+ streetName: 'Burgemeester van Nispen tot Sevenaerstraat-Zuidzijde',
186
+ city: 'Sint-Annaparochie aan de Rijksstraatweg',
187
+ houseType: 'Vrijstaande woning met aangebouwd bedrijfspand en dubbele garage',
188
+ },
189
+ },
190
+ render,
191
+ }