@nexxtmove/ui 2.6.0 → 2.8.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 (229) hide show
  1. package/dist/components/editor/molecules/EditorColor/EditorColor.vue.d.ts +7 -5
  2. package/dist/components/editor/molecules/EditorColor/_ColorField.vue.d.ts +4 -1
  3. package/dist/components/organisms/ColorPicker/ColorPicker.vue.d.ts +3 -1
  4. package/dist/index.js +95 -172
  5. package/dist/nuxt.js +0 -1
  6. package/package.json +38 -36
  7. package/src/attribute-contract.test.ts +151 -0
  8. package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
  9. package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
  10. package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
  11. package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
  12. package/src/components/atoms/Button/Button.stories.ts +290 -0
  13. package/src/components/atoms/Button/Button.test.ts +406 -0
  14. package/src/components/atoms/Card/Card.stories.ts +45 -0
  15. package/src/components/atoms/Card/Card.test.ts +46 -0
  16. package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
  17. package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
  18. package/src/components/atoms/Chip/Chip.stories.ts +274 -0
  19. package/src/components/atoms/Chip/Chip.test.ts +230 -0
  20. package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
  21. package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
  22. package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
  23. package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
  24. package/src/components/atoms/Icon/Icon.stories.ts +63 -0
  25. package/src/components/atoms/Icon/Icon.test.ts +89 -0
  26. package/src/components/atoms/Loader/Loader.stories.ts +36 -0
  27. package/src/components/atoms/Loader/Loader.test.ts +40 -0
  28. package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
  29. package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
  30. package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
  31. package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
  32. package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
  33. package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
  34. package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
  35. package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
  36. package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
  37. package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
  38. package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
  39. package/src/components/atoms/Slider/Slider.stories.ts +151 -0
  40. package/src/components/atoms/Slider/Slider.test.ts +191 -0
  41. package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
  42. package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
  43. package/src/components/atoms/Switch/Switch.stories.ts +91 -0
  44. package/src/components/atoms/Switch/Switch.test.ts +183 -0
  45. package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
  46. package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
  47. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
  48. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
  49. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
  50. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
  51. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
  52. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
  53. package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
  54. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
  55. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
  56. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
  57. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
  58. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
  59. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
  60. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
  61. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
  62. package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
  63. package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
  64. package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
  65. package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
  66. package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +633 -0
  67. package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +776 -0
  68. package/src/components/editor/molecules/EditorColor/EditorColor.vue +7 -5
  69. package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +832 -0
  70. package/src/components/editor/molecules/EditorColor/_ColorField.vue +20 -15
  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 +696 -0
  182. package/src/components/organisms/ColorPicker/ColorPicker.vue +12 -3
  183. package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
  184. package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
  185. package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
  186. package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
  187. package/src/components/organisms/Combobox/Combobox.vue +1 -1
  188. package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
  189. package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
  190. package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
  191. package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
  192. package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
  193. package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
  194. package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
  195. package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
  196. package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
  197. package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
  198. package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
  199. package/src/components/organisms/Header/Header.stories.ts +75 -0
  200. package/src/components/organisms/Header/Header.test.ts +233 -0
  201. package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
  202. package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
  203. package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
  204. package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
  205. package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
  206. package/src/components/organisms/Modal/Modal.stories.ts +350 -0
  207. package/src/components/organisms/Modal/Modal.test.ts +275 -0
  208. package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
  209. package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
  210. package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
  211. package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
  212. package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
  213. package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
  214. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
  215. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
  216. package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
  217. package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
  218. package/src/components/organisms/Table/Table.stories.ts +332 -0
  219. package/src/components/organisms/Table/Table.test.ts +320 -0
  220. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
  221. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
  222. package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
  223. package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
  224. package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
  225. package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
  226. package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
  227. package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
  228. package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
  229. package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
