@nexxtmove/ui 2.6.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 (223) hide show
  1. package/dist/index.js +49 -126
  2. package/dist/nuxt.js +0 -1
  3. package/package.json +38 -36
  4. package/src/attribute-contract.test.ts +151 -0
  5. package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
  6. package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
  7. package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
  8. package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
  9. package/src/components/atoms/Button/Button.stories.ts +290 -0
  10. package/src/components/atoms/Button/Button.test.ts +406 -0
  11. package/src/components/atoms/Card/Card.stories.ts +45 -0
  12. package/src/components/atoms/Card/Card.test.ts +46 -0
  13. package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
  14. package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
  15. package/src/components/atoms/Chip/Chip.stories.ts +274 -0
  16. package/src/components/atoms/Chip/Chip.test.ts +230 -0
  17. package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
  18. package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
  19. package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
  20. package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
  21. package/src/components/atoms/Icon/Icon.stories.ts +63 -0
  22. package/src/components/atoms/Icon/Icon.test.ts +89 -0
  23. package/src/components/atoms/Loader/Loader.stories.ts +36 -0
  24. package/src/components/atoms/Loader/Loader.test.ts +40 -0
  25. package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
  26. package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
  27. package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
  28. package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
  29. package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
  30. package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
  31. package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
  32. package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
  33. package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
  34. package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
  35. package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
  36. package/src/components/atoms/Slider/Slider.stories.ts +151 -0
  37. package/src/components/atoms/Slider/Slider.test.ts +191 -0
  38. package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
  39. package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
  40. package/src/components/atoms/Switch/Switch.stories.ts +91 -0
  41. package/src/components/atoms/Switch/Switch.test.ts +183 -0
  42. package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
  43. package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
  44. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
  45. package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
  46. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
  47. package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
  48. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
  49. package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
  50. package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
  51. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
  52. package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
  53. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
  54. package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
  55. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
  56. package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
  57. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
  58. package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
  59. package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
  60. package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
  61. package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
  62. package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
  63. package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +563 -0
  64. package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +689 -0
  65. package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +793 -0
  66. package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
  67. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
  68. package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
  69. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
  70. package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
  71. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
  72. package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
  73. package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
  74. package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
  75. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
  76. package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
  77. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
  78. package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
  79. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
  80. package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
  81. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
  82. package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
  83. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
  84. package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
  85. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
  86. package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
  87. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
  88. package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
  89. package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
  90. package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
  91. package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
  92. package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
  93. package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
  94. package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
  95. package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
  96. package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
  97. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
  98. package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
  99. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
  100. package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
  101. package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
  102. package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
  103. package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
  104. package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
  105. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
  106. package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
  107. package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
  108. package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
  109. package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
  110. package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
  111. package/src/components/molecules/InputField/InputField.stories.ts +117 -0
  112. package/src/components/molecules/InputField/InputField.test.ts +138 -0
  113. package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
  114. package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
  115. package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
  116. package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
  117. package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
  118. package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
  119. package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
  120. package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
  121. package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
  122. package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
  123. package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
  124. package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
  125. package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
  126. package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
  127. package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
  128. package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
  129. package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
  130. package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
  131. package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
  132. package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
  133. package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
  134. package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
  135. package/src/components/molecules/Select/Select.stories.ts +264 -0
  136. package/src/components/molecules/Select/Select.test.ts +315 -0
  137. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
  138. package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
  139. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
  140. package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
  141. package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
  142. package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
  143. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
  144. package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
  145. package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
  146. package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
  147. package/src/components/molecules/Source/Source.stories.ts +35 -0
  148. package/src/components/molecules/Source/Source.test.ts +45 -0
  149. package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
  150. package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
  151. package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
  152. package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
  153. package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
  154. package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
  155. package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
  156. package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
  157. package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
  158. package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
  159. package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
  160. package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
  161. package/src/components/molecules/Toast/Toast.stories.ts +110 -0
  162. package/src/components/molecules/Toast/Toast.test.ts +103 -0
  163. package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
  164. package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
  165. package/src/components/molecules/Tree/Tree.stories.ts +884 -0
  166. package/src/components/molecules/Tree/Tree.test.ts +1247 -0
  167. package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
  168. package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
  169. package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
  170. package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
  171. package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
  172. package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
  173. package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
  174. package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
  175. package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
  176. package/src/components/organisms/ColorPicker/ColorPicker.test.ts +638 -0
  177. package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
  178. package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
  179. package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
  180. package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
  181. package/src/components/organisms/Combobox/Combobox.vue +1 -1
  182. package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
  183. package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
  184. package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
  185. package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
  186. package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
  187. package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
  188. package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
  189. package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
  190. package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
  191. package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
  192. package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
  193. package/src/components/organisms/Header/Header.stories.ts +75 -0
  194. package/src/components/organisms/Header/Header.test.ts +233 -0
  195. package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
  196. package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
  197. package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
  198. package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
  199. package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
  200. package/src/components/organisms/Modal/Modal.stories.ts +350 -0
  201. package/src/components/organisms/Modal/Modal.test.ts +275 -0
  202. package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
  203. package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
  204. package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
  205. package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
  206. package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
  207. package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
  208. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
  209. package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
  210. package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
  211. package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
  212. package/src/components/organisms/Table/Table.stories.ts +332 -0
  213. package/src/components/organisms/Table/Table.test.ts +320 -0
  214. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
  215. package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
  216. package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
  217. package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
  218. package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
  219. package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
  220. package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
  221. package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
  222. package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
  223. package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
