@nexxtmove/ui 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/editor/molecules/EditorBorder/EditorBorder.vue.d.ts +12 -0
- package/dist/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue.d.ts +16 -0
- package/dist/index.js +1094 -1135
- package/dist/nuxt.js +0 -1
- package/package.json +38 -36
- package/src/attribute-contract.test.ts +151 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
- package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
- package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
- package/src/components/atoms/Button/Button.stories.ts +290 -0
- package/src/components/atoms/Button/Button.test.ts +406 -0
- package/src/components/atoms/Card/Card.stories.ts +45 -0
- package/src/components/atoms/Card/Card.test.ts +46 -0
- package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
- package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
- package/src/components/atoms/Chip/Chip.stories.ts +274 -0
- package/src/components/atoms/Chip/Chip.test.ts +230 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
- package/src/components/atoms/Icon/Icon.stories.ts +63 -0
- package/src/components/atoms/Icon/Icon.test.ts +89 -0
- package/src/components/atoms/Loader/Loader.stories.ts +36 -0
- package/src/components/atoms/Loader/Loader.test.ts +40 -0
- package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
- package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
- package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
- package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
- package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
- package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
- package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
- package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
- package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
- package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
- package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
- package/src/components/atoms/Slider/Slider.stories.ts +151 -0
- package/src/components/atoms/Slider/Slider.test.ts +191 -0
- package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
- package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
- package/src/components/atoms/Switch/Switch.stories.ts +91 -0
- package/src/components/atoms/Switch/Switch.test.ts +183 -0
- package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
- package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
- package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.vue +58 -2
- package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +563 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +689 -0
- package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +793 -0
- package/src/components/editor/molecules/EditorColor/_ColorField.vue +7 -29
- package/src/components/editor/molecules/EditorColor/_ThemeRoleSwatches.vue +57 -0
- package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
- package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
- package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
- package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
- package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
- package/src/components/molecules/InputField/InputField.stories.ts +117 -0
- package/src/components/molecules/InputField/InputField.test.ts +138 -0
- package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
- package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
- package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
- package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
- package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
- package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
- package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
- package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
- package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
- package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
- package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
- package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
- package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
- package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
- package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
- package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
- package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
- package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
- package/src/components/molecules/Select/Select.stories.ts +264 -0
- package/src/components/molecules/Select/Select.test.ts +315 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
- package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
- package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
- package/src/components/molecules/Source/Source.stories.ts +35 -0
- package/src/components/molecules/Source/Source.test.ts +45 -0
- package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
- package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
- package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
- package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
- package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
- package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
- package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
- package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
- package/src/components/molecules/Toast/Toast.stories.ts +110 -0
- package/src/components/molecules/Toast/Toast.test.ts +103 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
- package/src/components/molecules/Tree/Tree.stories.ts +884 -0
- package/src/components/molecules/Tree/Tree.test.ts +1247 -0
- package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
- package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
- package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
- package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
- package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
- package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
- package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
- package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
- package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
- package/src/components/organisms/ColorPicker/ColorPicker.test.ts +638 -0
- package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
- package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
- package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
- package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
- package/src/components/organisms/Combobox/Combobox.vue +1 -1
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
- package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
- package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
- package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
- package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
- package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
- package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
- package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
- package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
- package/src/components/organisms/Header/Header.stories.ts +75 -0
- package/src/components/organisms/Header/Header.test.ts +233 -0
- package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
- package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
- package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
- package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
- package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
- package/src/components/organisms/Modal/Modal.stories.ts +350 -0
- package/src/components/organisms/Modal/Modal.test.ts +275 -0
- package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
- package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
- package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
- package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
- package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
- package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
- package/src/components/organisms/Table/Table.stories.ts +332 -0
- package/src/components/organisms/Table/Table.test.ts +320 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
- package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
- package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
- package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
- package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
import EditorGap from './EditorGap.vue'
|
|
5
|
+
import type { EditorGapValue } from './EditorGap.vue'
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/Editor Components/Molecules/Editor Gap',
|
|
9
|
+
component: EditorGap,
|
|
10
|
+
decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
|
|
11
|
+
argTypes: {
|
|
12
|
+
label: { control: 'text' },
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof EditorGap>
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof EditorGap>
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
render: (args) => ({
|
|
20
|
+
components: { EditorGap },
|
|
21
|
+
setup() {
|
|
22
|
+
const gap = ref<EditorGapValue>('gap-4')
|
|
23
|
+
return { args, gap }
|
|
24
|
+
},
|
|
25
|
+
// The value is shown as-is: it is the Tailwind class the editor applies.
|
|
26
|
+
template: `
|
|
27
|
+
<div>
|
|
28
|
+
<EditorGap v-bind="args" v-model="gap" />
|
|
29
|
+
<p class="small-normal text-gray-700">{{ gap }}</p>
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
}),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const CustomMessages: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
label: 'Gap',
|
|
38
|
+
messages: { unit: 'px' },
|
|
39
|
+
},
|
|
40
|
+
render: (args) => ({
|
|
41
|
+
components: { EditorGap },
|
|
42
|
+
setup() {
|
|
43
|
+
const gap = ref<EditorGapValue>('gap-4')
|
|
44
|
+
return { args, gap }
|
|
45
|
+
},
|
|
46
|
+
template: `<EditorGap v-bind="args" v-model="gap" />`,
|
|
47
|
+
}),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* axe in error mode on the Auto stories. The toolbar is NexxtToggleGroup, whose
|
|
52
|
+
* chosen label fails color-contrast on its own (cornflower-600 text, 4.31:1 on
|
|
53
|
+
* white); that is ToggleGroup's, not this block's, so it is left out here.
|
|
54
|
+
*/
|
|
55
|
+
const AUTO_A11Y = { a11y: { test: 'error', context: { exclude: ['[role="toolbar"]'] } } }
|
|
56
|
+
|
|
57
|
+
/** The slider and the readout of one EditorGap, by the readout's text. */
|
|
58
|
+
const sliderRow = (block: HTMLElement) => {
|
|
59
|
+
const readout = within(block).getByText(/\d+ px/)
|
|
60
|
+
const slider = readout.parentElement!.firstElementChild!
|
|
61
|
+
const box = (el: Element) => {
|
|
62
|
+
const { left, width } = el.getBoundingClientRect()
|
|
63
|
+
return { left, width }
|
|
64
|
+
}
|
|
65
|
+
return { slider: box(slider), readout: box(readout) }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* With the auto model bound: Vast | Auto above the slider, on Vast. Below it,
|
|
70
|
+
* for comparison, the same block without the model: the slider and the readout
|
|
71
|
+
* keep their width and place in all three.
|
|
72
|
+
*/
|
|
73
|
+
export const WithAuto: Story = {
|
|
74
|
+
parameters: AUTO_A11Y,
|
|
75
|
+
render: (args) => ({
|
|
76
|
+
components: { EditorGap },
|
|
77
|
+
setup() {
|
|
78
|
+
const gap = ref<EditorGapValue>('gap-4')
|
|
79
|
+
const auto = ref(false)
|
|
80
|
+
return { args, gap, auto }
|
|
81
|
+
},
|
|
82
|
+
template: `
|
|
83
|
+
<div>
|
|
84
|
+
<EditorGap v-bind="args" v-model="gap" v-model:auto="auto" data-variant="auto" />
|
|
85
|
+
<EditorGap v-bind="args" v-model="gap" data-variant="plain" />
|
|
86
|
+
<p class="small-normal text-gray-700">{{ gap }}, auto: {{ auto }}</p>
|
|
87
|
+
</div>
|
|
88
|
+
`,
|
|
89
|
+
}),
|
|
90
|
+
play: async ({ canvasElement }) => {
|
|
91
|
+
const canvas = within(canvasElement)
|
|
92
|
+
const block = (variant: string) =>
|
|
93
|
+
canvasElement.querySelector(`[data-variant="${variant}"]`) as HTMLElement
|
|
94
|
+
const settle = () =>
|
|
95
|
+
new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
|
96
|
+
|
|
97
|
+
await document.fonts.ready
|
|
98
|
+
await settle()
|
|
99
|
+
const plain = sliderRow(block('plain'))
|
|
100
|
+
const vast = sliderRow(block('auto'))
|
|
101
|
+
await expect(vast).toEqual(plain)
|
|
102
|
+
await expect(
|
|
103
|
+
within(block('auto')).getByRole('slider', { name: 'Tussenruimte' }),
|
|
104
|
+
).toBeInTheDocument()
|
|
105
|
+
|
|
106
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Auto' }))
|
|
107
|
+
await settle()
|
|
108
|
+
|
|
109
|
+
await expect(sliderRow(block('auto'))).toEqual(plain)
|
|
110
|
+
await expect(
|
|
111
|
+
within(block('auto')).getByRole('slider', { name: 'Tussenruimte, minimaal' }),
|
|
112
|
+
).toBeInTheDocument()
|
|
113
|
+
await expect(canvas.getByText('gap-4, auto: true')).toBeInTheDocument()
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Auto on: the slider sets the minimum gap. */
|
|
118
|
+
export const AutoOn: Story = {
|
|
119
|
+
parameters: AUTO_A11Y,
|
|
120
|
+
render: (args) => ({
|
|
121
|
+
components: { EditorGap },
|
|
122
|
+
setup() {
|
|
123
|
+
const gap = ref<EditorGapValue>('gap-6')
|
|
124
|
+
const auto = ref(true)
|
|
125
|
+
return { args, gap, auto }
|
|
126
|
+
},
|
|
127
|
+
template: `<EditorGap v-bind="args" v-model="gap" v-model:auto="auto" />`,
|
|
128
|
+
}),
|
|
129
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import EditorGap, { EDITOR_GAP_STEPS } from './EditorGap.vue'
|
|
4
|
+
import type { EditorGapValue } from './EditorGap.vue'
|
|
5
|
+
import Slider from '../../../atoms/Slider/Slider.vue'
|
|
6
|
+
|
|
7
|
+
// jsdom ships no ResizeObserver, and reka-ui's slider observes its track to place the thumbs
|
|
8
|
+
vi.stubGlobal(
|
|
9
|
+
'ResizeObserver',
|
|
10
|
+
class {
|
|
11
|
+
observe() {}
|
|
12
|
+
unobserve() {}
|
|
13
|
+
disconnect() {}
|
|
14
|
+
},
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// jsdom implements no pointer capture either, and reka-ui captures the pointer on thumb pointerdown
|
|
18
|
+
Element.prototype.setPointerCapture ??= () => {}
|
|
19
|
+
Element.prototype.releasePointerCapture ??= () => {}
|
|
20
|
+
Element.prototype.hasPointerCapture ??= () => false
|
|
21
|
+
|
|
22
|
+
/** The index the slider sits on, read from the model handed to the slider. */
|
|
23
|
+
const sliderIndex = (wrapper: ReturnType<typeof mount>) =>
|
|
24
|
+
wrapper.findComponent(Slider).props('modelValue')
|
|
25
|
+
|
|
26
|
+
describe('EditorGap', () => {
|
|
27
|
+
// No assertions on typography utilities: the label and the value are found by
|
|
28
|
+
// text or by structure, so restyling the block or the slider cannot turn
|
|
29
|
+
// these red.
|
|
30
|
+
it('renders the Dutch default label', () => {
|
|
31
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-4' } })
|
|
32
|
+
|
|
33
|
+
expect(wrapper.find('p').text()).toBe('Tussenruimte')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('renders the gap in pixels with the default unit', () => {
|
|
37
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-4' } })
|
|
38
|
+
|
|
39
|
+
expect(wrapper.text()).toContain('16 px')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('renders a custom label', () => {
|
|
43
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-4', label: 'Gap' } })
|
|
44
|
+
|
|
45
|
+
expect(wrapper.find('p').text()).toBe('Gap')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('renders a custom unit', () => {
|
|
49
|
+
const wrapper = mount(EditorGap, {
|
|
50
|
+
props: { modelValue: 'gap-4', messages: { unit: 'pixels' } },
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
expect(wrapper.text()).toContain('16 pixels')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// The scale skips 13 and 15, so the slider steps over the list rather than
|
|
57
|
+
// over the px value: gap-14 is the fourteenth step, not the fifteenth.
|
|
58
|
+
it('puts the slider on the index of the value, not on its number', () => {
|
|
59
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-14' } })
|
|
60
|
+
|
|
61
|
+
expect(sliderIndex(wrapper)).toEqual([13])
|
|
62
|
+
expect(wrapper.text()).toContain('56 px')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('spans the whole step list', () => {
|
|
66
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-0' } })
|
|
67
|
+
const slider = wrapper.findComponent(Slider)
|
|
68
|
+
|
|
69
|
+
expect(slider.props('min')).toBe(0)
|
|
70
|
+
expect(slider.props('max')).toBe(14)
|
|
71
|
+
expect(slider.props('step')).toBe(1)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('emits update:modelValue with the Tailwind class of the new step', async () => {
|
|
75
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-12' } })
|
|
76
|
+
|
|
77
|
+
// One step up from index 12 (gap-12) is index 13, which is gap-14.
|
|
78
|
+
wrapper.findComponent(Slider).vm.$emit('update:modelValue', [13])
|
|
79
|
+
await wrapper.vm.$nextTick()
|
|
80
|
+
|
|
81
|
+
expect(wrapper.emitted('update:modelValue')).toEqual([['gap-14']])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('falls back to the first step for a value outside the scale', () => {
|
|
85
|
+
// `gap-13` has no spacing token, so there is no step for it to sit on. The
|
|
86
|
+
// type rules it out, which is exactly why the runtime fallback is worth a
|
|
87
|
+
// test: a consumer restoring an old stored value gets past the types.
|
|
88
|
+
const outOfScale = 'gap-13' as unknown as EditorGapValue
|
|
89
|
+
const wrapper = mount(EditorGap, { props: { modelValue: outOfScale } })
|
|
90
|
+
|
|
91
|
+
expect(sliderIndex(wrapper)).toEqual([0])
|
|
92
|
+
expect(wrapper.text()).toContain('0 px')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// aria-valuenow is the step index, which is meaningless read aloud; the text
|
|
96
|
+
// the readout shows is what the thumb announces instead.
|
|
97
|
+
it('announces the gap in pixels on the thumb', () => {
|
|
98
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-4' } })
|
|
99
|
+
const thumb = wrapper.find('[role="slider"]')
|
|
100
|
+
|
|
101
|
+
expect(thumb.attributes('aria-valuetext')).toBe('16 px')
|
|
102
|
+
expect(thumb.attributes('aria-label')).toBe('Tussenruimte')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('is always visible, so it renders no switch', () => {
|
|
106
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-4' } })
|
|
107
|
+
|
|
108
|
+
expect(wrapper.find('input[type="checkbox"]').exists()).toBe(false)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// The consumer derives its class matchers from this list, so the list is a
|
|
113
|
+
// contract: any change here changes what the app recognises.
|
|
114
|
+
describe('EDITOR_GAP_STEPS', () => {
|
|
115
|
+
it('offers exactly the agreed steps', () => {
|
|
116
|
+
expect(EDITOR_GAP_STEPS).toEqual([
|
|
117
|
+
{ token: 0, px: 0 },
|
|
118
|
+
{ token: 1, px: 4 },
|
|
119
|
+
{ token: 2, px: 8 },
|
|
120
|
+
{ token: 3, px: 12 },
|
|
121
|
+
{ token: 4, px: 16 },
|
|
122
|
+
{ token: 5, px: 20 },
|
|
123
|
+
{ token: 6, px: 24 },
|
|
124
|
+
{ token: 7, px: 28 },
|
|
125
|
+
{ token: 8, px: 32 },
|
|
126
|
+
{ token: 9, px: 36 },
|
|
127
|
+
{ token: 10, px: 40 },
|
|
128
|
+
{ token: 11, px: 44 },
|
|
129
|
+
{ token: 12, px: 48 },
|
|
130
|
+
{ token: 14, px: 56 },
|
|
131
|
+
{ token: 16, px: 64 },
|
|
132
|
+
])
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('EditorGap with auto', () => {
|
|
137
|
+
// ToggleGroup awaits document.fonts.ready on mount, which jsdom does not ship.
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
Object.defineProperty(document, 'fonts', {
|
|
140
|
+
value: { ready: Promise.resolve() },
|
|
141
|
+
configurable: true,
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const mountAuto = (auto: boolean, props: Record<string, unknown> = {}) =>
|
|
146
|
+
mount(EditorGap, { props: { modelValue: 'gap-4', auto, ...props } })
|
|
147
|
+
|
|
148
|
+
const toolbar = (wrapper: ReturnType<typeof mount>) => wrapper.find('[role="toolbar"]')
|
|
149
|
+
|
|
150
|
+
const pressed = (wrapper: ReturnType<typeof mount>) =>
|
|
151
|
+
toolbar(wrapper)
|
|
152
|
+
.findAll('button[aria-pressed="true"]')
|
|
153
|
+
.map((button) => button.text())
|
|
154
|
+
|
|
155
|
+
it('offers no choice without the auto model', () => {
|
|
156
|
+
const wrapper = mount(EditorGap, { props: { modelValue: 'gap-4' } })
|
|
157
|
+
|
|
158
|
+
expect(toolbar(wrapper).exists()).toBe(false)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Named apart from the slider, which carries the block label on Vast.
|
|
162
|
+
it('offers Vast and Auto in the block, under a name of their own', () => {
|
|
163
|
+
const wrapper = mountAuto(false)
|
|
164
|
+
|
|
165
|
+
expect(toolbar(wrapper).attributes('aria-label')).toBe('Soort tussenruimte')
|
|
166
|
+
expect(pressed(wrapper)).toEqual(['Vast'])
|
|
167
|
+
expect(wrapper.findAll('p').map((p) => p.text())).toEqual(['Tussenruimte'])
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('keeps the slider named by the block label on Vast', () => {
|
|
171
|
+
const wrapper = mountAuto(false)
|
|
172
|
+
|
|
173
|
+
expect(wrapper.get('[role="slider"]').attributes('aria-label')).toBe('Tussenruimte')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('names the slider as the minimum on Auto, without a visible field label', () => {
|
|
177
|
+
const wrapper = mountAuto(true)
|
|
178
|
+
|
|
179
|
+
expect(pressed(wrapper)).toEqual(['Auto'])
|
|
180
|
+
expect(wrapper.get('[role="slider"]').attributes('aria-label')).toBe('Tussenruimte, minimaal')
|
|
181
|
+
expect(wrapper.text()).not.toContain('inimaal')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// The slider row has the same structure and classes as without the model, so
|
|
185
|
+
// it cannot change width or place between the three variants. Compared by
|
|
186
|
+
// structure: reka-ui positions its thumb a tick after mount.
|
|
187
|
+
it('renders the slider row as without the model, on Vast and on Auto', async () => {
|
|
188
|
+
const shape = (wrapper: ReturnType<typeof mount>) => {
|
|
189
|
+
const row = wrapper.findAll('span').find((span) => span.text() === '16 px')!.element
|
|
190
|
+
.parentElement!
|
|
191
|
+
return [row.className, ...[...row.children].map((child) => child.className)]
|
|
192
|
+
}
|
|
193
|
+
const plain = shape(mount(EditorGap, { props: { modelValue: 'gap-4' } }))
|
|
194
|
+
const withAuto = mountAuto(false)
|
|
195
|
+
|
|
196
|
+
expect(shape(withAuto)).toEqual(plain)
|
|
197
|
+
await withAuto.setProps({ auto: true })
|
|
198
|
+
expect(shape(withAuto)).toEqual(plain)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('emits only the auto model when switched, never the gap', async () => {
|
|
202
|
+
const wrapper = mountAuto(false)
|
|
203
|
+
const auto = toolbar(wrapper)
|
|
204
|
+
.findAll('button')
|
|
205
|
+
.find((button) => button.text() === 'Auto')!
|
|
206
|
+
|
|
207
|
+
await auto.trigger('click')
|
|
208
|
+
|
|
209
|
+
expect(wrapper.emitted('update:auto')).toEqual([[true]])
|
|
210
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('keeps the slider on the same step in both states', async () => {
|
|
214
|
+
const wrapper = mountAuto(false, { modelValue: 'gap-6' })
|
|
215
|
+
const before = sliderIndex(wrapper)
|
|
216
|
+
|
|
217
|
+
await wrapper.setProps({ auto: true })
|
|
218
|
+
|
|
219
|
+
expect(sliderIndex(wrapper)).toEqual(before)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('takes the new copy from messages, per key', () => {
|
|
223
|
+
const wrapper = mountAuto(true, {
|
|
224
|
+
messages: {
|
|
225
|
+
fixed: 'Fixed',
|
|
226
|
+
minimumName: 'Minimum gap',
|
|
227
|
+
modeName: 'Gap type',
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
expect(
|
|
232
|
+
toolbar(wrapper)
|
|
233
|
+
.findAll('button[aria-pressed]')
|
|
234
|
+
.map((b) => b.text()),
|
|
235
|
+
).toEqual(['Fixed', 'Auto'])
|
|
236
|
+
expect(wrapper.get('[role="slider"]').attributes('aria-label')).toBe('Minimum gap')
|
|
237
|
+
expect(toolbar(wrapper).attributes('aria-label')).toBe('Gap type')
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// Recorded before the optional `auto` model existed: without it the markup has
|
|
242
|
+
// to stay what consumers already ship, also for a value outside the scale.
|
|
243
|
+
describe('EditorGap markup without auto', () => {
|
|
244
|
+
it.each([
|
|
245
|
+
['a value on the scale', 'gap-6'],
|
|
246
|
+
['a value outside the scale', 'gap-13'],
|
|
247
|
+
] as const)('renders unchanged with %s', (_, value) => {
|
|
248
|
+
const wrapper = mount(EditorGap, { props: { modelValue: value as EditorGapValue } })
|
|
249
|
+
|
|
250
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
251
|
+
wrapper.unmount()
|
|
252
|
+
})
|
|
253
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
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 EditorHeadingType from './EditorHeadingType.vue'
|
|
5
|
+
import type { EditorHeadingTypeValue } from './EditorHeadingType.vue'
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/Editor Components/Molecules/Editor Heading Type',
|
|
9
|
+
component: EditorHeadingType,
|
|
10
|
+
decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
|
|
11
|
+
argTypes: {
|
|
12
|
+
label: { control: 'text' },
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof EditorHeadingType>
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof EditorHeadingType>
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
render: (args) => ({
|
|
20
|
+
components: { EditorHeadingType },
|
|
21
|
+
setup() {
|
|
22
|
+
const heading = ref<EditorHeadingTypeValue>('text-4xl')
|
|
23
|
+
return { args, heading }
|
|
24
|
+
},
|
|
25
|
+
// The value is shown as-is: it is the Tailwind class the editor applies.
|
|
26
|
+
template: `
|
|
27
|
+
<div>
|
|
28
|
+
<EditorHeadingType v-bind="args" v-model="heading" />
|
|
29
|
+
<p class="small-normal text-gray-700">{{ heading }}</p>
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
}),
|
|
33
|
+
play: async ({ canvasElement }) => {
|
|
34
|
+
const canvas = within(canvasElement)
|
|
35
|
+
|
|
36
|
+
await userEvent.click(canvas.getByRole('combobox'))
|
|
37
|
+
await waitFor(() => expect(canvas.getByRole('listbox')).toBeInTheDocument())
|
|
38
|
+
|
|
39
|
+
// Picked by its text rather than by accessible name: the option's icon is a
|
|
40
|
+
// Font Awesome glyph, and the kit's CSS is supplied by the consumer, so the
|
|
41
|
+
// name computation would depend on whether it happens to be loaded.
|
|
42
|
+
await userEvent.click(canvas.getByText('Heading 3'))
|
|
43
|
+
|
|
44
|
+
await waitFor(() => expect(canvas.getByText('text-2xl')).toBeInTheDocument())
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const CustomMessages: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
label: 'Kopniveau',
|
|
51
|
+
messages: {
|
|
52
|
+
h1: 'Kop 1',
|
|
53
|
+
h2: 'Kop 2',
|
|
54
|
+
h3: 'Kop 3',
|
|
55
|
+
h4: 'Kop 4',
|
|
56
|
+
h5: 'Kop 5',
|
|
57
|
+
h6: 'Kop 6',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
render: (args) => ({
|
|
61
|
+
components: { EditorHeadingType },
|
|
62
|
+
setup() {
|
|
63
|
+
const heading = ref<EditorHeadingTypeValue>('text-4xl')
|
|
64
|
+
return { args, heading }
|
|
65
|
+
},
|
|
66
|
+
template: `<EditorHeadingType v-bind="args" v-model="heading" />`,
|
|
67
|
+
}),
|
|
68
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils'
|
|
3
|
+
import { ref, nextTick } from 'vue'
|
|
4
|
+
import EditorHeadingType, {
|
|
5
|
+
EDITOR_HEADING_TYPE_OPTIONS,
|
|
6
|
+
type EditorHeadingTypeValue,
|
|
7
|
+
} from './EditorHeadingType.vue'
|
|
8
|
+
|
|
9
|
+
// Select positions its list with floating-ui, which needs a real layout engine.
|
|
10
|
+
vi.mock('@floating-ui/vue', async (importOriginal) => {
|
|
11
|
+
const actual = await importOriginal<typeof import('@floating-ui/vue')>()
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
useFloating: () => ({
|
|
15
|
+
floatingStyles: ref({}),
|
|
16
|
+
placement: ref('bottom-start'),
|
|
17
|
+
isPositioned: ref(true),
|
|
18
|
+
}),
|
|
19
|
+
autoUpdate: () => () => {},
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
/** Text of the closed field, which is the label of the selected option. */
|
|
24
|
+
const fieldText = (wrapper: VueWrapper) => wrapper.find('[role="combobox"]').text()
|
|
25
|
+
|
|
26
|
+
const open = async (wrapper: VueWrapper) => {
|
|
27
|
+
await wrapper.find('[role="combobox"]').trigger('click')
|
|
28
|
+
await flushPromises()
|
|
29
|
+
await nextTick()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const optionLabels = (wrapper: VueWrapper) =>
|
|
33
|
+
wrapper.findAll('[role="option"]').map((option) => option.text())
|
|
34
|
+
|
|
35
|
+
describe('EditorHeadingType', () => {
|
|
36
|
+
// No assertions on typography utilities: labels are found by text or by
|
|
37
|
+
// structure, so restyling the block or the select cannot turn these red.
|
|
38
|
+
it('renders the default label', () => {
|
|
39
|
+
const wrapper = mount(EditorHeadingType, { props: { modelValue: 'text-4xl' } })
|
|
40
|
+
|
|
41
|
+
expect(wrapper.find('p').text()).toBe('Type')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('renders a custom label', () => {
|
|
45
|
+
const wrapper = mount(EditorHeadingType, {
|
|
46
|
+
props: { modelValue: 'text-4xl', label: 'Kopniveau' },
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(wrapper.find('p').text()).toBe('Kopniveau')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('shows the label of the selected heading in the field', () => {
|
|
53
|
+
const wrapper = mount(EditorHeadingType, { props: { modelValue: 'text-4xl' } })
|
|
54
|
+
|
|
55
|
+
expect(fieldText(wrapper)).toBe('Heading 1')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('offers every heading level with its default label', async () => {
|
|
59
|
+
const wrapper = mount(EditorHeadingType, { props: { modelValue: 'text-4xl' } })
|
|
60
|
+
await open(wrapper)
|
|
61
|
+
|
|
62
|
+
expect(optionLabels(wrapper)).toEqual([
|
|
63
|
+
'Heading 1',
|
|
64
|
+
'Heading 2',
|
|
65
|
+
'Heading 3',
|
|
66
|
+
'Heading 4',
|
|
67
|
+
'Heading 5',
|
|
68
|
+
'Heading 6',
|
|
69
|
+
])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('renders custom option labels', async () => {
|
|
73
|
+
const wrapper = mount(EditorHeadingType, {
|
|
74
|
+
props: {
|
|
75
|
+
modelValue: 'text-4xl',
|
|
76
|
+
messages: {
|
|
77
|
+
h1: 'Kop 1',
|
|
78
|
+
h2: 'Kop 2',
|
|
79
|
+
h3: 'Kop 3',
|
|
80
|
+
h4: 'Kop 4',
|
|
81
|
+
h5: 'Kop 5',
|
|
82
|
+
h6: 'Kop 6',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
await open(wrapper)
|
|
87
|
+
|
|
88
|
+
expect(optionLabels(wrapper)).toEqual(['Kop 1', 'Kop 2', 'Kop 3', 'Kop 4', 'Kop 5', 'Kop 6'])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('keeps the untouched defaults when only one message is overridden', async () => {
|
|
92
|
+
const wrapper = mount(EditorHeadingType, {
|
|
93
|
+
props: { modelValue: 'text-4xl', messages: { h3: 'Kop 3' } },
|
|
94
|
+
})
|
|
95
|
+
await open(wrapper)
|
|
96
|
+
|
|
97
|
+
expect(optionLabels(wrapper)).toEqual([
|
|
98
|
+
'Heading 1',
|
|
99
|
+
'Heading 2',
|
|
100
|
+
'Kop 3',
|
|
101
|
+
'Heading 4',
|
|
102
|
+
'Heading 5',
|
|
103
|
+
'Heading 6',
|
|
104
|
+
])
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('emits update:modelValue with the Tailwind class of the chosen option', async () => {
|
|
108
|
+
const wrapper = mount(EditorHeadingType, { props: { modelValue: 'text-4xl' } })
|
|
109
|
+
await open(wrapper)
|
|
110
|
+
|
|
111
|
+
await wrapper.findAll('[role="option"]')[2].trigger('click')
|
|
112
|
+
|
|
113
|
+
expect(wrapper.emitted('update:modelValue')).toEqual([['text-2xl']])
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('follows the model when the parent changes it', async () => {
|
|
117
|
+
const wrapper = mount(EditorHeadingType, { props: { modelValue: 'text-4xl' } })
|
|
118
|
+
|
|
119
|
+
await wrapper.setProps({ modelValue: 'text-lg' })
|
|
120
|
+
|
|
121
|
+
expect(fieldText(wrapper)).toBe('Heading 5')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// A stored value from before this component, or from a scale it does not
|
|
125
|
+
// offer, must not be written back through the model as if it were a choice.
|
|
126
|
+
it('ignores a value that is not one of the offered sizes', async () => {
|
|
127
|
+
const wrapper = mount(EditorHeadingType, { props: { modelValue: 'text-4xl' } })
|
|
128
|
+
|
|
129
|
+
// The model type rules this value out, which is the point: it arrives from stored data.
|
|
130
|
+
await wrapper.setProps({ modelValue: 'text-7xl' as unknown as EditorHeadingTypeValue })
|
|
131
|
+
|
|
132
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
133
|
+
expect(fieldText(wrapper)).toBe('Selecteer...')
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// The consumer derives its class matchers from this list, so the list is a
|
|
138
|
+
// contract: any change here changes what the app recognises.
|
|
139
|
+
describe('EDITOR_HEADING_TYPE_OPTIONS', () => {
|
|
140
|
+
it('offers exactly the agreed options', () => {
|
|
141
|
+
expect(EDITOR_HEADING_TYPE_OPTIONS).toEqual([
|
|
142
|
+
{ token: 'text-4xl', level: 1 },
|
|
143
|
+
{ token: 'text-3xl', level: 2 },
|
|
144
|
+
{ token: 'text-2xl', level: 3 },
|
|
145
|
+
{ token: 'text-xl', level: 4 },
|
|
146
|
+
{ token: 'text-lg', level: 5 },
|
|
147
|
+
{ token: 'text-base', level: 6 },
|
|
148
|
+
])
|
|
149
|
+
})
|
|
150
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import EditorJustifyContent from './EditorJustifyContent.vue'
|
|
4
|
+
import type { EditorJustifyContentValue } from './EditorJustifyContent.vue'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: 'Components/Editor Components/Molecules/Editor Justify Content',
|
|
8
|
+
component: EditorJustifyContent,
|
|
9
|
+
decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
|
|
10
|
+
argTypes: {
|
|
11
|
+
label: { control: 'text' },
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta<typeof EditorJustifyContent>
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof EditorJustifyContent>
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
render: (args) => ({
|
|
19
|
+
components: { EditorJustifyContent },
|
|
20
|
+
setup() {
|
|
21
|
+
const justify = ref<EditorJustifyContentValue>('justify-start')
|
|
22
|
+
return { args, justify }
|
|
23
|
+
},
|
|
24
|
+
// The value is shown as-is: it is the Tailwind class the editor applies.
|
|
25
|
+
template: `
|
|
26
|
+
<div>
|
|
27
|
+
<EditorJustifyContent v-bind="args" v-model="justify" />
|
|
28
|
+
<p class="small-normal text-gray-700">{{ justify }}</p>
|
|
29
|
+
</div>
|
|
30
|
+
`,
|
|
31
|
+
}),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const CustomMessages: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
label: 'Alignment',
|
|
37
|
+
messages: { start: 'Start', center: 'Center', end: 'End', between: 'Space between' },
|
|
38
|
+
},
|
|
39
|
+
render: (args) => ({
|
|
40
|
+
components: { EditorJustifyContent },
|
|
41
|
+
setup() {
|
|
42
|
+
const justify = ref<EditorJustifyContentValue>('justify-start')
|
|
43
|
+
return { args, justify }
|
|
44
|
+
},
|
|
45
|
+
template: `<EditorJustifyContent v-bind="args" v-model="justify" />`,
|
|
46
|
+
}),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A subset of the options, always in the canonical order: a consumer placing a
|
|
51
|
+
* block within its parent has no use for `justify-between`.
|
|
52
|
+
*/
|
|
53
|
+
export const OptionsSubset: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
options: ['justify-start', 'justify-center', 'justify-end'],
|
|
56
|
+
messages: { start: 'Links', center: 'Midden', end: 'Rechts' },
|
|
57
|
+
},
|
|
58
|
+
render: (args) => ({
|
|
59
|
+
components: { EditorJustifyContent },
|
|
60
|
+
setup() {
|
|
61
|
+
const justify = ref<EditorJustifyContentValue>('justify-center')
|
|
62
|
+
return { args, justify }
|
|
63
|
+
},
|
|
64
|
+
template: `
|
|
65
|
+
<div>
|
|
66
|
+
<EditorJustifyContent v-bind="args" v-model="justify" />
|
|
67
|
+
<p class="small-normal text-gray-700">{{ justify }}</p>
|
|
68
|
+
</div>
|
|
69
|
+
`,
|
|
70
|
+
}),
|
|
71
|
+
}
|