@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,266 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import { expect, userEvent, waitFor, within } from 'storybook/test'
3
+ import { ref } from 'vue'
4
+ import EditorSettingsBlock from './EditorSettingsBlock.vue'
5
+ import EditorSettingsGroup from '../EditorSettingsGroup/EditorSettingsGroup.vue'
6
+
7
+ /** Rendered width of a `size="sm"` switch track (`w-9`), in pixels. */
8
+ const SM_TRACK_WIDTH = 36
9
+
10
+ /**
11
+ * A wrapping label used to squash the switch, because the track sat in a flex
12
+ * row without `shrink-0`. Measured in a real browser: jsdom has no layout.
13
+ */
14
+ const expectFullWidthTrack = (canvasElement: HTMLElement) => {
15
+ const track = canvasElement.querySelector('label > span.relative')
16
+
17
+ expect(track).not.toBeNull()
18
+ expect(track!.getBoundingClientRect().width).toBe(SM_TRACK_WIDTH)
19
+ }
20
+
21
+ /**
22
+ * The collapsing wrapper, found by the grid row it animates rather than by any
23
+ * styling class, so restyling the block cannot quietly break these assertions.
24
+ */
25
+ const contentWrapper = (canvasElement: HTMLElement) =>
26
+ canvasElement.querySelector('[class*="grid-rows-"]') as HTMLElement
27
+
28
+ export default {
29
+ title: 'Components/Editor Components/Atoms/Editor Settings Block',
30
+ component: EditorSettingsBlock,
31
+ parameters: {
32
+ docs: {
33
+ description: {
34
+ component:
35
+ 'One labelled setting in the editor sidebar, optionally collapsible via a switch. The block draws no separator of its own: the consumer groups and divides blocks on the wrapper, e.g. `divide-y divide-gray-200`.',
36
+ },
37
+ },
38
+ },
39
+ decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
40
+ argTypes: {
41
+ label: { control: 'text' },
42
+ switchable: { control: 'boolean' },
43
+ modelValue: { control: 'boolean' },
44
+ },
45
+ args: {
46
+ label: 'Achtergrond',
47
+ switchable: false,
48
+ },
49
+ } satisfies Meta<typeof EditorSettingsBlock>
50
+
51
+ type Story = StoryObj<typeof EditorSettingsBlock>
52
+
53
+ export const Default: Story = {
54
+ render: (args) => ({
55
+ components: { EditorSettingsBlock },
56
+ setup: () => ({ args }),
57
+ template: `
58
+ <EditorSettingsBlock v-bind="args">
59
+ <p class="small-normal text-gray-700">Deze instelling staat altijd aan.</p>
60
+ </EditorSettingsBlock>
61
+ `,
62
+ }),
63
+ // The `-mx-1` / `px-1` pair that keeps a focus ring from being clipped must
64
+ // cancel out: slot content stays aligned with the label, and the widened
65
+ // clipping box may not push the block past its container.
66
+ play: ({ canvasElement }) => {
67
+ const label = canvasElement.querySelector('p')!
68
+ const block = label.parentElement!.parentElement!
69
+ const content = canvasElement.querySelectorAll('p')[1]
70
+ const clip = contentWrapper(canvasElement).firstElementChild!
71
+
72
+ expect(content.getBoundingClientRect().left).toBe(label.getBoundingClientRect().left)
73
+ expect(clip.getBoundingClientRect().width).toBe(block.getBoundingClientRect().width + 8)
74
+ expect(canvasElement.scrollWidth).toBe(canvasElement.clientWidth)
75
+ },
76
+ }
77
+
78
+ export const Switchable: Story = {
79
+ args: { switchable: true },
80
+ render: (args) => ({
81
+ components: { EditorSettingsBlock },
82
+ setup() {
83
+ const enabled = ref(true)
84
+ return { args, enabled }
85
+ },
86
+ template: `
87
+ <EditorSettingsBlock v-bind="args" v-model="enabled">
88
+ <p class="small-normal text-gray-700">Zet de switch uit om dit blok in te klappen.</p>
89
+ </EditorSettingsBlock>
90
+ `,
91
+ }),
92
+ play: async ({ canvasElement }) => {
93
+ expectFullWidthTrack(canvasElement)
94
+
95
+ const toggle = within(canvasElement).getByRole('switch')
96
+ const content = contentWrapper(canvasElement)
97
+
98
+ expect(content).toHaveClass('grid-rows-[1fr]')
99
+ expect(content).not.toHaveAttribute('inert')
100
+
101
+ await userEvent.click(toggle)
102
+ await waitFor(() => {
103
+ expect(content).toHaveClass('grid-rows-[0fr]')
104
+ expect(content).toHaveAttribute('inert')
105
+ })
106
+
107
+ await userEvent.click(toggle)
108
+ await waitFor(() => {
109
+ expect(content).toHaveClass('grid-rows-[1fr]')
110
+ expect(content).not.toHaveAttribute('inert')
111
+ })
112
+ },
113
+ }
114
+
115
+ export const SwitchableClosed: Story = {
116
+ args: { switchable: true },
117
+ render: (args) => ({
118
+ components: { EditorSettingsBlock },
119
+ setup() {
120
+ const enabled = ref(false)
121
+ return { args, enabled }
122
+ },
123
+ template: `
124
+ <EditorSettingsBlock v-bind="args" v-model="enabled">
125
+ <p class="small-normal text-gray-700">Deze content is gemount maar ingeklapt.</p>
126
+ </EditorSettingsBlock>
127
+ `,
128
+ }),
129
+ }
130
+
131
+ export const LongLabel: Story = {
132
+ args: {
133
+ switchable: true,
134
+ label: 'Achtergrondafbeelding en overlaykleur van de headersectie',
135
+ },
136
+ render: (args) => ({
137
+ components: { EditorSettingsBlock },
138
+ setup() {
139
+ const enabled = ref(true)
140
+ return { args, enabled }
141
+ },
142
+ template: `
143
+ <EditorSettingsBlock v-bind="args" v-model="enabled">
144
+ <p class="small-normal text-gray-700">Het label loopt door over meerdere regels.</p>
145
+ </EditorSettingsBlock>
146
+ `,
147
+ }),
148
+ // Same assertion as Switchable: a wrapping label must not change the track.
149
+ play: ({ canvasElement }) => expectFullWidthTrack(canvasElement),
150
+ }
151
+
152
+ // The block draws no separator of its own; the wrapper divides the blocks,
153
+ // the way the app does.
154
+ export const Stacked: Story = {
155
+ render: () => ({
156
+ components: { EditorSettingsBlock },
157
+ setup() {
158
+ const logo = ref(true)
159
+ const footer = ref(false)
160
+ return { logo, footer }
161
+ },
162
+ template: `
163
+ <div class="divide-y divide-gray-200">
164
+ <EditorSettingsBlock label="Titel">
165
+ <p class="small-normal text-gray-700">Deze instelling staat altijd aan.</p>
166
+ </EditorSettingsBlock>
167
+ <EditorSettingsBlock label="Logo" switchable v-model="logo">
168
+ <p class="small-normal text-gray-700">Toon het makelaarslogo linksboven.</p>
169
+ </EditorSettingsBlock>
170
+ <EditorSettingsBlock label="Voettekst" switchable v-model="footer">
171
+ <p class="small-normal text-gray-700">Toon de contactgegevens onderaan.</p>
172
+ </EditorSettingsBlock>
173
+ </div>
174
+ `,
175
+ }),
176
+ }
177
+
178
+ /**
179
+ * Two groups of blocks: the outer wrapper divides the groups, and the blocks
180
+ * inside a group share no separator.
181
+ */
182
+ export const Grouped: Story = {
183
+ render: () => ({
184
+ components: { EditorSettingsBlock },
185
+ setup() {
186
+ const shadow = ref(false)
187
+ return { shadow }
188
+ },
189
+ template: `
190
+ <div class="divide-y divide-gray-200">
191
+ <div data-testid="group">
192
+ <EditorSettingsBlock label="Lettertype">
193
+ <p class="small-normal text-gray-700">Kies het lettertype van de kop.</p>
194
+ </EditorSettingsBlock>
195
+ <EditorSettingsBlock label="Regelhoogte">
196
+ <p class="small-normal text-gray-700">Ruimte tussen de regels.</p>
197
+ </EditorSettingsBlock>
198
+ </div>
199
+ <div data-testid="group">
200
+ <EditorSettingsBlock label="Achtergrond">
201
+ <p class="small-normal text-gray-700">Kleur achter de sectie.</p>
202
+ </EditorSettingsBlock>
203
+ <EditorSettingsBlock label="Schaduw" switchable v-model="shadow">
204
+ <p class="small-normal text-gray-700">Schaduw onder de sectie.</p>
205
+ </EditorSettingsBlock>
206
+ </div>
207
+ </div>
208
+ `,
209
+ }),
210
+ // Measured in a real browser. Tailwind v4 draws `divide-y` as a bottom border
211
+ // on every child but the last, so only the first group carries one; no block
212
+ // inside either group draws a border.
213
+ play: ({ canvasElement }) => {
214
+ const groups = within(canvasElement).getAllByTestId('group')
215
+ const borderWidths = (element: Element) => {
216
+ const style = getComputedStyle(element)
217
+ return [style.borderTopWidth, style.borderBottomWidth]
218
+ }
219
+
220
+ expect(borderWidths(groups[0])).toEqual(['0px', '1px'])
221
+ expect(borderWidths(groups[1])).toEqual(['0px', '0px'])
222
+ for (const block of groups.flatMap((group) => [...group.children])) {
223
+ expect(borderWidths(block)).toEqual(['0px', '0px'])
224
+ }
225
+ },
226
+ }
227
+
228
+ export const EmptyContent: Story = {
229
+ args: { switchable: true, label: 'Watermerk' },
230
+ render: (args) => ({
231
+ components: { EditorSettingsBlock },
232
+ setup() {
233
+ const enabled = ref(true)
234
+ return { args, enabled }
235
+ },
236
+ template: `<EditorSettingsBlock v-bind="args" v-model="enabled" />`,
237
+ }),
238
+ }
239
+
240
+ /**
241
+ * Inside a group the block takes only `pt-4`: the group owns the line and the
242
+ * bottom space, so two settings sit 16px apart instead of 32.
243
+ */
244
+ export const InGroup: Story = {
245
+ render: () => ({
246
+ components: { EditorSettingsBlock, EditorSettingsGroup },
247
+ template: `
248
+ <EditorSettingsGroup label="Achtergrond">
249
+ <EditorSettingsBlock label="Kleur">
250
+ <p class="small-normal text-gray-700">Kleurkiezer</p>
251
+ </EditorSettingsBlock>
252
+ <EditorSettingsBlock label="Afbeelding">
253
+ <p data-testid="second" class="small-normal text-gray-700">Afbeeldingkiezer</p>
254
+ </EditorSettingsBlock>
255
+ </EditorSettingsGroup>
256
+ `,
257
+ }),
258
+ play: ({ canvasElement }) => {
259
+ const canvas = within(canvasElement)
260
+ const firstContent = canvas.getByText('Kleurkiezer').getBoundingClientRect()
261
+ const secondLabel = canvas.getByText('Afbeelding').getBoundingClientRect()
262
+
263
+ // The content wrapper's pb-1 plus the next block's pt-4.
264
+ expect(secondLabel.top - firstContent.bottom).toBe(20)
265
+ },
266
+ }
@@ -0,0 +1,421 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { defineComponent } from 'vue'
4
+ import EditorSettingsBlock from './EditorSettingsBlock.vue'
5
+ import EditorSettingsGroup from '../EditorSettingsGroup/EditorSettingsGroup.vue'
6
+
7
+ const slots = { default: '<p>Block content</p>' }
8
+
9
+ describe('EditorSettingsBlock', () => {
10
+ // Deliberately no assertions on typography utilities anywhere in this file:
11
+ // the label is selected by structure or by text, so restyling the header
12
+ // never turns these tests red.
13
+ // The consumer groups and divides blocks on the wrapper, so the block itself
14
+ // may not draw a separator in either mode.
15
+ it.each([false, true])('puts no border classes on the root (switchable: %s)', (switchable) => {
16
+ const wrapper = mount(EditorSettingsBlock, {
17
+ props: { label: 'Achtergrond', switchable },
18
+ slots,
19
+ })
20
+
21
+ expect(wrapper.classes().filter((name) => /(^|:)border(-|$)/.test(name))).toEqual([])
22
+ expect(wrapper.classes()).toContain('py-4')
23
+ })
24
+
25
+ it('renders the label in a paragraph when it is not switchable', () => {
26
+ const wrapper = mount(EditorSettingsBlock, { props: { label: 'Achtergrond' }, slots })
27
+
28
+ expect(wrapper.find('p').text()).toBe('Achtergrond')
29
+ })
30
+
31
+ it('renders the label inside the switch label when it is switchable', () => {
32
+ const wrapper = mount(EditorSettingsBlock, {
33
+ props: { label: 'Achtergrond', switchable: true },
34
+ slots,
35
+ })
36
+
37
+ expect(wrapper.find('label span').text()).toBe('Achtergrond')
38
+ expect(wrapper.find('label').text()).toContain('Achtergrond')
39
+ })
40
+
41
+ it('does not render a checkbox when it is not switchable', () => {
42
+ const wrapper = mount(EditorSettingsBlock, { props: { label: 'Achtergrond' }, slots })
43
+
44
+ expect(wrapper.find('input[type="checkbox"]').exists()).toBe(false)
45
+ })
46
+
47
+ it('keeps the content open and not inert when it is not switchable', () => {
48
+ const wrapper = mount(EditorSettingsBlock, { props: { label: 'Achtergrond' }, slots })
49
+ const content = wrapper.find('div.grid')
50
+
51
+ expect(content.classes()).toContain('grid-rows-[1fr]')
52
+ expect(content.attributes('inert')).toBeUndefined()
53
+ })
54
+
55
+ it('ignores modelValue false when it is not switchable', () => {
56
+ const wrapper = mount(EditorSettingsBlock, {
57
+ props: { label: 'Achtergrond', modelValue: false },
58
+ slots,
59
+ })
60
+ const content = wrapper.find('div.grid')
61
+
62
+ expect(content.classes()).toContain('grid-rows-[1fr]')
63
+ expect(content.attributes('inert')).toBeUndefined()
64
+ })
65
+
66
+ it('does not animate the content when it is not switchable', () => {
67
+ const wrapper = mount(EditorSettingsBlock, { props: { label: 'Achtergrond' }, slots })
68
+
69
+ expect(wrapper.find('div.grid').classes()).not.toContain('transition-[grid-template-rows]')
70
+ })
71
+
72
+ it('animates the content when it is switchable', () => {
73
+ const wrapper = mount(EditorSettingsBlock, {
74
+ props: { label: 'Achtergrond', switchable: true },
75
+ slots,
76
+ })
77
+
78
+ expect(wrapper.find('div.grid').classes()).toEqual(
79
+ expect.arrayContaining(['transition-[grid-template-rows]', 'motion-reduce:transition-none']),
80
+ )
81
+ })
82
+
83
+ it('checks the switch and opens the content when modelValue is true', () => {
84
+ const wrapper = mount(EditorSettingsBlock, {
85
+ props: { label: 'Achtergrond', switchable: true, modelValue: true },
86
+ slots,
87
+ })
88
+ const content = wrapper.find('div.grid')
89
+
90
+ expect((wrapper.find('input[type="checkbox"]').element as HTMLInputElement).checked).toBe(true)
91
+ expect(content.classes()).toContain('grid-rows-[1fr]')
92
+ expect(content.attributes('inert')).toBeUndefined()
93
+ })
94
+
95
+ it('collapses and inerts the content when modelValue is false', () => {
96
+ const wrapper = mount(EditorSettingsBlock, {
97
+ props: { label: 'Achtergrond', switchable: true, modelValue: false },
98
+ slots,
99
+ })
100
+ const content = wrapper.find('div.grid')
101
+
102
+ expect((wrapper.find('input[type="checkbox"]').element as HTMLInputElement).checked).toBe(false)
103
+ expect(content.classes()).toContain('grid-rows-[0fr]')
104
+ expect(content.attributes('inert')).toBeDefined()
105
+ })
106
+
107
+ it('keeps collapsed slot content mounted', () => {
108
+ const wrapper = mount(EditorSettingsBlock, {
109
+ props: { label: 'Achtergrond', switchable: true, modelValue: false },
110
+ slots,
111
+ })
112
+
113
+ expect(wrapper.find('div.grid').text()).toContain('Block content')
114
+ })
115
+
116
+ it('emits update:modelValue when the switch is toggled', async () => {
117
+ const wrapper = mount(EditorSettingsBlock, {
118
+ props: { label: 'Achtergrond', switchable: true, modelValue: false },
119
+ slots,
120
+ })
121
+
122
+ await wrapper.find('input[type="checkbox"]').setValue(true)
123
+
124
+ expect(wrapper.emitted('update:modelValue')).toEqual([[true]])
125
+ })
126
+
127
+ it('defaults the model to true', () => {
128
+ const wrapper = mount(EditorSettingsBlock, {
129
+ props: { label: 'Achtergrond', switchable: true },
130
+ slots,
131
+ })
132
+
133
+ expect((wrapper.find('input[type="checkbox"]').element as HTMLInputElement).checked).toBe(true)
134
+ expect(wrapper.find('div.grid').attributes('inert')).toBeUndefined()
135
+ })
136
+
137
+ it('does not render a content wrapper without slot content', () => {
138
+ const wrapper = mount(EditorSettingsBlock, {
139
+ props: { label: 'Achtergrond', switchable: true },
140
+ })
141
+
142
+ expect(wrapper.find('div.grid').exists()).toBe(false)
143
+ })
144
+
145
+ it('stretches the switch label across the header row', () => {
146
+ const wrapper = mount(EditorSettingsBlock, {
147
+ props: { label: 'Achtergrond', switchable: true },
148
+ slots,
149
+ })
150
+ const header = wrapper.find('label').element.parentElement!
151
+
152
+ expect(header.className).toContain('[&>label]:w-full')
153
+ expect(header.querySelector(':scope > label')).not.toBeNull()
154
+ })
155
+
156
+ // The gap between label and track lives on the Switch's own <label>, not on
157
+ // the header: the header has exactly one child in both modes, so a `gap` on
158
+ // the header would never apply.
159
+ it('puts the header gap on the switch label instead of the header', () => {
160
+ const wrapper = mount(EditorSettingsBlock, {
161
+ props: { label: 'Achtergrond', switchable: true },
162
+ slots,
163
+ })
164
+ const header = wrapper.find('label').element.parentElement!
165
+
166
+ expect(header.className).toContain('[&>label]:gap-3')
167
+ expect(header.children).toHaveLength(1)
168
+ })
169
+
170
+ it('reacts to modelValue changing after mount', async () => {
171
+ const wrapper = mount(EditorSettingsBlock, {
172
+ props: { label: 'Achtergrond', switchable: true, modelValue: true },
173
+ slots,
174
+ })
175
+
176
+ await wrapper.setProps({ modelValue: false })
177
+
178
+ expect(wrapper.find('div.grid').attributes('inert')).toBeDefined()
179
+ expect(wrapper.find('div.grid').classes()).toContain('grid-rows-[0fr]')
180
+
181
+ await wrapper.setProps({ modelValue: true })
182
+
183
+ expect(wrapper.find('div.grid').attributes('inert')).toBeUndefined()
184
+ expect(wrapper.find('div.grid').classes()).toContain('grid-rows-[1fr]')
185
+ })
186
+
187
+ it('reacts to switchable changing after mount', async () => {
188
+ const wrapper = mount(EditorSettingsBlock, {
189
+ props: { label: 'Achtergrond', switchable: false, modelValue: false },
190
+ slots,
191
+ })
192
+
193
+ expect(wrapper.find('input[type="checkbox"]').exists()).toBe(false)
194
+ expect(wrapper.find('div.grid').attributes('inert')).toBeUndefined()
195
+
196
+ await wrapper.setProps({ switchable: true })
197
+
198
+ expect(wrapper.find('input[type="checkbox"]').exists()).toBe(true)
199
+ expect(wrapper.find('div.grid').attributes('inert')).toBeDefined()
200
+ expect(wrapper.find('div.grid').classes()).toContain('grid-rows-[0fr]')
201
+
202
+ await wrapper.setProps({ switchable: false })
203
+
204
+ expect(wrapper.find('input[type="checkbox"]').exists()).toBe(false)
205
+ expect(wrapper.find('div.grid').attributes('inert')).toBeUndefined()
206
+ expect(wrapper.find('div.grid').classes()).toContain('grid-rows-[1fr]')
207
+ })
208
+
209
+ it('sets describedby as aria-describedby on the switch input', () => {
210
+ const wrapper = mount(EditorSettingsBlock, {
211
+ props: { label: 'Achtergrond', switchable: true, describedby: 'hint' },
212
+ slots,
213
+ })
214
+
215
+ expect(wrapper.get('[role="switch"]').attributes('aria-describedby')).toBe('hint')
216
+ })
217
+
218
+ it('renders no aria-describedby on the switch without describedby', () => {
219
+ const wrapper = mount(EditorSettingsBlock, {
220
+ props: { label: 'Achtergrond', switchable: true },
221
+ slots,
222
+ })
223
+
224
+ expect(wrapper.get('[role="switch"]').attributes('aria-describedby')).toBeUndefined()
225
+ })
226
+ })
227
+
228
+ // Outside a NexxtEditorSettingsGroup the block must render exactly what it did
229
+ // before groups existed: other consumers of the library rely on its `py-4` and
230
+ // its markup. These snapshots were taken from the block before the group
231
+ // context was added, so any drift outside a group shows up here.
232
+ describe('EditorSettingsBlock outside a group', () => {
233
+ it('renders the stacked block unchanged', () => {
234
+ const wrapper = mount(EditorSettingsBlock, { props: { label: 'Achtergrond' }, slots })
235
+
236
+ expect(wrapper.html()).toMatchInlineSnapshot(`
237
+ "<div class="py-4">
238
+ <!--
239
+ Switch.vue sets \`inheritAttrs: false\` and binds \`$attrs\` on its sr-only
240
+ input, so a class on <Switch> never reaches its root <label>. The child
241
+ variant below styles that label from the header instead, which keeps the
242
+ whole row clickable and the accessible name on the real <label>. The gap
243
+ has to go on that label too: the header has a single child in both modes,
244
+ so its own \`gap\` would never apply and the spacing would silently fall
245
+ back to the \`gap-2\` that Switch.vue sets for itself.
246
+ -->
247
+ <div class="flex items-center [&amp;>label]:flex [&amp;>label]:w-full [&amp;>label]:gap-3">
248
+ <p class="small-normal text-gray-900">Achtergrond</p>
249
+ </div>
250
+ <div class="grid grid-rows-[1fr]">
251
+ <!--
252
+ \`overflow-hidden\` is what makes the 0fr collapse clip, but it also clips
253
+ a focus ring on a full-width control in the slot, and the block has no
254
+ horizontal padding of its own. \`-mx-1\` widens the clipping box by 4px on
255
+ each side and the \`px-1\` below pushes the content back, so the ring has
256
+ room while the content keeps the same x position as the label.
257
+ -->
258
+ <div class="-mx-1 min-h-0 overflow-hidden opacity-100">
259
+ <!-- Padding sits on the inner element so nothing is left over at 0fr. -->
260
+ <div class="px-1 pt-1.5 pb-1">
261
+ <p>Block content</p>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>"
266
+ `)
267
+ })
268
+
269
+ it('renders the switchable block unchanged', () => {
270
+ const wrapper = mount(EditorSettingsBlock, {
271
+ props: { label: 'Achtergrond', switchable: true, modelValue: false },
272
+ slots,
273
+ })
274
+
275
+ expect(wrapper.html()).toMatchInlineSnapshot(`
276
+ "<div class="py-4">
277
+ <!--
278
+ Switch.vue sets \`inheritAttrs: false\` and binds \`$attrs\` on its sr-only
279
+ input, so a class on <Switch> never reaches its root <label>. The child
280
+ variant below styles that label from the header instead, which keeps the
281
+ whole row clickable and the accessible name on the real <label>. The gap
282
+ has to go on that label too: the header has a single child in both modes,
283
+ so its own \`gap\` would never apply and the spacing would silently fall
284
+ back to the \`gap-2\` that Switch.vue sets for itself.
285
+ -->
286
+ <div class="flex items-center [&amp;>label]:flex [&amp;>label]:w-full [&amp;>label]:gap-3"><label class="inline-flex cursor-pointer items-center gap-2"><span class="grow text-left small-normal text-gray-900">Achtergrond</span>
287
+ <!--
288
+ \`shrink-0\`: the track has a fixed pixel size, so letting flex shrink it
289
+ squashes the toggle the moment a sibling label wraps onto a second line.
290
+ --><span class="relative inline-block shrink-0 h-5 w-9"><!--
291
+ \`role="switch"\` on a native checkbox: it is on/off rather than
292
+ selected/unselected, and the checked state is still the input's own, so
293
+ no \`aria-checked\` is set -- the browser derives it from the checkbox.
294
+ --><input type="checkbox" role="switch" class="peer sr-only"><span class="absolute inset-0 overflow-hidden rounded-[30px] border transition-colors duration-200 peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-cornflower-blue-500 border-gray-500 bg-gray-200"><span class="absolute rounded-full bg-white transition-transform duration-400 top-0.5 left-0.5 h-3.5 w-3.5"></span><span class="absolute flex items-center justify-center rounded-full bg-white transition-transform duration-300 top-0.5 left-0.5 h-3.5 w-3.5"><!--v-if--></span></span></span>
295
+ <!--v-if-->
296
+ </label></div>
297
+ <div inert="true" class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-in-out motion-reduce:transition-none">
298
+ <!--
299
+ \`overflow-hidden\` is what makes the 0fr collapse clip, but it also clips
300
+ a focus ring on a full-width control in the slot, and the block has no
301
+ horizontal padding of its own. \`-mx-1\` widens the clipping box by 4px on
302
+ each side and the \`px-1\` below pushes the content back, so the ring has
303
+ room while the content keeps the same x position as the label.
304
+ -->
305
+ <div class="-mx-1 min-h-0 overflow-hidden opacity-0 transition-opacity duration-200 ease-in-out motion-reduce:transition-none">
306
+ <!-- Padding sits on the inner element so nothing is left over at 0fr. -->
307
+ <div class="px-1 pt-1.5 pb-1">
308
+ <p>Block content</p>
309
+ </div>
310
+ </div>
311
+ </div>
312
+ </div>"
313
+ `)
314
+ })
315
+
316
+ it('renders a block without content unchanged', () => {
317
+ const wrapper = mount(EditorSettingsBlock, { props: { label: 'Achtergrond' } })
318
+
319
+ expect(wrapper.html()).toMatchInlineSnapshot(`
320
+ "<div class="py-4">
321
+ <!--
322
+ Switch.vue sets \`inheritAttrs: false\` and binds \`$attrs\` on its sr-only
323
+ input, so a class on <Switch> never reaches its root <label>. The child
324
+ variant below styles that label from the header instead, which keeps the
325
+ whole row clickable and the accessible name on the real <label>. The gap
326
+ has to go on that label too: the header has a single child in both modes,
327
+ so its own \`gap\` would never apply and the spacing would silently fall
328
+ back to the \`gap-2\` that Switch.vue sets for itself.
329
+ -->
330
+ <div class="flex items-center [&amp;>label]:flex [&amp;>label]:w-full [&amp;>label]:gap-3">
331
+ <p class="small-normal text-gray-900">Achtergrond</p>
332
+ </div>
333
+ <!--v-if-->
334
+ </div>"
335
+ `)
336
+ })
337
+ })
338
+
339
+ describe('EditorSettingsBlock in a group', () => {
340
+ /** A block inside a group, as one app so the group context reaches it. */
341
+ const inGroup = (blockProps: Record<string, unknown>) =>
342
+ mount(
343
+ defineComponent({
344
+ components: { EditorSettingsGroup, EditorSettingsBlock },
345
+ setup: () => ({ blockProps }),
346
+ template: `
347
+ <EditorSettingsGroup label="Lijn">
348
+ <EditorSettingsBlock label="Uitlijning" v-bind="blockProps">
349
+ <button type="button">Optie</button>
350
+ </EditorSettingsBlock>
351
+ </EditorSettingsGroup>
352
+ `,
353
+ }),
354
+ ).findComponent(EditorSettingsBlock)
355
+
356
+ it('stacks: label above, content in the collapsing wrapper, pt-4 on the root', () => {
357
+ const block = inGroup({})
358
+
359
+ expect(block.find('[class*="grid-rows-"]').element.contains(block.get('button').element)).toBe(
360
+ true,
361
+ )
362
+ expect((block.element as HTMLElement).classList).toEqual(
363
+ expect.objectContaining({ value: 'pt-4' }),
364
+ )
365
+ })
366
+
367
+ it('keeps an ordinary label gray-900 under a labelled group', () => {
368
+ const block = inGroup({})
369
+
370
+ expect(block.get('p').classes()).toContain('text-gray-900')
371
+ expect(block.get('p').classes()).not.toContain('text-cornflower-blue-700')
372
+ })
373
+
374
+ it('takes no layout prop: a stray one falls through as a plain attribute', () => {
375
+ const block = inGroup({ layout: 'inline' })
376
+
377
+ expect(block.props()).not.toHaveProperty('layout')
378
+ expect(block.find('[class*="grid-rows-"]').exists()).toBe(true)
379
+ })
380
+ })
381
+
382
+ describe('EditorSettingsBlock label colour', () => {
383
+ /** A block as the only setting of a group without a label, so its label is promoted. */
384
+ const promoted = (blockProps: Record<string, unknown> = {}) =>
385
+ mount(
386
+ defineComponent({
387
+ components: { EditorSettingsGroup, EditorSettingsBlock },
388
+ setup: () => ({ blockProps }),
389
+ template: `
390
+ <EditorSettingsGroup>
391
+ <EditorSettingsBlock label="Uitlijning" v-bind="blockProps">Optie</EditorSettingsBlock>
392
+ </EditorSettingsGroup>
393
+ `,
394
+ }),
395
+ ).findComponent(EditorSettingsBlock)
396
+
397
+ it.each([false, true])('stays gray-900 outside a group (switchable: %s)', (switchable) => {
398
+ const wrapper = mount(EditorSettingsBlock, {
399
+ props: { label: 'Achtergrond', switchable },
400
+ slots,
401
+ })
402
+ const label = wrapper.get(switchable ? 'label span' : 'p')
403
+
404
+ expect(label.classes()).toContain('text-gray-900')
405
+ expect(label.classes()).not.toContain('text-cornflower-blue-700')
406
+ })
407
+
408
+ it('takes the group title colour when promoted to a heading', () => {
409
+ const heading = promoted().get('h3')
410
+
411
+ expect(heading.classes()).toContain('text-cornflower-blue-700')
412
+ expect(heading.classes()).not.toContain('text-gray-900')
413
+ })
414
+
415
+ it('takes the group title colour when promoted inside the switch label', () => {
416
+ const label = promoted({ switchable: true }).get('label span')
417
+
418
+ expect(label.classes()).toContain('text-cornflower-blue-700')
419
+ expect(label.classes()).not.toContain('text-gray-900')
420
+ })
421
+ })