@@ -0,0 +1,365 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import { ref } from 'vue'
3
+ import { expect, userEvent, waitFor, within } from 'storybook/test'
4
+ import EditorSettingsGroup from './EditorSettingsGroup.vue'
5
+ import EditorSettingsBlock from '../EditorSettingsBlock/EditorSettingsBlock.vue'
6
+ import EditorPadding from '../../molecules/EditorPadding/EditorPadding.vue'
7
+ import EditorLineStyle from '../../molecules/EditorLineStyle/EditorLineStyle.vue'
8
+ import EditorJustifyContent from '../../molecules/EditorJustifyContent/EditorJustifyContent.vue'
9
+ import type { EditorLineStyleValue } from '../../molecules/EditorLineStyle/EditorLineStyle.vue'
10
+ import type { EditorJustifyContentValue } from '../../molecules/EditorJustifyContent/EditorJustifyContent.vue'
11
+
12
+ export default {
13
+ title: 'Components/Editor Components/Atoms/Editor Settings Group',
14
+ component: EditorSettingsGroup,
15
+ parameters: {
16
+ docs: {
17
+ description: {
18
+ component:
19
+ 'A section of an editor settings panel: the settings about the same thing. Draws the divider above itself and owns the vertical space. Without a label, the label of the one setting inside is promoted to group weight.',
20
+ },
21
+ },
22
+ },
23
+ // The settings panel's content width: `w-92` with `p-7` leaves 312px.
24
+ decorators: [() => ({ template: '<div class="mx-auto w-78"><story /></div>' })],
25
+ argTypes: {
26
+ label: { control: 'text' },
27
+ headingLevel: { control: 'select', options: [2, 3, 4, 5, 6] },
28
+ },
29
+ } satisfies Meta<typeof EditorSettingsGroup>
30
+
31
+ type Story = StoryObj<typeof EditorSettingsGroup>
32
+
33
+ /** A labelled group: the heading names the group for assistive technology too. */
34
+ export const Labelled: Story = {
35
+ args: { label: 'Achtergrond' },
36
+ render: (args) => ({
37
+ components: { EditorSettingsGroup, EditorSettingsBlock },
38
+ setup: () => ({ args }),
39
+ template: `
40
+ <EditorSettingsGroup v-bind="args">
41
+ <EditorSettingsBlock label="Kleur">
42
+ <p class="small-normal text-gray-700">Kleurkiezer</p>
43
+ </EditorSettingsBlock>
44
+ <EditorSettingsBlock label="Afbeelding">
45
+ <p class="small-normal text-gray-700">Afbeeldingkiezer</p>
46
+ </EditorSettingsBlock>
47
+ </EditorSettingsGroup>
48
+ `,
49
+ }),
50
+ play: ({ canvasElement }) => {
51
+ const canvas = within(canvasElement)
52
+
53
+ expect(canvas.getByRole('group', { name: 'Achtergrond' })).toBeInTheDocument()
54
+ expect(canvas.getByRole('heading', { level: 3, name: 'Achtergrond' })).toBeInTheDocument()
55
+ },
56
+ }
57
+
58
+ /** No label: the one setting inside carries the group's weight, as a heading. */
59
+ export const Unlabelled: Story = {
60
+ render: (args) => ({
61
+ components: { EditorSettingsGroup, EditorPadding },
62
+ setup() {
63
+ const padding = ref('py-6')
64
+ return { args, padding }
65
+ },
66
+ template: `
67
+ <EditorSettingsGroup v-bind="args">
68
+ <EditorPadding v-model="padding" sides="vertical" label="Witruimte" />
69
+ </EditorSettingsGroup>
70
+ `,
71
+ }),
72
+ play: ({ canvasElement }) => {
73
+ const canvas = within(canvasElement)
74
+
75
+ expect(canvas.queryByRole('group')).toBeNull()
76
+ expect(canvas.getByRole('heading', { level: 3, name: 'Witruimte' })).toBeInTheDocument()
77
+ },
78
+ }
79
+
80
+ /**
81
+ * The divider panel from the pattern spec: a promoted Witruimte and the group
82
+ * Lijn. Measured: 24px from a line to the heading under it in both groups and
83
+ * from the last visible control to the next line or the group's end, 16px from
84
+ * the group heading to the first setting and between two settings.
85
+ */
86
+ export const DividerPanel: Story = {
87
+ render: () => ({
88
+ components: { EditorSettingsGroup, EditorPadding, EditorLineStyle, EditorJustifyContent },
89
+ setup() {
90
+ const padding = ref('py-6')
91
+ const lineStyle = ref<EditorLineStyleValue>('solid')
92
+ const thickness = ref<1 | 2 | 3 | 4 | 6 | 8>(2)
93
+ const align = ref<EditorJustifyContentValue>('justify-center')
94
+ return { padding, lineStyle, thickness, align }
95
+ },
96
+ template: `
97
+ <div class="flex flex-col">
98
+ <EditorSettingsGroup>
99
+ <EditorPadding v-model="padding" sides="vertical" label="Witruimte" />
100
+ </EditorSettingsGroup>
101
+ <EditorSettingsGroup label="Lijn">
102
+ <EditorLineStyle v-model="lineStyle" v-model:thickness="thickness" label="Stijl" />
103
+ <EditorJustifyContent
104
+ v-model="align"
105
+ :options="['justify-start', 'justify-center', 'justify-end']"
106
+ :messages="{ start: 'Links', center: 'Midden', end: 'Rechts' }"
107
+ />
108
+ </EditorSettingsGroup>
109
+ </div>
110
+ `,
111
+ }),
112
+ play: async ({ canvasElement }) => {
113
+ const canvas = within(canvasElement)
114
+ await document.fonts.ready
115
+
116
+ const [first, second] = canvasElement.querySelectorAll<HTMLElement>('.border-t')
117
+ const witruimte = canvas.getByRole('heading', { name: 'Witruimte' })
118
+ const lijn = canvas.getByRole('heading', { name: 'Lijn' })
119
+ const stijl = canvas.getByText('Stijl')
120
+ const uitlijning = canvas.getByText('Uitlijning')
121
+
122
+ // Both headings sit 24px under their own line, promoted or not.
123
+ const gapUnderLine = (group: HTMLElement, heading: HTMLElement) =>
124
+ heading.getBoundingClientRect().top - group.getBoundingClientRect().top - 1
125
+ expect(gapUnderLine(first, witruimte)).toBe(24)
126
+ expect(gapUnderLine(second, lijn)).toBe(24)
127
+
128
+ // Last visible control to the next group's line: 24px, the same as line
129
+ // to heading. Here the control is the padding field.
130
+ const lastControl = first.querySelector('input')!.parentElement!
131
+ expect(second.getBoundingClientRect().top - lastControl.getBoundingClientRect().bottom).toBe(24)
132
+
133
+ // The last setting of the second group (Uitlijning, stacked) ends 24px
134
+ // above the group's bottom too.
135
+ const alignOptions = canvas.getByRole('button', { name: 'Links' }).parentElement!
136
+ expect(
137
+ second.getBoundingClientRect().bottom - alignOptions.getBoundingClientRect().bottom,
138
+ ).toBe(24)
139
+
140
+ // Group heading to the first setting: 16px.
141
+ expect(stijl.getBoundingClientRect().top - lijn.getBoundingClientRect().bottom).toBe(16)
142
+
143
+ // Setting to setting: 16px between the blocks (Stijl to Uitlijning).
144
+ const stijlBlock = stijl.parentElement!.parentElement!
145
+ expect(uitlijning.getBoundingClientRect().top - stijlBlock.getBoundingClientRect().bottom).toBe(
146
+ 16,
147
+ )
148
+
149
+ // The two promoted and labelled headings weigh the same.
150
+ expect(getComputedStyle(witruimte).fontWeight).toBe(getComputedStyle(lijn).fontWeight)
151
+ expect(getComputedStyle(stijl).fontWeight).not.toBe(getComputedStyle(lijn).fontWeight)
152
+ },
153
+ }
154
+
155
+ /**
156
+ * The divider panel with "Lijn" collapsible, the open state shown under it.
157
+ * `lastFocusable` adds a mobile marker with a link as the last item of the
158
+ * group, to check that the clip box does not cut off its focus ring.
159
+ */
160
+ const collapsiblePanel =
161
+ (initiallyOpen: boolean, lastFocusable = false) =>
162
+ () => ({
163
+ components: { EditorSettingsGroup, EditorPadding, EditorLineStyle, EditorJustifyContent },
164
+ setup() {
165
+ const padding = ref('py-6')
166
+ const lineStyle = ref<EditorLineStyleValue>('solid')
167
+ const thickness = ref<1 | 2 | 3 | 4 | 6 | 8>(2)
168
+ const align = ref<EditorJustifyContentValue>('justify-center')
169
+ const open = ref(initiallyOpen)
170
+ return { padding, lineStyle, thickness, align, open, lastFocusable }
171
+ },
172
+ template: `
173
+ <div class="flex flex-col">
174
+ <EditorSettingsGroup>
175
+ <EditorPadding v-model="padding" sides="vertical" label="Witruimte" />
176
+ </EditorSettingsGroup>
177
+ <EditorSettingsGroup v-model:open="open" label="Lijn" collapsible>
178
+ <EditorLineStyle v-model="lineStyle" v-model:thickness="thickness" label="Stijl" />
179
+ <EditorJustifyContent
180
+ v-model="align"
181
+ :options="['justify-start', 'justify-center', 'justify-end']"
182
+ :messages="{ start: 'Links', center: 'Midden', end: 'Rechts' }"
183
+ />
184
+ <div v-if="lastFocusable" class="flex items-center justify-between pt-2">
185
+ <span class="small-normal text-gray-700">Aangepast voor mobiel</span>
186
+ <a
187
+ href="#"
188
+ data-testid="marker-link"
189
+ class="small-normal text-gray-900 underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cornflower-blue-600"
190
+ @click.prevent
191
+ >Terug naar desktop</a>
192
+ </div>
193
+ </EditorSettingsGroup>
194
+ <p data-testid="open-state" class="pt-4 small-normal text-gray-700">open: {{ open }}</p>
195
+ </div>
196
+ `,
197
+ })
198
+
199
+ const lijnGroup = (canvasElement: HTMLElement) =>
200
+ canvasElement.querySelectorAll<HTMLElement>('.border-t')[1]
201
+
202
+ const groupContent = (canvasElement: HTMLElement) =>
203
+ canvasElement.querySelector<HTMLElement>('[data-testid="editor-settings-group-content"]')!
204
+
205
+ /**
206
+ * Open, pixel-equal to DividerPanel: the same 24px under the line, 16px to the
207
+ * first setting, and a 20px title row. Click closes it (content inert, the
208
+ * group folds to line + 24 + title + 24); Space opens it again with focus
209
+ * staying on the button.
210
+ */
211
+ export const Collapsible: Story = {
212
+ render: collapsiblePanel(true),
213
+ play: async ({ canvasElement }) => {
214
+ const canvas = within(canvasElement)
215
+ await document.fonts.ready
216
+
217
+ const group = lijnGroup(canvasElement)
218
+ const heading = canvas.getByRole('heading', { name: 'Lijn' })
219
+ const button = canvas.getByRole('button', { name: 'Lijn' })
220
+ const content = groupContent(canvasElement)
221
+
222
+ // Parity with a group that does not collapse.
223
+ expect(heading.getBoundingClientRect().top - group.getBoundingClientRect().top - 1).toBe(24)
224
+ expect(heading.getBoundingClientRect().height).toBe(20)
225
+ expect(
226
+ canvas.getByText('Stijl').getBoundingClientRect().top -
227
+ heading.getBoundingClientRect().bottom,
228
+ ).toBe(16)
229
+
230
+ // The whole row is the hit area, at least 24px tall.
231
+ expect(button.getBoundingClientRect().height).toBeGreaterThanOrEqual(24)
232
+ expect(button.getBoundingClientRect().width).toBeGreaterThanOrEqual(
233
+ heading.getBoundingClientRect().width,
234
+ )
235
+ expect(button).toHaveAttribute('aria-controls', content.id)
236
+
237
+ await userEvent.click(button)
238
+ await waitFor(() => expect(button).toHaveAttribute('aria-expanded', 'false'))
239
+ expect(content).toHaveAttribute('inert')
240
+ expect(canvas.getByTestId('open-state')).toHaveTextContent('open: false')
241
+
242
+ // Closed, the chevron points up: a half turn from pointing down.
243
+ const chevron = button.querySelector<HTMLElement>('span[aria-hidden="true"]')!
244
+ await waitFor(() => expect(getComputedStyle(chevron).rotate).toBe('180deg'))
245
+
246
+ // Closed: line + 24 + title + 24, nothing left of the first block.
247
+ await waitFor(() => expect(group.getBoundingClientRect().height).toBe(1 + 24 + 20 + 24))
248
+ expect(group.getBoundingClientRect().bottom - heading.getBoundingClientRect().bottom).toBe(24)
249
+
250
+ button.focus()
251
+ await userEvent.keyboard(' ')
252
+ await waitFor(() => expect(button).toHaveAttribute('aria-expanded', 'true'))
253
+ expect(content).not.toHaveAttribute('inert')
254
+ expect(document.activeElement).toBe(button)
255
+ await waitFor(() => expect(['0deg', 'none']).toContain(getComputedStyle(chevron).rotate))
256
+
257
+ // Open again: 24px from the last visible control to the group's bottom.
258
+ const alignOptions = canvas.getByRole('button', { name: 'Links' }).parentElement!
259
+ await waitFor(() =>
260
+ expect(
261
+ group.getBoundingClientRect().bottom - alignOptions.getBoundingClientRect().bottom,
262
+ ).toBe(24),
263
+ )
264
+ },
265
+ }
266
+
267
+ /** Starts closed: no animation on the first render, Tab skips the content. */
268
+ export const CollapsibleStartsClosed: Story = {
269
+ render: collapsiblePanel(false),
270
+ play: async ({ canvasElement }) => {
271
+ const canvas = within(canvasElement)
272
+ const button = canvas.getByRole('button', { name: 'Lijn' })
273
+
274
+ expect(button).toHaveAttribute('aria-expanded', 'false')
275
+ expect(groupContent(canvasElement)).toHaveAttribute('inert')
276
+
277
+ // Keyboard focus shows a real ring: a stray `focus:outline-none` would set
278
+ // the outline style to none under `focus-visible:outline-2` and hide it.
279
+ button.focus()
280
+ expect(button.matches(':focus-visible')).toBe(true)
281
+ expect(getComputedStyle(button).outlineStyle).not.toBe('none')
282
+ expect(getComputedStyle(button).outlineWidth).toBe('2px')
283
+
284
+ await userEvent.keyboard('{Enter}')
285
+ await waitFor(() => expect(button).toHaveAttribute('aria-expanded', 'true'))
286
+ expect(document.activeElement).toBe(button)
287
+ },
288
+ }
289
+
290
+ /**
291
+ * A focusable mobile marker as the last item: its focus ring (2px outline,
292
+ * 2px offset) must fit inside the clip box on every side, the bottom too.
293
+ */
294
+ export const CollapsibleLastFocusable: Story = {
295
+ render: collapsiblePanel(true, true),
296
+ play: async ({ canvasElement }) => {
297
+ const canvas = within(canvasElement)
298
+ await document.fonts.ready
299
+
300
+ const link = canvas.getByTestId('marker-link')
301
+ const clip = groupContent(canvasElement).firstElementChild!.getBoundingClientRect()
302
+ const ring = link.getBoundingClientRect()
303
+
304
+ expect(clip.bottom).toBeGreaterThanOrEqual(ring.bottom + 4)
305
+ expect(clip.right).toBeGreaterThanOrEqual(ring.right + 4)
306
+ expect(clip.left).toBeLessThanOrEqual(ring.left - 4)
307
+
308
+ // And it is reachable by keyboard while open.
309
+ link.focus()
310
+ expect(document.activeElement).toBe(link)
311
+ },
312
+ }
313
+
314
+ /**
315
+ * A tall group (~330px of settings), where the closing curve shows most. Closing
316
+ * on ease-in-out-strong starts gently: about 40ms in, well over half the height
317
+ * is still there (a strong ease-out would already have taken most of it). Fully
318
+ * closed, the content wrapper is exactly 0px and the group is line + 24 + title
319
+ * + 24.
320
+ */
321
+ export const CollapsibleTall: Story = {
322
+ render: () => ({
323
+ components: { EditorSettingsGroup, EditorSettingsBlock },
324
+ setup() {
325
+ const open = ref(true)
326
+ return { open }
327
+ },
328
+ template: `
329
+ <EditorSettingsGroup v-model:open="open" label="Achtergrond" collapsible>
330
+ <EditorSettingsBlock v-for="name in ['Kleur', 'Afbeelding', 'Vulling']" :key="name" :label="name">
331
+ <div class="h-16 rounded-lg bg-gray-100" />
332
+ </EditorSettingsBlock>
333
+ </EditorSettingsGroup>
334
+ `,
335
+ }),
336
+ play: async ({ canvasElement }) => {
337
+ const canvas = within(canvasElement)
338
+ const group = canvasElement.querySelector<HTMLElement>('.border-t')!
339
+ const button = canvas.getByRole('button', { name: 'Achtergrond' })
340
+ const content = groupContent(canvasElement)
341
+
342
+ const openHeight = content.getBoundingClientRect().height
343
+ expect(openHeight).toBeGreaterThan(300)
344
+
345
+ // Sample frame by frame from the click until 40ms have passed.
346
+ const start = performance.now()
347
+ button.click()
348
+ const sample = await new Promise<{ height: number; elapsed: number }>((resolve) => {
349
+ const tick = () => {
350
+ const elapsed = performance.now() - start
351
+ if (elapsed >= 40) resolve({ height: content.getBoundingClientRect().height, elapsed })
352
+ else requestAnimationFrame(tick)
353
+ }
354
+ requestAnimationFrame(tick)
355
+ })
356
+ expect(sample.elapsed).toBeLessThan(80)
357
+ expect(sample.height).toBeGreaterThan(openHeight / 2)
358
+
359
+ // Fully closed: no height left behind by the content or the clip box.
360
+ await waitFor(() => expect(content.getBoundingClientRect().height).toBe(0))
361
+ await waitFor(() => expect(group.getBoundingClientRect().height).toBe(1 + 24 + 20 + 24))
362
+ const heading = canvas.getByRole('heading', { name: 'Achtergrond' })
363
+ expect(group.getBoundingClientRect().bottom - heading.getBoundingClientRect().bottom).toBe(24)
364
+ },
365
+ }