@nexxtmove/ui 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/editor/molecules/EditorColor/EditorColor.vue.d.ts +7 -5
- package/dist/components/editor/molecules/EditorColor/_ColorField.vue.d.ts +4 -1
- package/dist/components/organisms/ColorPicker/ColorPicker.vue.d.ts +3 -1
- package/dist/index.js +95 -172
- 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/EditorColor/EditorColor.stories.ts +633 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +776 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.vue +7 -5
- package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +832 -0
- package/src/components/editor/molecules/EditorColor/_ColorField.vue +20 -15
- 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 +696 -0
- package/src/components/organisms/ColorPicker/ColorPicker.vue +12 -3
- 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
|
@@ -31,7 +31,10 @@ export interface ColorFieldProps<P extends string> {
|
|
|
31
31
|
suggestions?: string[]
|
|
32
32
|
/** Theme roles offered above the suggestions; an entry with an unreadable hex is left out. */
|
|
33
33
|
themeRoles?: EditorColorThemeRole[]
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Offer an opacity slider, also while a theme role is selected: it then stands
|
|
36
|
+
* at 100%, and moving it turns the role into its hex with that opacity.
|
|
37
|
+
*/
|
|
35
38
|
showAlpha?: boolean
|
|
36
39
|
/**
|
|
37
40
|
* The fills offered: `['solid']` (the default), `['solid', 'gradient']` or
|
|
@@ -122,25 +125,27 @@ const offeredRoles = computed(() => themeRoles.filter((entry) => ROLE_HEX_PATTER
|
|
|
122
125
|
const activeRole = computed(() => offeredRoles.value.find(({ role }) => role === storedRole.value))
|
|
123
126
|
|
|
124
127
|
/**
|
|
128
|
+
* A role's hex as the picker gets it: opaque, in the 8-digit form.
|
|
129
|
+
*
|
|
125
130
|
* Picking the role's own colour -- from the suggestions, or typed -- is how a
|
|
126
131
|
* colour is detached from the theme without changing how it looks. The picker
|
|
127
|
-
* reports nothing when it is told to pick the string it already has, so
|
|
128
|
-
*
|
|
129
|
-
*
|
|
132
|
+
* reports nothing when it is told to pick the string it already has, so the hex
|
|
133
|
+
* is handed over with an `ff` pair: the picker writes an opaque colour as six
|
|
134
|
+
* digits and an alpha pair only below full opacity, so it never writes this
|
|
135
|
+
* string back, and every pick is a change. That holds with the opacity slider
|
|
136
|
+
* shown as well.
|
|
137
|
+
*
|
|
138
|
+
* A role has no opacity, so an alpha pair in the role's own hex is replaced by
|
|
139
|
+
* `ff`: the slider of an active role stands at 100%. Moving it below that writes
|
|
140
|
+
* the role's hex with the alpha pair, which detaches the role the same way;
|
|
141
|
+
* back at 100% it stays a literal (`OPAQUE_ALPHA_PATTERN`).
|
|
130
142
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
143
|
+
* Opening the panel, or a role becoming active, writes nothing: the picker's
|
|
144
|
+
* primitives only report a change the user makes, and a key that leaves the
|
|
145
|
+
* slider where it is (ArrowRight at 100%) is no change.
|
|
134
146
|
*/
|
|
135
147
|
const pickerHex = (roleHex: string) => `${roleHex.slice(0, 7)}ff`
|
|
136
148
|
|
|
137
|
-
/**
|
|
138
|
-
* A role class has no opacity, so the slider is only offered for a literal.
|
|
139
|
-
* Hiding it is also what keeps the trick in `pickerHex` working: without the
|
|
140
|
-
* alpha channel the picker reads the role's hex as opaque.
|
|
141
|
-
*/
|
|
142
|
-
const alphaEnabled = computed(() => showAlpha && !activeRole.value)
|
|
143
|
-
|
|
144
149
|
// An unparseable value -- empty, a token class such as `bg-red-500`, or copy
|
|
145
150
|
// from before this component -- leaves the picker empty and its placeholder
|
|
146
151
|
// visible, rather than being reported as a problem: the first pick writes a
|
|
@@ -401,7 +406,7 @@ watch(
|
|
|
401
406
|
:label="label"
|
|
402
407
|
:describedby="describedby"
|
|
403
408
|
:formats="['hex']"
|
|
404
|
-
:show-alpha="
|
|
409
|
+
:show-alpha="showAlpha"
|
|
405
410
|
:placeholder="placeholder"
|
|
406
411
|
:suggestions="{ hex: suggestions }"
|
|
407
412
|
:suggestions-label="suggestionsLabel"
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
gradientClassToCss,
|
|
4
|
+
looksLikeGradientClass,
|
|
5
|
+
parseGradientClass,
|
|
6
|
+
serializeGradientClass,
|
|
7
|
+
type GradientClass,
|
|
8
|
+
} from './gradientClass'
|
|
9
|
+
import type { EditorColorRole } from './colorClass'
|
|
10
|
+
|
|
11
|
+
describe('parseGradientClass', () => {
|
|
12
|
+
it('reads a linear gradient with two stops at their default positions', () => {
|
|
13
|
+
expect(parseGradientClass('bg-linear-135 from-[#7f56d9] to-[#432e73]')).toEqual({
|
|
14
|
+
type: 'linear',
|
|
15
|
+
angle: 135,
|
|
16
|
+
stops: [
|
|
17
|
+
{ color: { hex: '#7f56d9' }, position: 0 },
|
|
18
|
+
{ color: { hex: '#432e73' }, position: 100 },
|
|
19
|
+
],
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('reads a via stop, roles and explicit positions', () => {
|
|
24
|
+
expect(
|
|
25
|
+
parseGradientClass(
|
|
26
|
+
'bg-linear-0 from-theme-main from-10% via-[#ffffff80] via-40% to-theme-text to-90%',
|
|
27
|
+
),
|
|
28
|
+
).toEqual({
|
|
29
|
+
type: 'linear',
|
|
30
|
+
angle: 0,
|
|
31
|
+
stops: [
|
|
32
|
+
{ color: { role: 'main' }, position: 10 },
|
|
33
|
+
{ color: { hex: '#ffffff80' }, position: 40 },
|
|
34
|
+
{ color: { role: 'text' }, position: 90 },
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('reads a centred radial gradient and one with a centre elsewhere', () => {
|
|
40
|
+
expect(parseGradientClass('bg-radial from-[#000000] to-[#ffffff]')).toMatchObject({
|
|
41
|
+
type: 'radial',
|
|
42
|
+
x: 50,
|
|
43
|
+
y: 50,
|
|
44
|
+
})
|
|
45
|
+
expect(parseGradientClass('bg-radial-[at_30%_70%] from-[#000000] to-[#ffffff]')).toMatchObject({
|
|
46
|
+
type: 'radial',
|
|
47
|
+
x: 30,
|
|
48
|
+
y: 70,
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('normalizes hexes: lower case, an opaque ff dropped', () => {
|
|
53
|
+
expect(parseGradientClass('bg-linear-90 from-[#AABBCCFF] to-[#AABBCC80]')?.stops).toEqual([
|
|
54
|
+
{ color: { hex: '#aabbcc' }, position: 0 },
|
|
55
|
+
{ color: { hex: '#aabbcc80' }, position: 100 },
|
|
56
|
+
])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('accepts the classes in another order', () => {
|
|
60
|
+
expect(parseGradientClass('to-[#ffffff] from-[#000000] bg-linear-45')).toMatchObject({
|
|
61
|
+
angle: 45,
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it.each([
|
|
66
|
+
['empty', ''],
|
|
67
|
+
['undefined', undefined],
|
|
68
|
+
['a solid colour', 'bg-[#ffffff]'],
|
|
69
|
+
['no type', 'from-[#000000] to-[#ffffff]'],
|
|
70
|
+
['no from', 'bg-linear-90 to-[#ffffff]'],
|
|
71
|
+
['no to', 'bg-linear-90 from-[#000000]'],
|
|
72
|
+
['an unknown role', 'bg-linear-90 from-theme-brand to-[#ffffff]'],
|
|
73
|
+
['an angle of 360', 'bg-linear-360 from-[#000000] to-[#ffffff]'],
|
|
74
|
+
['an angle with a leading zero', 'bg-linear-045 from-[#000000] to-[#ffffff]'],
|
|
75
|
+
['a position above 100', 'bg-linear-90 from-[#000000] to-[#ffffff] to-101%'],
|
|
76
|
+
['a via position without via', 'bg-linear-90 from-[#000000] via-40% to-[#ffffff]'],
|
|
77
|
+
['two types', 'bg-linear-90 bg-radial from-[#000000] to-[#ffffff]'],
|
|
78
|
+
['a stop given twice', 'bg-linear-90 from-[#000000] from-[#111111] to-[#ffffff]'],
|
|
79
|
+
['an unknown class', 'bg-linear-90 from-[#000000] to-[#ffffff] rounded'],
|
|
80
|
+
['a short hex', 'bg-linear-90 from-[#000] to-[#ffffff]'],
|
|
81
|
+
['positions that run backwards', 'bg-linear-90 from-[#000000] from-80% to-[#ffffff] to-20%'],
|
|
82
|
+
['oklab in the radial value', 'bg-radial-[at_30%_70%_in_oklab] from-[#000000] to-[#ffffff]'],
|
|
83
|
+
['a centre above 100', 'bg-radial-[at_130%_70%] from-[#000000] to-[#ffffff]'],
|
|
84
|
+
['a direction keyword', 'bg-linear-to-b from-[#000000] to-[#ffffff]'],
|
|
85
|
+
])('returns null for %s', (_, value) => {
|
|
86
|
+
expect(parseGradientClass(value)).toBeNull()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('serializeGradientClass', () => {
|
|
91
|
+
it('writes the fixed order and leaves default positions out', () => {
|
|
92
|
+
expect(
|
|
93
|
+
serializeGradientClass({
|
|
94
|
+
type: 'linear',
|
|
95
|
+
angle: 135,
|
|
96
|
+
stops: [
|
|
97
|
+
{ color: { hex: '#7f56d9' }, position: 0 },
|
|
98
|
+
{ color: { role: 'main' }, position: 40 },
|
|
99
|
+
{ color: { hex: '#432e73cc' }, position: 100 },
|
|
100
|
+
],
|
|
101
|
+
}),
|
|
102
|
+
).toBe('bg-linear-135 from-[#7f56d9] via-theme-main via-40% to-[#432e73cc]')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('writes every position that differs from 0, 50 and 100', () => {
|
|
106
|
+
expect(
|
|
107
|
+
serializeGradientClass({
|
|
108
|
+
type: 'linear',
|
|
109
|
+
angle: 90,
|
|
110
|
+
stops: [
|
|
111
|
+
{ color: { hex: '#000000' }, position: 5 },
|
|
112
|
+
{ color: { hex: '#888888' }, position: 50 },
|
|
113
|
+
{ color: { hex: '#ffffff' }, position: 95 },
|
|
114
|
+
],
|
|
115
|
+
}),
|
|
116
|
+
).toBe('bg-linear-90 from-[#000000] from-5% via-[#888888] to-[#ffffff] to-95%')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('writes hexes in lower case without an opaque ff', () => {
|
|
120
|
+
expect(
|
|
121
|
+
serializeGradientClass({
|
|
122
|
+
type: 'linear',
|
|
123
|
+
angle: 0,
|
|
124
|
+
stops: [
|
|
125
|
+
{ color: { hex: '#AABBCCFF' }, position: 0 },
|
|
126
|
+
{ color: { hex: '#AABBCC00' }, position: 100 },
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
).toBe('bg-linear-0 from-[#aabbcc] to-[#aabbcc00]')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('writes bg-radial for a centred radial gradient, the arbitrary value otherwise', () => {
|
|
133
|
+
const stops = [
|
|
134
|
+
{ color: { hex: '#000000' as const }, position: 0 },
|
|
135
|
+
{ color: { hex: '#ffffff' as const }, position: 100 },
|
|
136
|
+
]
|
|
137
|
+
expect(serializeGradientClass({ type: 'radial', x: 50, y: 50, stops })).toBe(
|
|
138
|
+
'bg-radial from-[#000000] to-[#ffffff]',
|
|
139
|
+
)
|
|
140
|
+
expect(serializeGradientClass({ type: 'radial', x: 30, y: 70, stops })).toBe(
|
|
141
|
+
'bg-radial-[at_30%_70%] from-[#000000] to-[#ffffff]',
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('rounds, clamps and wraps out-of-range numbers', () => {
|
|
146
|
+
expect(
|
|
147
|
+
serializeGradientClass({
|
|
148
|
+
type: 'linear',
|
|
149
|
+
angle: -10.4,
|
|
150
|
+
stops: [
|
|
151
|
+
{ color: { hex: '#000000' }, position: -3 },
|
|
152
|
+
{ color: { hex: '#ffffff' }, position: 99.6 },
|
|
153
|
+
],
|
|
154
|
+
}),
|
|
155
|
+
).toBe('bg-linear-350 from-[#000000] to-[#ffffff]')
|
|
156
|
+
expect(
|
|
157
|
+
serializeGradientClass({
|
|
158
|
+
type: 'linear',
|
|
159
|
+
angle: 360,
|
|
160
|
+
stops: [
|
|
161
|
+
{ color: { hex: '#000000' }, position: 0 },
|
|
162
|
+
{ color: { hex: '#ffffff' }, position: 100 },
|
|
163
|
+
],
|
|
164
|
+
}),
|
|
165
|
+
).toMatch(/^bg-linear-0 /)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('never writes var() or a free linear-gradient', () => {
|
|
169
|
+
const value = serializeGradientClass({
|
|
170
|
+
type: 'linear',
|
|
171
|
+
angle: 45,
|
|
172
|
+
stops: [
|
|
173
|
+
{ color: { role: 'accent' }, position: 0 },
|
|
174
|
+
{ color: { role: 'background' }, position: 100 },
|
|
175
|
+
],
|
|
176
|
+
})
|
|
177
|
+
expect(value).not.toMatch(/var\(|linear-gradient/)
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe('round trip', () => {
|
|
182
|
+
const cases: GradientClass[] = [
|
|
183
|
+
{
|
|
184
|
+
type: 'linear',
|
|
185
|
+
angle: 0,
|
|
186
|
+
stops: [
|
|
187
|
+
{ color: { hex: '#000000' }, position: 0 },
|
|
188
|
+
{ color: { hex: '#ffffff' }, position: 100 },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
type: 'linear',
|
|
193
|
+
angle: 359,
|
|
194
|
+
stops: [
|
|
195
|
+
{ color: { role: 'heading' }, position: 20 },
|
|
196
|
+
{ color: { hex: '#12345678' }, position: 20 },
|
|
197
|
+
{ color: { role: 'text' }, position: 20 },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
type: 'radial',
|
|
202
|
+
x: 0,
|
|
203
|
+
y: 100,
|
|
204
|
+
stops: [
|
|
205
|
+
{ color: { hex: '#7f56d900' }, position: 0 },
|
|
206
|
+
{ color: { hex: '#7f56d9' }, position: 50 },
|
|
207
|
+
{ color: { hex: '#432e73' }, position: 100 },
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: 'radial',
|
|
212
|
+
x: 50,
|
|
213
|
+
y: 50,
|
|
214
|
+
stops: [
|
|
215
|
+
{ color: { role: 'main' }, position: 33 },
|
|
216
|
+
{ color: { role: 'main' }, position: 67 },
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
it.each(cases.map((value, index) => [index, value] as const))(
|
|
222
|
+
'parse(serialize(x)) equals x for case %i',
|
|
223
|
+
(_, value) => {
|
|
224
|
+
expect(parseGradientClass(serializeGradientClass(value))).toEqual(value)
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
it.each([
|
|
229
|
+
'bg-linear-135 from-[#7f56d9] via-theme-main via-40% to-[#432e73cc]',
|
|
230
|
+
'bg-radial-[at_30%_70%] from-theme-accent to-[#ffffff00] to-80%',
|
|
231
|
+
'bg-radial from-[#000000] from-10% to-[#ffffff]',
|
|
232
|
+
])('serialize(parse(s)) equals s for %s', (value) => {
|
|
233
|
+
const parsed = parseGradientClass(value)
|
|
234
|
+
expect(parsed && serializeGradientClass(parsed)).toBe(value)
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
describe('looksLikeGradientClass', () => {
|
|
239
|
+
it('is true for a value led by a gradient type, readable or not', () => {
|
|
240
|
+
expect(looksLikeGradientClass('bg-linear-90 from-[#000000] to-[#ffffff]')).toBe(true)
|
|
241
|
+
expect(looksLikeGradientClass('bg-linear-999 garbage')).toBe(true)
|
|
242
|
+
expect(looksLikeGradientClass('bg-radial')).toBe(true)
|
|
243
|
+
expect(looksLikeGradientClass('bg-radial-[at_1%_2%] x')).toBe(true)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('is false for a solid colour or nothing', () => {
|
|
247
|
+
expect(looksLikeGradientClass('bg-[#ffffff]')).toBe(false)
|
|
248
|
+
expect(looksLikeGradientClass('bg-theme-main')).toBe(false)
|
|
249
|
+
expect(looksLikeGradientClass('bg-radialx')).toBe(false)
|
|
250
|
+
expect(looksLikeGradientClass(undefined)).toBe(false)
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe('gradientClassToCss', () => {
|
|
255
|
+
const roles = (role: EditorColorRole) => `var(--color-theme-${role})`
|
|
256
|
+
const css = (value: string) => {
|
|
257
|
+
const parsed = parseGradientClass(value)
|
|
258
|
+
if (!parsed) throw new Error(`unreadable: ${value}`)
|
|
259
|
+
return gradientClassToCss(parsed, roles)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
it('writes a linear gradient with the angle and every position', () => {
|
|
263
|
+
expect(css('bg-linear-135 from-[#7f56d9] to-[#432e73]')).toBe(
|
|
264
|
+
'linear-gradient(135deg in oklab, #7f56d9 0%, #432e73 100%)',
|
|
265
|
+
)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('writes a centred radial gradient without a position', () => {
|
|
269
|
+
expect(css('bg-radial from-[#000000] to-[#ffffff] to-80%')).toBe(
|
|
270
|
+
'radial-gradient(in oklab, #000000 0%, #ffffff 80%)',
|
|
271
|
+
)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('writes the centre before the interpolation method off centre, in oklab', () => {
|
|
275
|
+
expect(css('bg-radial-[at_30%_70%] from-[#000000] to-[#ffffff]')).toBe(
|
|
276
|
+
'radial-gradient(at 30% 70% in oklab, #000000 0%, #ffffff 100%)',
|
|
277
|
+
)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('writes three stops with the via position, also the default 50 %', () => {
|
|
281
|
+
expect(css('bg-linear-90 from-[#000000] via-[#888888] to-[#ffffff]')).toBe(
|
|
282
|
+
'linear-gradient(90deg in oklab, #000000 0%, #888888 50%, #ffffff 100%)',
|
|
283
|
+
)
|
|
284
|
+
expect(css('bg-linear-90 from-[#000000] from-10% via-[#888888] via-40% to-[#ffffff]')).toBe(
|
|
285
|
+
'linear-gradient(90deg in oklab, #000000 10%, #888888 40%, #ffffff 100%)',
|
|
286
|
+
)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('resolves a role and keeps a hex with alpha', () => {
|
|
290
|
+
expect(css('bg-linear-0 from-theme-main to-[#432E73CC]')).toBe(
|
|
291
|
+
'linear-gradient(0deg in oklab, var(--color-theme-main) 0%, #432e73cc 100%)',
|
|
292
|
+
)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('round-trips: serialize, parse and draw give the same CSS as the parsed value', () => {
|
|
296
|
+
const value = 'bg-radial-[at_20%_80%] from-theme-accent via-[#ffffff80] via-35% to-[#000000]'
|
|
297
|
+
const parsed = parseGradientClass(value)
|
|
298
|
+
expect(parsed && serializeGradientClass(parsed)).toBe(value)
|
|
299
|
+
expect(css(value)).toBe(
|
|
300
|
+
'radial-gradient(at 20% 80% in oklab, var(--color-theme-accent) 0%, #ffffff80 35%, #000000 100%)',
|
|
301
|
+
)
|
|
302
|
+
})
|
|
303
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
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 EditorDashPattern from './EditorDashPattern.vue'
|
|
5
|
+
import type { EditorDashPatternValue } from './EditorDashPattern.vue'
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/Editor Components/Molecules/Editor Dash Pattern',
|
|
9
|
+
component: EditorDashPattern,
|
|
10
|
+
decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
|
|
11
|
+
argTypes: {
|
|
12
|
+
label: { control: 'text' },
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof EditorDashPattern>
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof EditorDashPattern>
|
|
17
|
+
|
|
18
|
+
/** Shows the model as-is: two whole px values. */
|
|
19
|
+
const render = (args: Story['args'], initial: EditorDashPatternValue) => ({
|
|
20
|
+
components: { EditorDashPattern },
|
|
21
|
+
setup() {
|
|
22
|
+
const pattern = ref(initial)
|
|
23
|
+
return { args, pattern }
|
|
24
|
+
},
|
|
25
|
+
template: `
|
|
26
|
+
<div>
|
|
27
|
+
<EditorDashPattern v-bind="args" v-model="pattern" />
|
|
28
|
+
<p data-testid="dash-pattern-value" class="small-normal text-gray-700">
|
|
29
|
+
{{ pattern.dash }} / {{ pattern.gap }}
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
`,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const readout = (canvasElement: HTMLElement) =>
|
|
36
|
+
canvasElement.querySelector('[data-testid="dash-pattern-value"]')!.textContent!.trim()
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {
|
|
39
|
+
render: (args) => render(args, { dash: 8, gap: 4 }),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The field never goes empty: clearing it and leaving puts the previous value
|
|
44
|
+
* back. Values outside 1-64 are clamped, with the field's snap animation.
|
|
45
|
+
*/
|
|
46
|
+
export const NeverEmpty: Story = {
|
|
47
|
+
render: (args) => render(args, { dash: 8, gap: 4 }),
|
|
48
|
+
play: async ({ canvasElement }) => {
|
|
49
|
+
const canvas = within(canvasElement)
|
|
50
|
+
const dash = canvas.getByRole('spinbutton', { name: 'Lengte (px)' })
|
|
51
|
+
const gap = canvas.getByRole('spinbutton', { name: 'Tussenruimte (px)' })
|
|
52
|
+
|
|
53
|
+
await userEvent.clear(dash)
|
|
54
|
+
await userEvent.tab()
|
|
55
|
+
await waitFor(() => expect(dash).toHaveValue(8))
|
|
56
|
+
expect(readout(canvasElement)).toBe('8 / 4')
|
|
57
|
+
|
|
58
|
+
await userEvent.clear(gap)
|
|
59
|
+
await userEvent.type(gap, '100')
|
|
60
|
+
await userEvent.tab()
|
|
61
|
+
await waitFor(() => expect(gap).toHaveValue(64))
|
|
62
|
+
expect(readout(canvasElement)).toBe('8 / 64')
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const CustomMessages: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
label: 'Dash',
|
|
69
|
+
messages: { dash: 'Length', gap: 'Gap', unit: 'px' },
|
|
70
|
+
},
|
|
71
|
+
render: (args) => render(args, { dash: 12, gap: 6 }),
|
|
72
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { flushPromises, mount, type DOMWrapper, type VueWrapper } from '@vue/test-utils'
|
|
3
|
+
import EditorDashPattern, { EDITOR_DASH_PATTERN_LIMITS } from './EditorDashPattern.vue'
|
|
4
|
+
import type { EditorDashPatternValue } from './EditorDashPattern.vue'
|
|
5
|
+
|
|
6
|
+
const render = (modelValue: EditorDashPatternValue, props: Record<string, unknown> = {}) =>
|
|
7
|
+
mount(EditorDashPattern, { props: { modelValue, ...props } })
|
|
8
|
+
|
|
9
|
+
const inputs = (wrapper: VueWrapper) => wrapper.findAll('input')
|
|
10
|
+
|
|
11
|
+
const values = (wrapper: VueWrapper) =>
|
|
12
|
+
inputs(wrapper).map((input) => (input.element as HTMLInputElement).value)
|
|
13
|
+
|
|
14
|
+
const lastEmit = (wrapper: VueWrapper) => wrapper.emitted('update:modelValue')?.at(-1)?.[0]
|
|
15
|
+
|
|
16
|
+
/** Type and confirm, the way leaving the field does. */
|
|
17
|
+
const commit = async (input: DOMWrapper<Element>, typed: string) => {
|
|
18
|
+
await input.setValue(typed)
|
|
19
|
+
await input.trigger('blur')
|
|
20
|
+
await flushPromises()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('EditorDashPattern', () => {
|
|
24
|
+
// No assertions on typography utilities: copy is found by text or by
|
|
25
|
+
// accessible name, so restyling cannot turn these red.
|
|
26
|
+
it('renders the Dutch default label', () => {
|
|
27
|
+
expect(render({ dash: 8, gap: 4 }).find('p').text()).toBe('Streep')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('renders a custom label', () => {
|
|
31
|
+
expect(render({ dash: 8, gap: 4 }, { label: 'Dash' }).find('p').text()).toBe('Dash')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('shows the two values in two fields side by side', () => {
|
|
35
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
36
|
+
|
|
37
|
+
expect(values(wrapper)).toEqual(['8', '4'])
|
|
38
|
+
expect(wrapper.get('.grid-cols-2').classes()).toEqual(expect.arrayContaining(['grid', 'gap-2']))
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('captions each field visibly and names it with the caption plus unit', () => {
|
|
42
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
43
|
+
|
|
44
|
+
expect(wrapper.text()).toContain('Lengte')
|
|
45
|
+
expect(wrapper.text()).toContain('Tussenruimte')
|
|
46
|
+
expect(inputs(wrapper).map((input) => input.attributes('aria-label'))).toEqual([
|
|
47
|
+
'Lengte (px)',
|
|
48
|
+
'Tussenruimte (px)',
|
|
49
|
+
])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('offers px only, so it renders no unit selector', () => {
|
|
53
|
+
expect(render({ dash: 8, gap: 4 }).find('select').exists()).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('merges custom messages per key', () => {
|
|
57
|
+
const wrapper = render({ dash: 8, gap: 4 }, { messages: { dash: 'Length', unit: 'pixels' } })
|
|
58
|
+
|
|
59
|
+
expect(inputs(wrapper).map((input) => input.attributes('aria-label'))).toEqual([
|
|
60
|
+
'Length (pixels)',
|
|
61
|
+
'Tussenruimte (pixels)',
|
|
62
|
+
])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('writes the dash and keeps the gap', async () => {
|
|
66
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
67
|
+
|
|
68
|
+
await commit(inputs(wrapper)[0], '12')
|
|
69
|
+
|
|
70
|
+
expect(lastEmit(wrapper)).toEqual({ dash: 12, gap: 4 })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('writes the gap and keeps the dash', async () => {
|
|
74
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
75
|
+
|
|
76
|
+
await commit(inputs(wrapper)[1], '6')
|
|
77
|
+
|
|
78
|
+
expect(lastEmit(wrapper)).toEqual({ dash: 8, gap: 6 })
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('passes on commit from the field that finished', async () => {
|
|
82
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
83
|
+
|
|
84
|
+
await commit(inputs(wrapper)[1], '6')
|
|
85
|
+
|
|
86
|
+
expect(wrapper.emitted('commit')).toHaveLength(1)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('clamps the dash to the limits', async () => {
|
|
90
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
91
|
+
|
|
92
|
+
await commit(inputs(wrapper)[0], '500')
|
|
93
|
+
|
|
94
|
+
expect(lastEmit(wrapper)).toEqual({ dash: 64, gap: 4 })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('clamps the gap to the limits', async () => {
|
|
98
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
99
|
+
|
|
100
|
+
await commit(inputs(wrapper)[1], '0')
|
|
101
|
+
|
|
102
|
+
expect(lastEmit(wrapper)).toEqual({ dash: 8, gap: 1 })
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('is never empty: an emptied field puts the previous value back', async () => {
|
|
106
|
+
const wrapper = render({ dash: 8, gap: 4 })
|
|
107
|
+
|
|
108
|
+
await commit(inputs(wrapper)[0], '')
|
|
109
|
+
|
|
110
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
111
|
+
expect(wrapper.emitted('commit')).toBeUndefined()
|
|
112
|
+
expect(values(wrapper)).toEqual(['8', '4'])
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// The consumer's matcher uses the same bounds, so they are a contract.
|
|
117
|
+
describe('EDITOR_DASH_PATTERN_LIMITS', () => {
|
|
118
|
+
it('bounds both values to 1-64 px', () => {
|
|
119
|
+
expect(EDITOR_DASH_PATTERN_LIMITS).toEqual({
|
|
120
|
+
dash: { min: 1, max: 64 },
|
|
121
|
+
gap: { min: 1, max: 64 },
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import EditorFlexDirection from './EditorFlexDirection.vue'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Editor Components/Molecules/Editor Flex Direction',
|
|
7
|
+
component: EditorFlexDirection,
|
|
8
|
+
decorators: [() => ({ template: '<div class="w-100 mx-auto"><story /></div>' })],
|
|
9
|
+
argTypes: {
|
|
10
|
+
label: { control: 'text' },
|
|
11
|
+
},
|
|
12
|
+
} satisfies Meta<typeof EditorFlexDirection>
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof EditorFlexDirection>
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: (args) => ({
|
|
18
|
+
components: { EditorFlexDirection },
|
|
19
|
+
setup() {
|
|
20
|
+
const direction = ref<'flex-row' | 'flex-col'>('flex-row')
|
|
21
|
+
return { args, direction }
|
|
22
|
+
},
|
|
23
|
+
// The value is shown as-is: it is the Tailwind class the editor applies.
|
|
24
|
+
template: `
|
|
25
|
+
<div>
|
|
26
|
+
<EditorFlexDirection v-bind="args" v-model="direction" />
|
|
27
|
+
<p class="small-normal text-gray-700">{{ direction }}</p>
|
|
28
|
+
</div>
|
|
29
|
+
`,
|
|
30
|
+
}),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const CustomMessages: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
label: 'Direction',
|
|
36
|
+
messages: { horizontal: 'Horizontal', vertical: 'Vertical' },
|
|
37
|
+
},
|
|
38
|
+
render: (args) => ({
|
|
39
|
+
components: { EditorFlexDirection },
|
|
40
|
+
setup() {
|
|
41
|
+
const direction = ref<'flex-row' | 'flex-col'>('flex-row')
|
|
42
|
+
return { args, direction }
|
|
43
|
+
},
|
|
44
|
+
template: `<EditorFlexDirection v-bind="args" v-model="direction" />`,
|
|
45
|
+
}),
|
|
46
|
+
}
|