@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,374 @@
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 EditorPadding from './EditorPadding.vue'
5
+
6
+ export default {
7
+ title: 'Components/Editor Components/Molecules/Editor Padding',
8
+ component: EditorPadding,
9
+ decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
10
+ argTypes: {
11
+ label: { control: 'text' },
12
+ },
13
+ } satisfies Meta<typeof EditorPadding>
14
+
15
+ type Story = StoryObj<typeof EditorPadding>
16
+
17
+ /** Shows the class string as-is: it is what the editor puts on the element. */
18
+ const render = (args: Story['args'], initial: string) => ({
19
+ components: { EditorPadding },
20
+ setup() {
21
+ const padding = ref(initial)
22
+ return { args, padding }
23
+ },
24
+ template: `
25
+ <div>
26
+ <EditorPadding v-bind="args" v-model="padding" />
27
+ <p data-testid="padding-class" class="small-normal text-gray-700">{{ padding }}</p>
28
+ </div>
29
+ `,
30
+ })
31
+
32
+ /** The class string the story is bound to, as the readout under the block shows it. */
33
+ const classString = (canvasElement: HTMLElement) =>
34
+ canvasElement.querySelector('[data-testid="padding-class"]')!.textContent
35
+
36
+ /** The fields, in reading order; the two of the collapsed row are in the DOM as well. */
37
+ const fields = (canvas: ReturnType<typeof within>) =>
38
+ canvas.getAllByRole('textbox') as HTMLInputElement[]
39
+
40
+ /** The fields show px while the model stays a Tailwind class: `p-6` reads as 24. */
41
+ export const Default: Story = {
42
+ render: (args) => render(args, 'p-6'),
43
+ }
44
+
45
+ /** Padding that is not set yet: every field falls back to its `0` placeholder. */
46
+ export const Empty: Story = {
47
+ render: (args) => render(args, 'p-0'),
48
+ }
49
+
50
+ /**
51
+ * The tooltip is portalled to the body, so it is looked up outside the canvas,
52
+ * and on reka-ui's own wrapper rather than by role or text: `role="tooltip"`
53
+ * sits on a visually hidden copy that is also `aria-hidden` (so `*ByRole`
54
+ * skips it) while the text itself is in the DOM twice (so `*ByText` finds two).
55
+ * Hover and keyboard focus both have to open it.
56
+ */
57
+ const expectTooltip = async (text: string, canvas: ReturnType<typeof within>) => {
58
+ const button = canvas.getByRole('button', { name: text })
59
+
60
+ const tooltip = () => document.querySelector('[data-reka-popper-content-wrapper]')
61
+
62
+ expect(button).toHaveAttribute('data-state', 'closed')
63
+
64
+ // Keyboard first, from a state the pointer has not touched yet.
65
+ button.focus()
66
+ await waitFor(() => expect(tooltip()).toHaveTextContent(text))
67
+ expect(button).toHaveAttribute('aria-describedby')
68
+
69
+ button.blur()
70
+ await waitFor(() => expect(button).toHaveAttribute('data-state', 'closed'))
71
+
72
+ await userEvent.hover(button)
73
+ await waitFor(() => expect(tooltip()).toHaveTextContent(text))
74
+ }
75
+
76
+ /**
77
+ * Opening the four fields slides the second row out from behind the first, so
78
+ * the block grows instead of jumping. Both rows keep the same column widths.
79
+ */
80
+ export const TogglesOpen: Story = {
81
+ render: (args) => render(args, 'p-4'),
82
+ play: async ({ canvasElement }) => {
83
+ const canvas = within(canvasElement)
84
+ const row = canvasElement.querySelector('[data-testid="editor-padding-secondary-row"]')!
85
+ const toggleLine = canvas
86
+ .getByRole('button', { name: 'Padding per zijde' })
87
+ .querySelector('line')!
88
+ const lengthBefore = toggleLine.getAttribute('x1')
89
+
90
+ expect(row.className).toContain('grid-rows-[0fr]')
91
+ expect(row.getAttribute('inert')).not.toBeNull()
92
+
93
+ await expectTooltip('Padding per zijde', canvas)
94
+
95
+ await userEvent.click(canvas.getByRole('button', { name: 'Padding per zijde' }))
96
+
97
+ await waitFor(() => expect(row.className).toContain('grid-rows-[1fr]'))
98
+ expect(row.getAttribute('inert')).toBeNull()
99
+
100
+ // The second row carries its own grid, so its columns are only guaranteed
101
+ // to line up with the first row as long as both use the same template.
102
+ const [first, , third] = fields(canvas)
103
+ expect(third.getBoundingClientRect().width).toBe(first.getBoundingClientRect().width)
104
+
105
+ // The second row is clipped to make the 0fr collapse work, so its fields
106
+ // need room left over for a focus ring inside that clipping box.
107
+ const clip = row.firstElementChild!.getBoundingClientRect()
108
+ const field = third.parentElement!.getBoundingClientRect()
109
+ expect(clip.left).toBeLessThanOrEqual(field.left - 2)
110
+ expect(clip.right).toBeGreaterThanOrEqual(field.right + 2)
111
+
112
+ // The toggle is four-sided in both states, so its lines must not change
113
+ // length when it flips -- only the field icons swap which lines are lit,
114
+ // and they fade rather than cut.
115
+ expect(toggleLine.getAttribute('x1')).toBe(lengthBefore)
116
+
117
+ const fieldLine = first.previousElementSibling!.querySelector('line')!
118
+ expect(getComputedStyle(fieldLine).transitionProperty).toContain('opacity')
119
+ await waitFor(() => expect(getComputedStyle(fieldLine).opacity).toBe('1'))
120
+ },
121
+ }
122
+
123
+ /**
124
+ * A value that two fields cannot express, so the four fields open on mount.
125
+ * `pt-4 pr-2 pb-4 pl-2` would *not* open them: that is `px-2 py-4`, which the
126
+ * collapsed pair says in full. Reads 16 / 8 / 24 / 8 px.
127
+ */
128
+ export const Expanded: Story = {
129
+ render: (args) => render(args, 'pt-4 pr-2 pb-6 pl-2'),
130
+ }
131
+
132
+ /**
133
+ * The widest the fields ever get: three digits and the unit. The story renders
134
+ * at the same width as every other one; the narrow case is measured in the play,
135
+ * because a clipped number is silent -- the value scrolls inside its own box and
136
+ * nothing looks broken.
137
+ */
138
+ export const MaxValue: Story = {
139
+ render: (args) => render(args, 'p-96'),
140
+ play: async ({ canvasElement }) => {
141
+ const canvas = within(canvasElement)
142
+ const [horizontal, vertical] = fields(canvas)
143
+
144
+ expect(horizontal.value).toBe('384')
145
+
146
+ // Poppins is what the numbers are measured in, and a fallback font is a
147
+ // different width: without this the margin below is measured against
148
+ // whatever happened to be loaded.
149
+ await document.fonts.ready
150
+
151
+ // The decorator is a fixed `w-100`, so narrowing the canvas would change
152
+ // nothing -- the wrapper itself has to shrink. Inline, and put back
153
+ // afterwards, so the story looks like all the others in Storybook.
154
+ const wrapper = canvasElement.firstElementChild as HTMLElement
155
+ const width = wrapper.style.width
156
+
157
+ try {
158
+ // A real editor sidebar column, narrower than the story renders at.
159
+ wrapper.style.width = '256px'
160
+
161
+ const field = horizontal.parentElement!.getBoundingClientRect()
162
+ const unit = horizontal.nextElementSibling!.getBoundingClientRect()
163
+
164
+ // Nothing is clipped. `scrollWidth` bottoms out at the padding box, so it
165
+ // can only prove the absence of overflow, never how much room is left.
166
+ expect(horizontal.scrollWidth).toBeLessThanOrEqual(horizontal.clientWidth)
167
+
168
+ // Room to spare, measured against the text itself: at 0 the next font or
169
+ // the next digit clips the value with no other symptom. The ruler carries
170
+ // the same utilities and sits in the same font context as the input.
171
+ const ruler = document.createElement('span')
172
+ ruler.className = 'small-normal tabular-nums'
173
+ ruler.style.cssText = 'position:absolute;visibility:hidden;white-space:pre'
174
+ ruler.textContent = horizontal.value
175
+ horizontal.parentElement!.append(ruler)
176
+
177
+ const text = ruler.getBoundingClientRect().width
178
+ ruler.remove()
179
+
180
+ // Measured at 6px with Poppins loaded; 4 is the floor worth defending.
181
+ expect(horizontal.clientWidth - text).toBeGreaterThanOrEqual(4)
182
+
183
+ // The unit sits behind the number and inside the field, never clipped by
184
+ // the rounded box or overlapping the value.
185
+ expect(unit.left).toBeGreaterThanOrEqual(horizontal.getBoundingClientRect().right - 1)
186
+ expect(unit.right).toBeLessThanOrEqual(field.right)
187
+
188
+ // Both fields share the 1fr columns, so neither grows with its content.
189
+ expect(vertical.parentElement!.getBoundingClientRect().width).toBe(field.width)
190
+ } finally {
191
+ wrapper.style.width = width
192
+ }
193
+ },
194
+ }
195
+
196
+ /**
197
+ * Closing the four fields is a decision, not just a view change: the two fields
198
+ * that stay on screen are the ones the user can see, so the hidden sides follow
199
+ * them and the value is rewritten at once. Opening is the harmless direction.
200
+ */
201
+ export const CollapsesToVisibleValues: Story = {
202
+ render: (args) => render(args, 'pt-4 pr-2 pb-6 pl-2'),
203
+ play: async ({ canvasElement }) => {
204
+ const canvas = within(canvasElement)
205
+
206
+ expect(classString(canvasElement)).toBe('pt-4 pr-2 pb-6 pl-2')
207
+ expect(fields(canvas).map((field) => field.value)).toEqual(['16', '8', '24', '8'])
208
+
209
+ await userEvent.click(canvas.getByRole('button', { name: 'Padding per zijde' }))
210
+
211
+ // Onder followed Boven and Links followed Rechts, so the 24 is gone.
212
+ await waitFor(() => expect(classString(canvasElement)).toBe('px-2 py-4'))
213
+ const [horizontal, vertical] = fields(canvas)
214
+ expect(horizontal.value).toBe('8')
215
+ expect(vertical.value).toBe('16')
216
+
217
+ // And opening again only shows what is already there.
218
+ await userEvent.click(canvas.getByRole('button', { name: 'Padding per zijde' }))
219
+ await waitFor(() => expect(fields(canvas)[2].value).toBe('16'))
220
+ expect(classString(canvasElement)).toBe('px-2 py-4')
221
+ },
222
+ }
223
+
224
+ /**
225
+ * Typing is left alone until it is confirmed: the value only snaps to a step on
226
+ * blur or Enter, so a "3" on the way to "30" is not snapped to 4 under the
227
+ * caret. The arrow keys skip that wait -- they step the list itself.
228
+ */
229
+ export const SnapsOnCommit: Story = {
230
+ render: (args) => render(args, 'p-4'),
231
+ play: async ({ canvasElement }) => {
232
+ const canvas = within(canvasElement)
233
+ const [horizontal] = fields(canvas)
234
+
235
+ // Focus selects the value, so the first digit replaces it instead of
236
+ // landing next to it.
237
+ horizontal.focus()
238
+ expect(horizontal.selectionStart).toBe(0)
239
+ expect(horizontal.selectionEnd).toBe(horizontal.value.length)
240
+
241
+ await userEvent.clear(horizontal)
242
+ await userEvent.type(horizontal, '30')
243
+
244
+ // Mid-edit: the field shows what was typed and the model is untouched.
245
+ expect(horizontal.value).toBe('30')
246
+ expect(classString(canvasElement)).toBe('p-4')
247
+
248
+ // 30 sits between 28 and 32, and the tie-free nearer of the two wins.
249
+ await userEvent.tab()
250
+ await waitFor(() => expect(horizontal.value).toBe('28'))
251
+ expect(classString(canvasElement)).toBe('px-7 py-4')
252
+
253
+ // A tie goes down: 26 is as far from 24 as it is from 28.
254
+ await userEvent.clear(horizontal)
255
+ await userEvent.type(horizontal, '26')
256
+ await userEvent.tab()
257
+ await waitFor(() => expect(horizontal.value).toBe('24'))
258
+ expect(classString(canvasElement)).toBe('px-6 py-4')
259
+
260
+ // Arrows write straight away, no blur needed.
261
+ horizontal.focus()
262
+ await userEvent.keyboard('{ArrowUp}')
263
+ await waitFor(() => expect(horizontal.value).toBe('28'))
264
+ expect(classString(canvasElement)).toBe('px-7 py-4')
265
+
266
+ // Escape is an undo of the typing, not of the value.
267
+ await userEvent.clear(horizontal)
268
+ await userEvent.type(horizontal, '9')
269
+ await userEvent.keyboard('{Escape}')
270
+ await waitFor(() => expect(horizontal.value).toBe('28'))
271
+ expect(classString(canvasElement)).toBe('px-7 py-4')
272
+ },
273
+ }
274
+
275
+ /**
276
+ * Top and bottom only, for an element where horizontal padding has no sensible
277
+ * effect. Collapsed it is one field writing `py-N`; the toggle shows Boven and
278
+ * Onder in the same row, written as `pt-N pb-N`. The incoming `px-6` is not
279
+ * shown and drops out on the first write.
280
+ */
281
+ export const Vertical: Story = {
282
+ args: { sides: 'vertical', label: 'Witruimte' },
283
+ render: (args) => render(args, 'px-6 py-4'),
284
+ play: async ({ canvasElement }) => {
285
+ const canvas = within(canvasElement)
286
+
287
+ expect(fields(canvas)).toHaveLength(1)
288
+ const [field] = fields(canvas)
289
+ expect(field).toHaveAccessibleName('Verticaal (px)')
290
+ expect(field.value).toBe('16')
291
+
292
+ // The single field spans both field columns, up to the toggle.
293
+ const toggle = canvas.getByRole('button', { name: 'Padding per zijde' })
294
+ const box = field.parentElement!.getBoundingClientRect()
295
+ expect(box.right).toBeLessThan(toggle.getBoundingClientRect().left)
296
+ expect(box.width).toBeGreaterThan(toggle.getBoundingClientRect().width * 2)
297
+
298
+ field.focus()
299
+ await userEvent.keyboard('{ArrowUp}')
300
+ await waitFor(() => expect(classString(canvasElement)).toBe('py-5'))
301
+
302
+ await userEvent.click(toggle)
303
+ await waitFor(() => expect(fields(canvas)).toHaveLength(2))
304
+ const [top, bottom] = fields(canvas)
305
+ expect(top).toHaveAccessibleName('Boven (px)')
306
+ expect(bottom).toHaveAccessibleName('Onder (px)')
307
+
308
+ // Same row: the two fields share a top edge.
309
+ expect(bottom.getBoundingClientRect().top).toBe(top.getBoundingClientRect().top)
310
+
311
+ bottom.focus()
312
+ await userEvent.keyboard('{ArrowUp}')
313
+ await waitFor(() => expect(classString(canvasElement)).toBe('pt-5 pb-6'))
314
+ },
315
+ }
316
+
317
+ /**
318
+ * The mirror image of `vertical`, for a standing divider in a row: left and
319
+ * right only. Collapsed it is one field writing `px-N`; the toggle shows Links
320
+ * and Rechts in the same row, written as `pl-N pr-N`. The incoming `py-6` is
321
+ * not shown and drops out on the first write.
322
+ */
323
+ export const Horizontal: Story = {
324
+ args: {
325
+ sides: 'horizontal',
326
+ label: 'Witruimte',
327
+ messages: { horizontal: 'Links en rechts', toggle: 'Links en rechts apart' },
328
+ },
329
+ render: (args) => render(args, 'px-4 py-6'),
330
+ play: async ({ canvasElement }) => {
331
+ const canvas = within(canvasElement)
332
+
333
+ expect(fields(canvas)).toHaveLength(1)
334
+ const [field] = fields(canvas)
335
+ expect(field).toHaveAccessibleName('Links en rechts (px)')
336
+ expect(field.value).toBe('16')
337
+
338
+ field.focus()
339
+ await userEvent.keyboard('{ArrowUp}')
340
+ await waitFor(() => expect(classString(canvasElement)).toBe('px-5'))
341
+
342
+ const toggle = canvas.getByRole('button', { name: 'Links en rechts apart' })
343
+ await userEvent.click(toggle)
344
+ await waitFor(() => expect(fields(canvas)).toHaveLength(2))
345
+ const [left, right] = fields(canvas)
346
+ expect(left).toHaveAccessibleName('Links (px)')
347
+ expect(right).toHaveAccessibleName('Rechts (px)')
348
+ expect(right.getBoundingClientRect().top).toBe(left.getBoundingClientRect().top)
349
+
350
+ right.focus()
351
+ await userEvent.keyboard('{ArrowUp}')
352
+ await waitFor(() => expect(classString(canvasElement)).toBe('pl-5 pr-6'))
353
+ },
354
+ }
355
+
356
+ export const CustomMessages: Story = {
357
+ args: {
358
+ label: 'Padding',
359
+ messages: {
360
+ horizontal: 'Horizontal',
361
+ vertical: 'Vertical',
362
+ top: 'Top',
363
+ right: 'Right',
364
+ bottom: 'Bottom',
365
+ left: 'Left',
366
+ toggle: 'Separate sides',
367
+ unit: 'px',
368
+ },
369
+ },
370
+ render: (args) => render(args, 'p-4'),
371
+ // The tooltip says the same thing as the accessible name, so overriding
372
+ // `toggle` has to move both.
373
+ play: async ({ canvasElement }) => expectTooltip('Separate sides', within(canvasElement)),
374
+ }