@@ -0,0 +1,63 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import Icon, { type IconType } from './Icon.vue'
3
+
4
+ export default {
5
+ title: 'Components/Atoms/Icon',
6
+ component: Icon,
7
+ // icons size with the surrounding font-size, so scale the canvas up to see them
8
+ decorators: [() => ({ template: '<div class="text-5xl"><story /></div>' })],
9
+ argTypes: {
10
+ type: {
11
+ control: 'select',
12
+ options: ['light', 'regular', 'solid', 'duotone'] satisfies IconType[],
13
+ },
14
+ name: {
15
+ // Font Awesome ships ~4300 names; this is a browsable sample plus the local SVG icons.
16
+ // Any kit icon name is valid — type one in the control to use it.
17
+ control: 'select',
18
+ options: ['star', 'house', 'check', 'arrows-up-down', 'github', 'whatsapp', 'funda'],
19
+ },
20
+ },
21
+ } satisfies Meta<typeof Icon>
22
+
23
+ type Story = StoryObj<typeof Icon>
24
+
25
+ export const Regular: Story = {
26
+ args: {
27
+ name: 'star',
28
+ type: 'regular',
29
+ },
30
+ }
31
+
32
+ export const Light: Story = {
33
+ args: {
34
+ name: 'star',
35
+ type: 'light',
36
+ },
37
+ }
38
+
39
+ export const Solid: Story = {
40
+ args: {
41
+ name: 'star',
42
+ type: 'solid',
43
+ },
44
+ }
45
+
46
+ export const Duotone: Story = {
47
+ args: {
48
+ name: 'star',
49
+ type: 'duotone',
50
+ },
51
+ }
52
+
53
+ export const Brands: Story = {
54
+ args: {
55
+ name: 'github',
56
+ },
57
+ }
58
+
59
+ export const Funda: Story = {
60
+ args: {
61
+ name: 'funda',
62
+ },
63
+ }
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Icon from './Icon.vue'
4
+
5
+ describe('Icon', () => {
6
+ it('renders as an <i> tag', () => {
7
+ const wrapper = mount(Icon, {
8
+ props: {
9
+ name: 'star',
10
+ },
11
+ })
12
+
13
+ expect(wrapper.element.tagName).toBe('I')
14
+ })
15
+
16
+ it('renders correctly with name', async () => {
17
+ const wrapper = mount(Icon, {
18
+ props: {
19
+ name: 'star',
20
+ },
21
+ })
22
+
23
+ expect(wrapper.classes()).toContain('fa-star')
24
+
25
+ await wrapper.setProps({ name: 'arrows-up-down' })
26
+
27
+ expect(wrapper.classes()).toContain('fa-arrows-up-down')
28
+ })
29
+
30
+ it('applies default type "light"', () => {
31
+ const wrapper = mount(Icon, {
32
+ props: {
33
+ name: 'star',
34
+ },
35
+ })
36
+
37
+ expect(wrapper.classes()).toContain('fa-light')
38
+ })
39
+
40
+ it('applies specified type', async () => {
41
+ const wrapper = mount(Icon, {
42
+ props: {
43
+ name: 'star',
44
+ type: 'regular',
45
+ },
46
+ })
47
+
48
+ expect(wrapper.classes()).toContain('fa-regular')
49
+
50
+ await wrapper.setProps({ type: 'light' })
51
+ expect(wrapper.classes()).toContain('fa-light')
52
+
53
+ await wrapper.setProps({ type: 'solid' })
54
+ expect(wrapper.classes()).toContain('fa-solid')
55
+
56
+ await wrapper.setProps({ type: 'duotone' })
57
+ expect(wrapper.classes()).toContain('fa-duotone')
58
+ })
59
+
60
+ it('applies fa-brands automatically for brand icon names', async () => {
61
+ const wrapper = mount(Icon, { props: { name: 'whatsapp' } })
62
+ expect(wrapper.classes()).toContain('fa-brands')
63
+ expect(wrapper.classes()).not.toContain('fa-light')
64
+
65
+ await wrapper.setProps({ name: 'house' })
66
+ expect(wrapper.classes()).toContain('fa-light')
67
+ expect(wrapper.classes()).not.toContain('fa-brands')
68
+ })
69
+
70
+ it('renders a local svg for custom brand icons', () => {
71
+ const wrapper = mount(Icon, { props: { name: 'funda' } })
72
+
73
+ expect(wrapper.element.tagName).toBe('svg')
74
+ expect(wrapper.classes()).toContain('size-[1em]')
75
+ expect(wrapper.classes()).toContain('fill-current')
76
+ expect(wrapper.find('i').exists()).toBe(false)
77
+ })
78
+
79
+ it('applies basic layout classes', () => {
80
+ const wrapper = mount(Icon, {
81
+ props: {
82
+ name: 'star',
83
+ },
84
+ })
85
+
86
+ expect(wrapper.classes()).toContain('items-center')
87
+ expect(wrapper.classes()).toContain('justify-center')
88
+ })
89
+ })
@@ -0,0 +1,36 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import Loader from './Loader.vue'
3
+
4
+ export default {
5
+ title: 'Components/Atoms/Loader',
6
+ component: Loader,
7
+ } satisfies Meta<typeof Loader>
8
+
9
+ type Story = StoryObj<typeof Loader>
10
+
11
+ export const Default: Story = {}
12
+
13
+ /** A size class on the root overrides the 96px default. */
14
+ export const Sizes: Story = {
15
+ render: () => ({
16
+ components: { Loader },
17
+ setup: () => ({ sizes: ['size-8', 'size-12', 'size-24', 'size-40'] }),
18
+ template: `
19
+ <div class="flex items-end gap-8">
20
+ <Loader v-for="size in sizes" :key="size" :class="size" />
21
+ </div>
22
+ `,
23
+ }),
24
+ }
25
+
26
+ /** Filling its container, the way a page shows it while its data loads. */
27
+ export const FullArea: Story = {
28
+ render: () => ({
29
+ components: { Loader },
30
+ template: `
31
+ <div class="flex h-64 w-96 items-center justify-center rounded-xl border border-gray-200">
32
+ <Loader />
33
+ </div>
34
+ `,
35
+ }),
36
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Loader from './Loader.vue'
4
+
5
+ describe('Loader', () => {
6
+ it('renders an svg as its root', () => {
7
+ const wrapper = mount(Loader)
8
+
9
+ expect(wrapper.element.tagName).toBe('svg')
10
+ })
11
+
12
+ it('renders the eight logo blocks', () => {
13
+ const wrapper = mount(Loader)
14
+
15
+ expect(wrapper.findAll('rect')).toHaveLength(8)
16
+ })
17
+
18
+ it('announces itself as a status with a Dutch default label', () => {
19
+ const wrapper = mount(Loader)
20
+
21
+ expect(wrapper.attributes('role')).toBe('status')
22
+ expect(wrapper.attributes('aria-label')).toBe('Bezig met laden')
23
+ })
24
+
25
+ it('takes a custom label', () => {
26
+ const wrapper = mount(Loader, { props: { label: 'Voorstel wordt opgebouwd' } })
27
+
28
+ expect(wrapper.attributes('aria-label')).toBe('Voorstel wordt opgebouwd')
29
+ })
30
+
31
+ it('sizes itself with presentation attributes so a size class always wins', () => {
32
+ const wrapper = mount(Loader, { attrs: { class: 'size-12' } })
33
+
34
+ // width/height attributes lose to any CSS rule, so the consumer's size
35
+ // class overrides the default regardless of Tailwind's output order.
36
+ expect(wrapper.attributes('width')).toBe('96')
37
+ expect(wrapper.attributes('height')).toBe('96')
38
+ expect(wrapper.classes()).toEqual(['size-12'])
39
+ })
40
+ })
@@ -0,0 +1,172 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import { expect, fn, userEvent, waitFor, within } from 'storybook/test'
3
+ import PickerItem from './PickerItem.vue'
4
+
5
+ /**
6
+ * An option needs a listbox parent: without it axe reports `aria-required-parent`, and the ring
7
+ * of a highlighted item only shows inside a focused `group/listbox`.
8
+ */
9
+ const LISTBOX_CLASSES =
10
+ 'group/listbox flex w-66 flex-col gap-0.5 rounded-xl border border-gray-200 bg-white p-2 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cornflower-blue-500'
11
+
12
+ export default {
13
+ title: 'Components/Atoms/PickerItem',
14
+ component: PickerItem,
15
+ args: {
16
+ id: 'picker-item-demo',
17
+ label: 'Afbeelding',
18
+ icon: 'image',
19
+ highlighted: false,
20
+ disabled: false,
21
+ description: 'Een foto of illustratie over de volle breedte.',
22
+ onSelect: fn(),
23
+ onHighlight: fn(),
24
+ },
25
+ render: (args) => ({
26
+ components: { PickerItem },
27
+ setup: () => ({ args, classes: LISTBOX_CLASSES }),
28
+ template: `
29
+ <div role="listbox" aria-label="Voorbeeld" tabindex="0" :class="classes">
30
+ <PickerItem v-bind="args" />
31
+ </div>
32
+ `,
33
+ }),
34
+ } satisfies Meta<typeof PickerItem>
35
+
36
+ type Story = StoryObj<typeof PickerItem>
37
+
38
+ export const Playground: Story = {}
39
+
40
+ /**
41
+ * Every state side by side. Active is the pressed state (`active:`), so press and hold an
42
+ * enabled row to see it; the ring on the highlighted row appears once the list has keyboard
43
+ * focus (Tab into it), see "Focused listbox".
44
+ */
45
+ export const States: Story = {
46
+ render: (args) => ({
47
+ components: { PickerItem },
48
+ setup: () => ({ args, classes: LISTBOX_CLASSES }),
49
+ template: `
50
+ <div role="listbox" aria-label="Staten" tabindex="0" :class="classes">
51
+ <PickerItem id="state-default" label="Standaard" icon="image" @select="args.onSelect" />
52
+ <PickerItem id="state-highlighted" label="Gemarkeerd" icon="heading" highlighted />
53
+ <PickerItem
54
+ id="state-disabled"
55
+ label="Uitgeschakeld"
56
+ icon="table"
57
+ disabled
58
+ description="Nog niet beschikbaar."
59
+ @select="args.onSelect"
60
+ />
61
+ <PickerItem
62
+ id="state-disabled-highlighted"
63
+ label="Uitgeschakeld en gemarkeerd"
64
+ icon="table"
65
+ disabled
66
+ highlighted
67
+ description="Nog niet beschikbaar."
68
+ />
69
+ <PickerItem
70
+ id="state-long"
71
+ label="Een heel lang label dat niet in de kolom past en wordt afgekapt"
72
+ icon="font"
73
+ highlighted
74
+ />
75
+ <PickerItem id="state-no-icon" label="Zonder icoon" />
76
+ <PickerItem id="state-slot" label="Met eigen inhoud">
77
+ <span class="flex size-5 shrink-0 items-center justify-center rounded-md bg-gray-100 text-gray-700 extra-small-semibold">
78
+ A
79
+ </span>
80
+ <span class="min-w-0 flex-1 truncate small-normal text-gray-900">Met eigen inhoud</span>
81
+ </PickerItem>
82
+ </div>
83
+ `,
84
+ }),
85
+ play: async ({ args, canvasElement }) => {
86
+ const options = Array.from(canvasElement.querySelectorAll<HTMLElement>('[role="option"]'))
87
+
88
+ // one option per item, each with its own id and a selected state that follows `highlighted`
89
+ await expect(options.map((option) => option.id)).toEqual([
90
+ 'state-default',
91
+ 'state-highlighted',
92
+ 'state-disabled',
93
+ 'state-disabled-highlighted',
94
+ 'state-long',
95
+ 'state-no-icon',
96
+ 'state-slot',
97
+ ])
98
+ await expect(options[0]).toHaveAttribute('aria-selected', 'false')
99
+ await expect(options[1]).toHaveAttribute('aria-selected', 'true')
100
+
101
+ // nothing inside an option can take focus
102
+ await expect(
103
+ canvasElement.querySelectorAll('[role="option"] :is(button, a, input, [tabindex])'),
104
+ ).toHaveLength(0)
105
+
106
+ // the description is reachable through aria-describedby, and hidden on screen
107
+ const disabled = options[2]
108
+ const description = document.getElementById(disabled.getAttribute('aria-describedby')!)!
109
+ await expect(description).toHaveTextContent('Nog niet beschikbaar.')
110
+ await expect(description).not.toBeVisible()
111
+ await expect(disabled).toHaveAttribute('aria-disabled', 'true')
112
+
113
+ // a disabled option swallows the click
114
+ await userEvent.click(disabled)
115
+ await expect(args.onSelect).not.toHaveBeenCalled()
116
+ await userEvent.click(options[0])
117
+ await expect(args.onSelect).toHaveBeenCalledOnce()
118
+
119
+ // the long label truncates instead of pushing the row wider
120
+ const longLabel = options[4].querySelector<HTMLElement>('span:last-of-type')!
121
+ await expect(longLabel.scrollWidth).toBeGreaterThan(longLabel.clientWidth)
122
+ await expect(options[4].scrollWidth).toBe(options[4].clientWidth)
123
+ },
124
+ }
125
+
126
+ /** Tab into the list: the highlighted row gets an inset ring on top of its tint. */
127
+ export const FocusedListbox: Story = {
128
+ render: () => ({
129
+ components: { PickerItem },
130
+ setup: () => ({ classes: LISTBOX_CLASSES }),
131
+ template: `
132
+ <div role="listbox" aria-label="Gefocuste lijst" tabindex="0" :class="classes">
133
+ <PickerItem id="focus-a" label="Titel" icon="heading" />
134
+ <PickerItem id="focus-b" label="Afbeelding" icon="image" highlighted />
135
+ <PickerItem id="focus-c" label="Tekst" icon="font" />
136
+ </div>
137
+ `,
138
+ }),
139
+ play: async ({ canvasElement }) => {
140
+ const highlighted = canvasElement.querySelector<HTMLElement>('#focus-b')!
141
+ const ringless = getComputedStyle(highlighted).boxShadow
142
+
143
+ await userEvent.tab()
144
+ await expect(within(canvasElement).getByRole('listbox')).toHaveFocus()
145
+
146
+ await waitFor(() => expect(getComputedStyle(highlighted).boxShadow).not.toBe(ringless))
147
+ // only the highlighted row is ringed
148
+ await expect(getComputedStyle(canvasElement.querySelector('#focus-a')!).boxShadow).toBe(
149
+ ringless,
150
+ )
151
+ },
152
+ }
153
+
154
+ /**
155
+ * Hover tints an enabled row and leaves a highlighted or disabled row as it is. Point at the rows
156
+ * to check. Deliberately no real-pointer play: a Playwright hover into the story iframe hangs for
157
+ * its full timeout in the mixed `pnpm test` run, which is why Tree's `HoveringARow` lost its play
158
+ * too. The class rules behind it are covered in PickerItem.test.ts.
159
+ */
160
+ export const Hover: Story = {
161
+ render: () => ({
162
+ components: { PickerItem },
163
+ setup: () => ({ classes: LISTBOX_CLASSES }),
164
+ template: `
165
+ <div role="listbox" aria-label="Hover" tabindex="0" :class="classes">
166
+ <PickerItem id="hover-plain" label="Standaard" icon="image" />
167
+ <PickerItem id="hover-highlighted" label="Gemarkeerd" icon="heading" highlighted />
168
+ <PickerItem id="hover-disabled" label="Uitgeschakeld" icon="table" disabled />
169
+ </div>
170
+ `,
171
+ }),
172
+ }
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import PickerItem from './PickerItem.vue'
4
+
5
+ const props = { id: 'opt-image', label: 'Afbeelding', icon: 'image' as const }
6
+
7
+ // No assertions on typography utilities: elements are found by structure or text, so a restyle
8
+ // of the label never turns these red.
9
+ describe('PickerItem', () => {
10
+ it('renders one option with the given id, not selected by default', () => {
11
+ const wrapper = mount(PickerItem, { props })
12
+
13
+ expect(wrapper.attributes('role')).toBe('option')
14
+ expect(wrapper.attributes('id')).toBe('opt-image')
15
+ expect(wrapper.attributes('aria-selected')).toBe('false')
16
+ expect(wrapper.text()).toBe('Afbeelding')
17
+ })
18
+
19
+ it('marks a highlighted item as selected', () => {
20
+ const wrapper = mount(PickerItem, { props: { ...props, highlighted: true } })
21
+
22
+ expect(wrapper.attributes('aria-selected')).toBe('true')
23
+ })
24
+
25
+ it('contains nothing focusable', () => {
26
+ const wrapper = mount(PickerItem, { props: { ...props, description: 'Uitleg' } })
27
+
28
+ expect(wrapper.find('button, a, input, [tabindex]').exists()).toBe(false)
29
+ expect(wrapper.attributes('tabindex')).toBeUndefined()
30
+ })
31
+
32
+ it('hides the icon from assistive tech', () => {
33
+ const wrapper = mount(PickerItem, { props })
34
+
35
+ expect(wrapper.find('i').attributes('aria-hidden')).toBe('true')
36
+ })
37
+
38
+ it('renders no icon wrapper without an icon', () => {
39
+ const wrapper = mount(PickerItem, { props: { id: 'x', label: 'Zonder icoon' } })
40
+
41
+ expect(wrapper.find('i').exists()).toBe(false)
42
+ expect(wrapper.findAll('span')).toHaveLength(1)
43
+ })
44
+
45
+ it('emits select on click', async () => {
46
+ const wrapper = mount(PickerItem, { props })
47
+
48
+ await wrapper.trigger('click')
49
+
50
+ expect(wrapper.emitted('select')).toEqual([[]])
51
+ })
52
+
53
+ it('never emits select while disabled, and drops the hover tint', async () => {
54
+ const wrapper = mount(PickerItem, { props: { ...props, disabled: true } })
55
+
56
+ await wrapper.trigger('click')
57
+
58
+ expect(wrapper.emitted('select')).toBeUndefined()
59
+ expect(wrapper.attributes('aria-disabled')).toBe('true')
60
+ expect(wrapper.classes().some((name) => name.startsWith('hover:'))).toBe(false)
61
+ })
62
+
63
+ it('has no aria-disabled while enabled', () => {
64
+ const wrapper = mount(PickerItem, { props })
65
+
66
+ expect(wrapper.attributes('aria-disabled')).toBeUndefined()
67
+ })
68
+
69
+ it('lets the highlighted tint win from the hover tint', () => {
70
+ const wrapper = mount(PickerItem, { props: { ...props, highlighted: true } })
71
+
72
+ expect(wrapper.classes()).toContain('bg-cornflower-blue-50')
73
+ expect(wrapper.classes().some((name) => name.startsWith('hover:'))).toBe(false)
74
+ })
75
+
76
+ it('keeps raw font utilities off the label', () => {
77
+ for (const highlighted of [false, true]) {
78
+ const wrapper = mount(PickerItem, { props: { ...props, highlighted } })
79
+ const label = wrapper.findAll('span').at(-1)!
80
+
81
+ expect(label.text()).toBe('Afbeelding')
82
+ expect(label.classes().join(' ')).not.toMatch(/\b(text-(xs|sm|base|lg)|font-\w+)\b/)
83
+ }
84
+ })
85
+
86
+ it('styles the label the same in every state, so it never reflows when highlighted', () => {
87
+ const labelClasses = (extra: Record<string, boolean>) =>
88
+ mount(PickerItem, { props: { ...props, ...extra } })
89
+ .findAll('span')
90
+ .at(-1)!
91
+ .classes()
92
+
93
+ const resting = labelClasses({})
94
+ expect(labelClasses({ highlighted: true })).toEqual(resting)
95
+ expect(labelClasses({ disabled: true, highlighted: true })).toEqual(resting)
96
+ })
97
+
98
+ it('emits highlight on pointer movement, also while disabled', async () => {
99
+ const wrapper = mount(PickerItem, { props: { ...props, disabled: true } })
100
+
101
+ await wrapper.trigger('pointermove')
102
+
103
+ expect(wrapper.emitted('highlight')).toEqual([[]])
104
+ })
105
+
106
+ it('prevents the mousedown default so focus stays where it was', async () => {
107
+ const wrapper = mount(PickerItem, { props })
108
+ const event = new MouseEvent('mousedown', { bubbles: true, cancelable: true })
109
+
110
+ wrapper.element.dispatchEvent(event)
111
+
112
+ expect(event.defaultPrevented).toBe(true)
113
+ })
114
+
115
+ it('points aria-describedby at a hidden span with the description', () => {
116
+ const wrapper = mount(PickerItem, { props: { ...props, description: 'Een foto of beeld' } })
117
+ const describedBy = wrapper.attributes('aria-describedby')!
118
+ const description = wrapper.find(`[id="${describedBy}"]`)
119
+
120
+ expect(description.text()).toBe('Een foto of beeld')
121
+ expect(description.attributes('hidden')).toBeDefined()
122
+ expect(wrapper.element.contains(description.element)).toBe(true)
123
+ })
124
+
125
+ it('has no aria-describedby without a description', () => {
126
+ const wrapper = mount(PickerItem, { props })
127
+
128
+ expect(wrapper.attributes('aria-describedby')).toBeUndefined()
129
+ })
130
+
131
+ it('replaces icon and label with the default slot, keeping the option', () => {
132
+ const wrapper = mount(PickerItem, {
133
+ props: { ...props, highlighted: true },
134
+ slots: { default: '<strong>Eigen inhoud</strong>' },
135
+ })
136
+
137
+ expect(wrapper.attributes('role')).toBe('option')
138
+ expect(wrapper.attributes('aria-selected')).toBe('true')
139
+ expect(wrapper.find('strong').text()).toBe('Eigen inhoud')
140
+ expect(wrapper.find('i').exists()).toBe(false)
141
+ })
142
+ })