@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
|
@@ -61,7 +61,9 @@ export interface NexxtColorPickerProps {
|
|
|
61
61
|
/**
|
|
62
62
|
* Offer an opacity slider and accept colours below full opacity. Turning it
|
|
63
63
|
* off while the colour is translucent writes the opaque colour to the model
|
|
64
|
-
* at once.
|
|
64
|
+
* at once. The gradient mode always has the slider; without `showAlpha` it is
|
|
65
|
+
* locked at 100% for a stop bound to a preset, with it a change unbinds the
|
|
66
|
+
* stop from its preset.
|
|
65
67
|
*/
|
|
66
68
|
showAlpha?: boolean
|
|
67
69
|
formats?: ColorFormat[]
|
|
@@ -283,8 +285,15 @@ const modeModel = computed({
|
|
|
283
285
|
/** The opacity slider is always there in the gradient mode. */
|
|
284
286
|
const alphaOn = computed(() => props.showAlpha || gradientMode.value)
|
|
285
287
|
|
|
286
|
-
/**
|
|
287
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Without `showAlpha` a stop bound to a preset is always opaque: its slider
|
|
290
|
+
* stays, at 100 and disabled. With `showAlpha` the slider stays usable, as it is
|
|
291
|
+
* for the solid colour, and a change unbinds the stop from its preset
|
|
292
|
+
* (`commitColor`), so the stop becomes a hex with that opacity.
|
|
293
|
+
*/
|
|
294
|
+
const alphaLocked = computed(
|
|
295
|
+
() => gradientMode.value && !props.showAlpha && !!editor.selected.value?.preset,
|
|
296
|
+
)
|
|
288
297
|
|
|
289
298
|
const panelClass = computed(
|
|
290
299
|
() =>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
colorAt,
|
|
4
|
+
gradientCss,
|
|
5
|
+
stopBarCss,
|
|
6
|
+
mixColors,
|
|
7
|
+
normalizeHex,
|
|
8
|
+
wrapAngle,
|
|
9
|
+
clampPercent,
|
|
10
|
+
DEFAULT_GRADIENT,
|
|
11
|
+
} from './gradient'
|
|
12
|
+
|
|
13
|
+
describe('gradient helpers', () => {
|
|
14
|
+
it('wraps angles and clamps percentages to whole numbers', () => {
|
|
15
|
+
expect(wrapAngle(360)).toBe(0)
|
|
16
|
+
expect(wrapAngle(-10)).toBe(350)
|
|
17
|
+
expect(wrapAngle(719.6)).toBe(0)
|
|
18
|
+
expect(clampPercent(-4)).toBe(0)
|
|
19
|
+
expect(clampPercent(100.4)).toBe(100)
|
|
20
|
+
expect(clampPercent(40.5)).toBe(41)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('normalizes hexes to lower case without an opaque ff', () => {
|
|
24
|
+
expect(normalizeHex('#AABBCCFF')).toBe('#aabbcc')
|
|
25
|
+
expect(normalizeHex('#AABBCC80')).toBe('#aabbcc80')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('renders in oklab always, as the app draws it, also off centre', () => {
|
|
29
|
+
expect(gradientCss(DEFAULT_GRADIENT)).toBe(
|
|
30
|
+
'linear-gradient(180deg in oklab, #00000000 0%, #00000099 100%)',
|
|
31
|
+
)
|
|
32
|
+
const radial = { ...DEFAULT_GRADIENT, type: 'radial' as const }
|
|
33
|
+
expect(gradientCss(radial)).toBe('radial-gradient(in oklab, #00000000 0%, #00000099 100%)')
|
|
34
|
+
const offCentre = { ...radial, centerX: 30 }
|
|
35
|
+
expect(gradientCss(offCentre)).toBe(
|
|
36
|
+
'radial-gradient(at 30% 50% in oklab, #00000000 0%, #00000099 100%)',
|
|
37
|
+
)
|
|
38
|
+
expect(stopBarCss(offCentre)).toBe(
|
|
39
|
+
'linear-gradient(to right in oklab, #00000000 0%, #00000099 100%)',
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('mixes with premultiplied alpha, so a fade from transparent keeps the hue', () => {
|
|
44
|
+
expect(mixColors('#00000000', '#ff0000', 0.5, 'srgb')).toBe('#ff000080')
|
|
45
|
+
expect(mixColors('#00000000', '#ff0000', 0.5, 'oklab')).toBe('#ff000080')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('mixes two opaque colours in oklab differently from srgb', () => {
|
|
49
|
+
const oklab = mixColors('#0000ff', '#ffffff', 0.5, 'oklab')
|
|
50
|
+
const srgb = mixColors('#0000ff', '#ffffff', 0.5, 'srgb')
|
|
51
|
+
expect(srgb).toBe('#8080ff')
|
|
52
|
+
expect(oklab).not.toBe(srgb)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('returns the colour at a position, flat beyond the ends', () => {
|
|
56
|
+
const stops = [
|
|
57
|
+
{ color: '#000000', position: 20 },
|
|
58
|
+
{ color: '#ffffff', position: 80 },
|
|
59
|
+
]
|
|
60
|
+
expect(colorAt(stops, 0)).toBe('#000000')
|
|
61
|
+
expect(colorAt(stops, 100)).toBe('#ffffff')
|
|
62
|
+
// halfway in oklab, which is lighter than the srgb #808080
|
|
63
|
+
expect(colorAt(stops, 50)).toBe(mixColors('#000000', '#ffffff', 0.5, 'oklab'))
|
|
64
|
+
expect(colorAt(stops, 50)).not.toBe('#808080')
|
|
65
|
+
expect(colorAt(DEFAULT_GRADIENT.stops, 50)).toBe('#0000004d')
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
angleBetween,
|
|
4
|
+
clampToSquare,
|
|
5
|
+
isInSquare,
|
|
6
|
+
linearHandlePoint,
|
|
7
|
+
farthestCornerAngle,
|
|
8
|
+
linearFromPoints,
|
|
9
|
+
linearLength,
|
|
10
|
+
linearPoint,
|
|
11
|
+
linearPosition,
|
|
12
|
+
projectOnSegment,
|
|
13
|
+
radialEllipse,
|
|
14
|
+
radialPoint,
|
|
15
|
+
radialPosition,
|
|
16
|
+
radialRay,
|
|
17
|
+
radialT,
|
|
18
|
+
radialAngle,
|
|
19
|
+
viaFraction,
|
|
20
|
+
lerpPoint,
|
|
21
|
+
} from './gradientGeometry'
|
|
22
|
+
|
|
23
|
+
const S = 176
|
|
24
|
+
|
|
25
|
+
describe('linear geometry', () => {
|
|
26
|
+
it('has the CSS gradient line length', () => {
|
|
27
|
+
expect(linearLength(0, S)).toBeCloseTo(S)
|
|
28
|
+
expect(linearLength(90, S)).toBeCloseTo(S)
|
|
29
|
+
expect(linearLength(45, S)).toBeCloseTo(S * Math.SQRT2)
|
|
30
|
+
expect(linearLength(135, S)).toBeCloseTo(S * Math.SQRT2)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('puts 0 % and 100 % where CSS does', () => {
|
|
34
|
+
// 0° runs upwards: 0 % at the bottom edge, 100 % at the top edge.
|
|
35
|
+
expect(linearPoint(0, 0, S)).toMatchObject({ x: S / 2, y: S })
|
|
36
|
+
expect(linearPoint(0, 100, S).y).toBeCloseTo(0)
|
|
37
|
+
// 90° runs to the right.
|
|
38
|
+
expect(linearPoint(90, 0, S).x).toBeCloseTo(0)
|
|
39
|
+
expect(linearPoint(90, 100, S).x).toBeCloseTo(S)
|
|
40
|
+
// 45° and 135° end exactly on the corners of the square.
|
|
41
|
+
expect(linearPoint(45, 100, S).x).toBeCloseTo(S)
|
|
42
|
+
expect(linearPoint(45, 100, S).y).toBeCloseTo(0)
|
|
43
|
+
expect(linearPoint(135, 100, S).x).toBeCloseTo(S)
|
|
44
|
+
expect(linearPoint(135, 100, S).y).toBeCloseTo(S)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('sticks out of the square by at most 0.104·S at 22.5°', () => {
|
|
48
|
+
const end = linearPoint(22.5, 100, S)
|
|
49
|
+
expect(-end.y / S).toBeCloseTo(0.104, 2)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it.each([0, 22, 45, 90, 135, 300])('round-trips angle and positions at %i°', (angle) => {
|
|
53
|
+
const positions = { from: 10, via: 37, to: 85 }
|
|
54
|
+
const from = linearPoint(angle, positions.from, S)
|
|
55
|
+
const to = linearPoint(angle, positions.to, S)
|
|
56
|
+
const f = viaFraction(positions.from, positions.via, positions.to)
|
|
57
|
+
expect(
|
|
58
|
+
linearFromPoints({ from, to, viaFraction: f, previousAngle: 999, size: S, minDistance: 8 }),
|
|
59
|
+
).toEqual({ angle, ...positions })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('projects a point beside the middle line onto the same position', () => {
|
|
63
|
+
const onLine = linearPoint(90, 30, S)
|
|
64
|
+
expect(linearPosition(90, { x: onLine.x, y: onLine.y + 40 }, S)).toBe(30)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('keeps the previous angle for points closer than the minimum distance', () => {
|
|
68
|
+
expect(angleBetween({ x: 10, y: 10 }, { x: 14, y: 14 }, 77, 8)).toBe(77)
|
|
69
|
+
expect(angleBetween({ x: 10, y: 10 }, { x: 10, y: 0 }, 77, 8)).toBe(0)
|
|
70
|
+
expect(angleBetween({ x: 10, y: 10 }, { x: 0, y: 10 }, 77, 8)).toBe(270)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('clamps positions of points beyond the square', () => {
|
|
74
|
+
expect(linearPosition(90, { x: -30, y: 50 }, S)).toBe(0)
|
|
75
|
+
expect(linearPosition(90, { x: S + 30, y: 50 }, S)).toBe(100)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('projects the via point onto the segment, clamped', () => {
|
|
79
|
+
const a = { x: 0, y: 0 }
|
|
80
|
+
const b = { x: 100, y: 0 }
|
|
81
|
+
expect(projectOnSegment({ x: 25, y: 40 }, a, b)).toBeCloseTo(0.25)
|
|
82
|
+
expect(projectOnSegment({ x: -50, y: 0 }, a, b)).toBe(0)
|
|
83
|
+
expect(projectOnSegment({ x: 150, y: 0 }, a, b)).toBe(1)
|
|
84
|
+
expect(lerpPoint(a, b, 0.25)).toEqual({ x: 25, y: 0 })
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('handles inside the square', () => {
|
|
89
|
+
it('clamps a dragged pointer onto the square', () => {
|
|
90
|
+
expect(clampToSquare({ x: -30, y: 200 }, S)).toEqual({ x: 0, y: S })
|
|
91
|
+
expect(clampToSquare({ x: 50, y: 60 }, S)).toEqual({ x: 50, y: 60 })
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('draws 0 % and 100 % at 22° on the edge, along the gradient line', () => {
|
|
95
|
+
const outside = linearPoint(22, 100, S)
|
|
96
|
+
expect(isInSquare(outside, S)).toBe(false)
|
|
97
|
+
const end = linearHandlePoint(22, 100, S)
|
|
98
|
+
const start = linearHandlePoint(22, 0, S)
|
|
99
|
+
expect(isInSquare(end, S)).toBe(true)
|
|
100
|
+
expect(isInSquare(start, S)).toBe(true)
|
|
101
|
+
// on the edge: the top for 100 %, the bottom for 0 %
|
|
102
|
+
expect(end.y).toBeCloseTo(0)
|
|
103
|
+
expect(start.y).toBeCloseTo(S)
|
|
104
|
+
// still on the line through the middle at 22°
|
|
105
|
+
expect(angleBetween(start, end, -1, 1)).toBe(22)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('leaves a canonical point that is inside where it is', () => {
|
|
109
|
+
expect(linearHandlePoint(90, 100, S)).toEqual(linearPoint(90, 100, S))
|
|
110
|
+
expect(linearHandlePoint(22, 50, S)).toEqual(linearPoint(22, 50, S))
|
|
111
|
+
const inner = linearHandlePoint(45, 80, S)
|
|
112
|
+
const canonical = linearPoint(45, 80, S)
|
|
113
|
+
expect(inner.x).toBeCloseTo(canonical.x)
|
|
114
|
+
expect(inner.y).toBeCloseTo(canonical.y)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('radial geometry', () => {
|
|
119
|
+
it('runs a centred ellipse through the corners', () => {
|
|
120
|
+
const ellipse = radialEllipse(50, 50, S)
|
|
121
|
+
expect(ellipse.rx).toBeCloseTo((S / 2) * Math.SQRT2)
|
|
122
|
+
expect(ellipse.ry).toBeCloseTo((S / 2) * Math.SQRT2)
|
|
123
|
+
expect(radialPosition(ellipse, { x: S, y: S })).toBe(100)
|
|
124
|
+
expect(radialPosition(ellipse, { x: S / 2, y: S / 2 })).toBe(0)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('keeps the closest-side proportions and reaches the farthest corner off centre', () => {
|
|
128
|
+
const ellipse = radialEllipse(20, 80, S)
|
|
129
|
+
// closest sides: 20 % horizontally and 20 % vertically, so a circle
|
|
130
|
+
expect(ellipse.rx / ellipse.ry).toBeCloseTo(1)
|
|
131
|
+
// farthest corner is top-right
|
|
132
|
+
expect(radialT(ellipse, { x: S, y: 0 })).toBeCloseTo(1)
|
|
133
|
+
const skewed = radialEllipse(30, 50, S)
|
|
134
|
+
expect(skewed.rx / skewed.ry).toBeCloseTo(0.3 / 0.5)
|
|
135
|
+
expect(radialT(skewed, { x: S, y: 0 })).toBeCloseTo(1)
|
|
136
|
+
expect(radialT(skewed, { x: S, y: S })).toBeCloseTo(1)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('stays within 100 % inside the square', () => {
|
|
140
|
+
const ellipse = radialEllipse(20, 80, S)
|
|
141
|
+
for (const corner of [
|
|
142
|
+
{ x: 0, y: 0 },
|
|
143
|
+
{ x: S, y: 0 },
|
|
144
|
+
{ x: 0, y: S },
|
|
145
|
+
{ x: S, y: S },
|
|
146
|
+
]) {
|
|
147
|
+
expect(radialT(ellipse, corner)).toBeLessThanOrEqual(1 + 1e-9)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('round-trips positions along the ray to the farthest corner', () => {
|
|
152
|
+
const ellipse = radialEllipse(20, 80, S)
|
|
153
|
+
const psi = farthestCornerAngle(ellipse, S)
|
|
154
|
+
const end = radialPoint(ellipse, psi, 100)
|
|
155
|
+
expect(end.x).toBeCloseTo(S)
|
|
156
|
+
expect(end.y).toBeCloseTo(0)
|
|
157
|
+
for (const position of [0, 13, 50, 87, 100]) {
|
|
158
|
+
expect(radialPosition(ellipse, radialPoint(ellipse, psi, position))).toBe(position)
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('clamps the centre to 1–99 so the ellipse never degenerates', () => {
|
|
163
|
+
const ellipse = radialEllipse(0, 100, S)
|
|
164
|
+
expect(Number.isFinite(ellipse.rx)).toBe(true)
|
|
165
|
+
expect(ellipse.rx).toBeGreaterThan(0)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
describe('radialRay', () => {
|
|
169
|
+
it('falls back to the farthest corner when the dragged ray puts the end outside', () => {
|
|
170
|
+
// Repro: centre 30/70, via dragged to (60, 100) would put to = 100 % at (104, -43).
|
|
171
|
+
const ellipse = radialEllipse(30, 70, S)
|
|
172
|
+
const dragged = radialAngle(ellipse, { x: 60, y: 100 })
|
|
173
|
+
const outside = radialPoint(ellipse, dragged, 100)
|
|
174
|
+
expect(outside.x).toBeCloseTo(104, 0)
|
|
175
|
+
expect(outside.y).toBeCloseTo(-43, 0)
|
|
176
|
+
const ray = radialRay(ellipse, dragged, 100, S)
|
|
177
|
+
expect(ray).toBe(farthestCornerAngle(ellipse, S))
|
|
178
|
+
const end = radialPoint(ellipse, ray, 100)
|
|
179
|
+
expect(end.x).toBeCloseTo(S)
|
|
180
|
+
expect(end.y).toBeCloseTo(0)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('falls back when a kept ray no longer fits after the centre moved', () => {
|
|
184
|
+
const before = radialEllipse(30, 70, S)
|
|
185
|
+
const dragged = radialAngle(before, { x: 20, y: 170 })
|
|
186
|
+
const position = radialPosition(before, { x: 20, y: 170 })
|
|
187
|
+
expect(radialRay(before, dragged, position, S)).toBe(dragged)
|
|
188
|
+
|
|
189
|
+
const after = radialEllipse(80, 80, S)
|
|
190
|
+
expect(radialPoint(after, dragged, position).y).toBeGreaterThan(S)
|
|
191
|
+
const ray = radialRay(after, dragged, position, S)
|
|
192
|
+
const end = radialPoint(after, ray, position)
|
|
193
|
+
expect(end.x).toBeGreaterThanOrEqual(0)
|
|
194
|
+
expect(end.y).toBeGreaterThanOrEqual(0)
|
|
195
|
+
expect(end.x).toBeLessThanOrEqual(S)
|
|
196
|
+
expect(end.y).toBeLessThanOrEqual(S)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('keeps a dragged ray while the farthest stop stays inside', () => {
|
|
200
|
+
const ellipse = radialEllipse(50, 50, S)
|
|
201
|
+
const dragged = radialAngle(ellipse, { x: 30, y: 88 })
|
|
202
|
+
expect(radialRay(ellipse, dragged, 40, S)).toBe(dragged)
|
|
203
|
+
expect(radialRay(ellipse, null, 40, S)).toBe(farthestCornerAngle(ellipse, S))
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
})
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
3
|
+
import Combobox from './Combobox.vue'
|
|
4
|
+
import type { ComboboxOption } from '../ComboboxPanel/combobox-types'
|
|
5
|
+
|
|
6
|
+
// Flat args type used for Meta/StoryObj — avoids the discriminated union in
|
|
7
|
+
// ComboboxOptions causing TypeScript to erase props to Record<string,any>.
|
|
8
|
+
type ComboboxStoryArgs = {
|
|
9
|
+
variant?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'link'
|
|
10
|
+
icon?: string
|
|
11
|
+
placeholder?: string
|
|
12
|
+
placement?: 'left' | 'right'
|
|
13
|
+
matchWidth?: boolean
|
|
14
|
+
showChips?: boolean
|
|
15
|
+
fullWidth?: boolean
|
|
16
|
+
singleSelect?: boolean
|
|
17
|
+
options: ComboboxOption[]
|
|
18
|
+
allowNew?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CITIES = [
|
|
22
|
+
{ label: 'Almere', value: 'ALM' },
|
|
23
|
+
{ label: 'Amersfoort', value: 'AMF' },
|
|
24
|
+
{ label: 'Amsterdam', value: 'AMS' },
|
|
25
|
+
{ label: 'Apeldoorn', value: 'APD' },
|
|
26
|
+
{ label: 'Arnhem', value: 'ARN' },
|
|
27
|
+
{ label: 'Bolsward', value: 'BOL' },
|
|
28
|
+
{ label: 'Breda', value: 'BRD' },
|
|
29
|
+
{ label: 'Den Haag', value: 'DHG' },
|
|
30
|
+
{ label: 'Dokkum', value: 'DOK' },
|
|
31
|
+
{ label: 'Dordrecht', value: 'DOR' },
|
|
32
|
+
{ label: 'Eindhoven', value: 'EHV' },
|
|
33
|
+
{ label: 'Enschede', value: 'ENS' },
|
|
34
|
+
{ label: 'Franeker', value: 'FRK' },
|
|
35
|
+
{ label: 'Groningen', value: 'GRQ' },
|
|
36
|
+
{ label: 'Haarlem', value: 'HAA' },
|
|
37
|
+
{ label: 'Harlingen', value: 'HRL' },
|
|
38
|
+
{ label: 'Hindeloopen', value: 'HIN' },
|
|
39
|
+
{ label: 'IJlst', value: 'IJL' },
|
|
40
|
+
{ label: 'Leeuwarden', value: 'LWR' },
|
|
41
|
+
{ label: 'Leiden', value: 'LEI' },
|
|
42
|
+
{ label: 'Maastricht', value: 'MST' },
|
|
43
|
+
{ label: 'Nijmegen', value: 'NJM' },
|
|
44
|
+
{ label: 'Rotterdam', value: 'RTM' },
|
|
45
|
+
{ label: 'Sloten', value: 'SLT' },
|
|
46
|
+
{ label: 'Sneek', value: 'SNK' },
|
|
47
|
+
{ label: 'Stavoren', value: 'STV' },
|
|
48
|
+
{ label: "'s-Hertogenbosch", value: 'HTG' },
|
|
49
|
+
{ label: 'Tilburg', value: 'TLB' },
|
|
50
|
+
{ label: 'Utrecht', value: 'UTC' },
|
|
51
|
+
{ label: 'Workum', value: 'WRK' },
|
|
52
|
+
{ label: 'Zaanstad', value: 'ZAA' },
|
|
53
|
+
{ label: 'Zwolle', value: 'ZWO' },
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
title: 'Components/Organisms/Combobox',
|
|
58
|
+
// Cast needed: withDefaults + discriminated union props cause Storybook's
|
|
59
|
+
// component type check to fail; ComboboxStoryArgs is used for args instead.
|
|
60
|
+
component: Combobox as unknown as Meta<ComboboxStoryArgs>['component'],
|
|
61
|
+
decorators: [
|
|
62
|
+
() => ({
|
|
63
|
+
template:
|
|
64
|
+
'<div style="min-height: 450px; padding: 16px; display: flex; align-items: start; justify-content: center; position: relative;"><story /></div>',
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
} satisfies Meta<ComboboxStoryArgs>
|
|
68
|
+
|
|
69
|
+
type Story = StoryObj<ComboboxStoryArgs>
|
|
70
|
+
|
|
71
|
+
export const StringOptions: Story = {
|
|
72
|
+
render: (args) => ({
|
|
73
|
+
components: { Combobox },
|
|
74
|
+
setup() {
|
|
75
|
+
const selected = ref<string[]>([])
|
|
76
|
+
const remove = (val: string) => {
|
|
77
|
+
selected.value = selected.value.filter((v) => v !== val)
|
|
78
|
+
}
|
|
79
|
+
return { args, selected, remove }
|
|
80
|
+
},
|
|
81
|
+
template: `
|
|
82
|
+
<Combobox v-bind="args" v-model="selected">Steden</Combobox>
|
|
83
|
+
<div style="position: absolute; top: 0; right: 0; font-size: 12px; color: #6b7280;">
|
|
84
|
+
<div v-for="val in selected" :key="val" style="cursor: pointer;" @click="remove(val)">{{ val }}</div>
|
|
85
|
+
<em v-if="!selected.length">Geen selectie</em>
|
|
86
|
+
</div>
|
|
87
|
+
`,
|
|
88
|
+
}),
|
|
89
|
+
args: {
|
|
90
|
+
placeholder: 'Zoeken...',
|
|
91
|
+
options: CITIES.map((city) => city.label),
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const AllowNew: Story = {
|
|
96
|
+
render: (args) => ({
|
|
97
|
+
components: { Combobox },
|
|
98
|
+
setup() {
|
|
99
|
+
const selected = ref<(string | number)[]>([])
|
|
100
|
+
const remove = (val: string | number) => {
|
|
101
|
+
selected.value = selected.value.filter((v) => v !== val)
|
|
102
|
+
}
|
|
103
|
+
return { args, selected, remove }
|
|
104
|
+
},
|
|
105
|
+
template: `
|
|
106
|
+
<Combobox v-bind="args" v-model="selected">Tags</Combobox>
|
|
107
|
+
<div style="position: absolute; top: 0; right: 0; font-size: 12px; color: #6b7280;">
|
|
108
|
+
<div v-for="val in selected" :key="val" style="cursor: pointer;" @click="remove(val)">{{ val }}</div>
|
|
109
|
+
<em v-if="!selected.length">Geen selectie</em>
|
|
110
|
+
</div>
|
|
111
|
+
`,
|
|
112
|
+
}),
|
|
113
|
+
args: {
|
|
114
|
+
placeholder: 'Zoeken of toevoegen...',
|
|
115
|
+
options: CITIES.map((city) => city.label),
|
|
116
|
+
allowNew: true,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const ObjectOptions: Story = {
|
|
121
|
+
render: (args) => ({
|
|
122
|
+
components: { Combobox },
|
|
123
|
+
setup() {
|
|
124
|
+
const selected = ref<(string | number)[]>([])
|
|
125
|
+
const remove = (val: string | number) => {
|
|
126
|
+
selected.value = selected.value.filter((v) => v !== val)
|
|
127
|
+
}
|
|
128
|
+
return { args, selected, remove }
|
|
129
|
+
},
|
|
130
|
+
template: `
|
|
131
|
+
<Combobox v-bind="args" v-model="selected">Steden</Combobox>
|
|
132
|
+
<div style="position: absolute; top: 0; right: 0; font-size: 12px; color: #6b7280;">
|
|
133
|
+
<div v-for="val in selected" :key="val" style="cursor: pointer;" @click="remove(val)">{{ val }}</div>
|
|
134
|
+
<em v-if="!selected.length">Geen selectie</em>
|
|
135
|
+
</div>
|
|
136
|
+
`,
|
|
137
|
+
}),
|
|
138
|
+
args: {
|
|
139
|
+
placeholder: 'Selecteer een stad...',
|
|
140
|
+
options: CITIES,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const SingleSelect: Story = {
|
|
145
|
+
render: (args) => ({
|
|
146
|
+
components: { Combobox },
|
|
147
|
+
setup() {
|
|
148
|
+
const selected = ref<(string | number)[]>([])
|
|
149
|
+
return { args, selected }
|
|
150
|
+
},
|
|
151
|
+
template: `
|
|
152
|
+
<Combobox v-bind="args" v-model="selected">{{ selected[0] ?? 'Kies een stad' }}</Combobox>
|
|
153
|
+
<div style="position: absolute; top: 0; right: 0; font-size: 12px; color: #6b7280;">
|
|
154
|
+
<span>{{ selected[0] ?? '—' }}</span>
|
|
155
|
+
</div>
|
|
156
|
+
`,
|
|
157
|
+
}),
|
|
158
|
+
args: {
|
|
159
|
+
placeholder: 'Zoeken...',
|
|
160
|
+
options: CITIES,
|
|
161
|
+
singleSelect: true,
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const MatchWidth: Story = {
|
|
166
|
+
render: (args) => ({
|
|
167
|
+
components: { Combobox },
|
|
168
|
+
setup() {
|
|
169
|
+
const selected = ref<(string | number)[]>([])
|
|
170
|
+
return { args, selected }
|
|
171
|
+
},
|
|
172
|
+
template: `
|
|
173
|
+
<div style="width: 320px;">
|
|
174
|
+
<Combobox v-bind="args" v-model="selected" style="width: 100%;" full-width match-width>Kies een stad</Combobox>
|
|
175
|
+
</div>
|
|
176
|
+
`,
|
|
177
|
+
}),
|
|
178
|
+
args: {
|
|
179
|
+
placeholder: 'Zoeken...',
|
|
180
|
+
options: CITIES,
|
|
181
|
+
matchWidth: true,
|
|
182
|
+
fullWidth: true,
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const FullWidth: Story = {
|
|
187
|
+
decorators: [
|
|
188
|
+
() => ({
|
|
189
|
+
template:
|
|
190
|
+
'<div style="min-height: 450px; padding: 16px; display: flex; flex-direction: column; gap: 8px; position: relative;"><story /></div>',
|
|
191
|
+
}),
|
|
192
|
+
],
|
|
193
|
+
render: (args) => ({
|
|
194
|
+
components: { Combobox },
|
|
195
|
+
setup() {
|
|
196
|
+
const selected = ref<(string | number)[]>([])
|
|
197
|
+
return { args, selected }
|
|
198
|
+
},
|
|
199
|
+
template: `
|
|
200
|
+
<p style="font-size: 12px; color: #6b7280; margin: 0;">The combobox below stretches to fill its container width.</p>
|
|
201
|
+
<div style="width: 100%; border: 1px dashed #d1d5db; padding: 12px; border-radius: 8px; box-sizing: border-box;">
|
|
202
|
+
<Combobox v-bind="args" v-model="selected">Kies een stad</Combobox>
|
|
203
|
+
</div>
|
|
204
|
+
`,
|
|
205
|
+
}),
|
|
206
|
+
args: {
|
|
207
|
+
placeholder: 'Zoeken...',
|
|
208
|
+
options: CITIES,
|
|
209
|
+
fullWidth: true,
|
|
210
|
+
matchWidth: true,
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const HideChips: Story = {
|
|
215
|
+
render: (args) => ({
|
|
216
|
+
components: { Combobox },
|
|
217
|
+
setup() {
|
|
218
|
+
const selected = ref<(string | number)[]>([])
|
|
219
|
+
return { args, selected }
|
|
220
|
+
},
|
|
221
|
+
template: `
|
|
222
|
+
<Combobox v-bind="args" v-model="selected">Steden</Combobox>
|
|
223
|
+
<div style="position: absolute; top: 0; right: 0; font-size: 12px; color: #6b7280;">
|
|
224
|
+
<div v-for="val in selected" :key="val">{{ val }}</div>
|
|
225
|
+
<em v-if="!selected.length">Geen selectie</em>
|
|
226
|
+
</div>
|
|
227
|
+
`,
|
|
228
|
+
}),
|
|
229
|
+
args: {
|
|
230
|
+
placeholder: 'Zoeken...',
|
|
231
|
+
options: CITIES,
|
|
232
|
+
showChips: false,
|
|
233
|
+
},
|
|
234
|
+
}
|