@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,476 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { expect, fn, userEvent, waitFor, within } from 'storybook/test'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
import FontPicker, { type NexxtFontPickerFont } from './FontPicker.vue'
|
|
5
|
+
|
|
6
|
+
const WEB_SAFE: NexxtFontPickerFont[] = [
|
|
7
|
+
'Arial',
|
|
8
|
+
'Helvetica',
|
|
9
|
+
'Verdana',
|
|
10
|
+
'Tahoma',
|
|
11
|
+
'Trebuchet MS',
|
|
12
|
+
'Georgia',
|
|
13
|
+
'Times New Roman',
|
|
14
|
+
'Palatino',
|
|
15
|
+
'Courier New',
|
|
16
|
+
].map((family) => ({ family, source: 'web-safe' }))
|
|
17
|
+
|
|
18
|
+
/** A slice of the Google catalogue, by popularity. Stories load no faces, so rows use the fallback. */
|
|
19
|
+
const GOOGLE: NexxtFontPickerFont[] = [
|
|
20
|
+
'Roboto',
|
|
21
|
+
'Open Sans',
|
|
22
|
+
'Noto Sans JP',
|
|
23
|
+
'Montserrat',
|
|
24
|
+
'Inter',
|
|
25
|
+
'Poppins',
|
|
26
|
+
'Lato',
|
|
27
|
+
'Roboto Condensed',
|
|
28
|
+
'Oswald',
|
|
29
|
+
'Raleway',
|
|
30
|
+
'Nunito',
|
|
31
|
+
'Ubuntu',
|
|
32
|
+
'Playfair Display',
|
|
33
|
+
'Rubik',
|
|
34
|
+
'Merriweather',
|
|
35
|
+
'Work Sans',
|
|
36
|
+
'Kanit',
|
|
37
|
+
'PT Sans',
|
|
38
|
+
'Lora',
|
|
39
|
+
'Fira Sans',
|
|
40
|
+
'Quicksand',
|
|
41
|
+
'Barlow',
|
|
42
|
+
'Mulish',
|
|
43
|
+
'Inconsolata',
|
|
44
|
+
'IBM Plex Sans',
|
|
45
|
+
'Titillium Web',
|
|
46
|
+
'DM Sans',
|
|
47
|
+
'Heebo',
|
|
48
|
+
'Libre Baskerville',
|
|
49
|
+
'Karla',
|
|
50
|
+
'Josefin Sans',
|
|
51
|
+
'Dancing Script',
|
|
52
|
+
'Source Code Pro',
|
|
53
|
+
'Libre Franklin',
|
|
54
|
+
'Cabin',
|
|
55
|
+
'Bebas Neue',
|
|
56
|
+
'Arimo',
|
|
57
|
+
'Manrope',
|
|
58
|
+
'Hind Siliguri',
|
|
59
|
+
'Space Grotesk',
|
|
60
|
+
].map((family) => ({ family, source: 'google' }))
|
|
61
|
+
|
|
62
|
+
const FONTS = [...WEB_SAFE, ...GOOGLE]
|
|
63
|
+
|
|
64
|
+
const LONG_FAMILY = 'Noto Serif Display Extra Condensed Semi Expanded Italic'
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
title: 'Components/Organisms/FontPicker',
|
|
68
|
+
component: FontPicker,
|
|
69
|
+
// Room for the 360px panel under the trigger, also when Docs renders the story inline
|
|
70
|
+
decorators: [() => ({ template: '<div class="min-h-112 p-2"><story /></div>' })],
|
|
71
|
+
args: {
|
|
72
|
+
fonts: FONTS,
|
|
73
|
+
catalogueStatus: 'ready',
|
|
74
|
+
loadFont: fn(),
|
|
75
|
+
onOpen: fn(),
|
|
76
|
+
onClose: fn(),
|
|
77
|
+
onRetry: fn(),
|
|
78
|
+
},
|
|
79
|
+
argTypes: {
|
|
80
|
+
catalogueStatus: { control: 'inline-radio', options: ['loading', 'ready', 'error'] },
|
|
81
|
+
},
|
|
82
|
+
} satisfies Meta<typeof FontPicker>
|
|
83
|
+
|
|
84
|
+
type Story = StoryObj<typeof FontPicker>
|
|
85
|
+
|
|
86
|
+
/** The settings panel of the editor is 312px of content; the panel is 320 and sticks out 8px. */
|
|
87
|
+
const EDITOR_WIDTH = 'w-78'
|
|
88
|
+
|
|
89
|
+
const renderPicker =
|
|
90
|
+
(initial: string | null, frame = EDITOR_WIDTH) =>
|
|
91
|
+
(args: Story['args']) => ({
|
|
92
|
+
components: { FontPicker },
|
|
93
|
+
setup() {
|
|
94
|
+
const font = ref<string | null>(initial)
|
|
95
|
+
return { args, font, frame }
|
|
96
|
+
},
|
|
97
|
+
template: `
|
|
98
|
+
<div class="flex flex-col gap-3">
|
|
99
|
+
<div :class="frame">
|
|
100
|
+
<FontPicker v-bind="args" v-model="font" />
|
|
101
|
+
</div>
|
|
102
|
+
<p class="extra-small-normal text-gray-700">
|
|
103
|
+
model: <code data-testid="model">{{ font === null ? 'null' : font }}</code>
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
`,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const triggerOf = (canvasElement: HTMLElement) =>
|
|
110
|
+
canvasElement.querySelector<HTMLButtonElement>('button[aria-haspopup="listbox"]')!
|
|
111
|
+
|
|
112
|
+
const openPanel = async (canvasElement: HTMLElement) => {
|
|
113
|
+
const canvas = within(canvasElement)
|
|
114
|
+
await userEvent.click(triggerOf(canvasElement))
|
|
115
|
+
const input = await canvas.findByRole('combobox')
|
|
116
|
+
await waitFor(() => expect(input).toHaveFocus())
|
|
117
|
+
return input
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The panel scales in for 200ms; layout sizes are not scaled, rects are. */
|
|
121
|
+
const settled = (element: HTMLElement) =>
|
|
122
|
+
waitFor(() => expect(getComputedStyle(element.closest('.z-50 > div')!).opacity).toBe('1'))
|
|
123
|
+
|
|
124
|
+
const activeOption = (input: HTMLElement) =>
|
|
125
|
+
document.getElementById(input.getAttribute('aria-activedescendant') ?? '')
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The editor: no visible label, `inherit` on top. `null` on the model is "Huisstijl", so the
|
|
129
|
+
* trigger reads "Huisstijl · Montserrat" in the house-style font.
|
|
130
|
+
*/
|
|
131
|
+
export const Editor: Story = {
|
|
132
|
+
args: {
|
|
133
|
+
ariaLabel: 'Lettertype',
|
|
134
|
+
inherit: { label: 'Huisstijl', family: 'Georgia' },
|
|
135
|
+
},
|
|
136
|
+
render: renderPicker(null),
|
|
137
|
+
play: async ({ canvasElement, args }) => {
|
|
138
|
+
const canvas = within(canvasElement)
|
|
139
|
+
const trigger = triggerOf(canvasElement)
|
|
140
|
+
|
|
141
|
+
// the visible value is part of the name
|
|
142
|
+
await expect(trigger).toHaveAccessibleName('Lettertype, Huisstijl · Georgia')
|
|
143
|
+
const value = trigger.querySelector<HTMLElement>('[id$="-value"]')!
|
|
144
|
+
await expect(value.style.fontFamily).toContain('Georgia')
|
|
145
|
+
|
|
146
|
+
const input = await openPanel(canvasElement)
|
|
147
|
+
await expect(args.onOpen).toHaveBeenCalledOnce()
|
|
148
|
+
|
|
149
|
+
// Huisstijl first and ungrouped, then the two groups; the current value is highlighted
|
|
150
|
+
const listbox = canvas.getByRole('listbox')
|
|
151
|
+
const first = listbox.firstElementChild as HTMLElement
|
|
152
|
+
await expect(first).toHaveAttribute('role', 'option')
|
|
153
|
+
await expect(activeOption(input)).toBe(first)
|
|
154
|
+
await expect(first).toHaveAccessibleDescription(
|
|
155
|
+
'Huidige keuze. Volgt de huisstijl, ook als die later verandert',
|
|
156
|
+
)
|
|
157
|
+
await settled(listbox)
|
|
158
|
+
await expect(canvas.getByRole('group', { name: 'Standaard' })).toBeVisible()
|
|
159
|
+
await expect(canvas.getByRole('group', { name: 'Google Fonts' })).toBeInTheDocument()
|
|
160
|
+
|
|
161
|
+
// 320px wide against a 312px trigger, and inside the viewport
|
|
162
|
+
const panel = canvas.getByRole('group', { name: 'Lettertype' })
|
|
163
|
+
await expect(panel.offsetWidth).toBe(320)
|
|
164
|
+
await expect(panel.getBoundingClientRect().right).toBeLessThanOrEqual(
|
|
165
|
+
document.documentElement.clientWidth,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
// a choice writes the model, closes the panel and puts focus back on the trigger
|
|
169
|
+
await userEvent.click(canvas.getByRole('option', { name: 'Verdana' }))
|
|
170
|
+
await waitFor(() => expect(canvas.queryByRole('listbox')).toBeNull())
|
|
171
|
+
await expect(trigger).toHaveFocus()
|
|
172
|
+
await expect(canvas.getByTestId('model')).toHaveTextContent('Verdana')
|
|
173
|
+
await expect(trigger).toHaveAccessibleName('Lettertype, Verdana')
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The house style has no font of its own: just "Huisstijl", in the sans font. */
|
|
178
|
+
export const InheritWithoutFont: Story = {
|
|
179
|
+
args: {
|
|
180
|
+
ariaLabel: 'Lettertype',
|
|
181
|
+
inherit: { label: 'Huisstijl' },
|
|
182
|
+
},
|
|
183
|
+
render: renderPicker(null),
|
|
184
|
+
play: async ({ canvasElement }) => {
|
|
185
|
+
await expect(triggerOf(canvasElement)).toHaveAccessibleName('Lettertype, Huisstijl')
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* A form: visible label, placeholder, no inherit option. The panel follows the trigger's width
|
|
191
|
+
* (at least 320px).
|
|
192
|
+
*/
|
|
193
|
+
export const Form: Story = {
|
|
194
|
+
args: {
|
|
195
|
+
label: 'Lettertype voor koppen',
|
|
196
|
+
placeholder: 'Kies een lettertype',
|
|
197
|
+
},
|
|
198
|
+
render: renderPicker(null, 'w-96'),
|
|
199
|
+
play: async ({ canvasElement }) => {
|
|
200
|
+
const canvas = within(canvasElement)
|
|
201
|
+
const trigger = triggerOf(canvasElement)
|
|
202
|
+
|
|
203
|
+
await expect(trigger).toHaveAccessibleName('Lettertype voor koppen Kies een lettertype')
|
|
204
|
+
await openPanel(canvasElement)
|
|
205
|
+
|
|
206
|
+
const panel = canvas.getByRole('group', { name: 'Lettertype voor koppen' })
|
|
207
|
+
await expect(panel.offsetWidth).toBe(trigger.offsetWidth)
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** A long name is cut off with an ellipsis; the trigger keeps the width of its container. */
|
|
212
|
+
export const LongName: Story = {
|
|
213
|
+
args: {
|
|
214
|
+
ariaLabel: 'Lettertype',
|
|
215
|
+
inherit: { label: 'Huisstijl', family: LONG_FAMILY },
|
|
216
|
+
},
|
|
217
|
+
render: renderPicker(null),
|
|
218
|
+
play: async ({ canvasElement }) => {
|
|
219
|
+
const trigger = triggerOf(canvasElement)
|
|
220
|
+
const frame = trigger.closest('.w-78')!
|
|
221
|
+
const [label, family] = Array.from(
|
|
222
|
+
trigger.querySelectorAll<HTMLElement>('[id$="-value"] > span'),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
await expect(trigger.getBoundingClientRect().width).toBe(frame.getBoundingClientRect().width)
|
|
226
|
+
// "Huisstijl" stays whole, the family is the part that truncates
|
|
227
|
+
await expect(label.scrollWidth).toBe(label.clientWidth)
|
|
228
|
+
await expect(family.scrollWidth).toBeGreaterThan(family.clientWidth)
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The current family is no longer in the catalogue; it is still listed, at the top. */
|
|
233
|
+
export const CurrentNotInCatalogue: Story = {
|
|
234
|
+
args: { ariaLabel: 'Lettertype' },
|
|
235
|
+
render: renderPicker('Comic Neue'),
|
|
236
|
+
play: async ({ canvasElement }) => {
|
|
237
|
+
const input = await openPanel(canvasElement)
|
|
238
|
+
const first = within(canvasElement).getByRole('listbox').firstElementChild
|
|
239
|
+
|
|
240
|
+
await expect(activeOption(input)).toBe(first)
|
|
241
|
+
await expect(first).toHaveAccessibleName('Comic Neue')
|
|
242
|
+
await expect(first).toHaveAccessibleDescription('Huidige keuze')
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The catalogue is on its way: the web-safe families can be chosen right away, with skeleton rows
|
|
248
|
+
* under "Google Fonts". A search without results keeps the skeletons; there is no empty state yet.
|
|
249
|
+
*/
|
|
250
|
+
export const Loading: Story = {
|
|
251
|
+
args: {
|
|
252
|
+
ariaLabel: 'Lettertype',
|
|
253
|
+
fonts: WEB_SAFE,
|
|
254
|
+
catalogueStatus: 'loading',
|
|
255
|
+
inherit: { label: 'Huisstijl', family: 'Georgia' },
|
|
256
|
+
},
|
|
257
|
+
render: renderPicker(null),
|
|
258
|
+
play: async ({ canvasElement }) => {
|
|
259
|
+
const canvas = within(canvasElement)
|
|
260
|
+
const input = await openPanel(canvasElement)
|
|
261
|
+
const listbox = canvas.getByRole('listbox')
|
|
262
|
+
|
|
263
|
+
await expect(listbox).toHaveAttribute('aria-busy', 'true')
|
|
264
|
+
await settled(listbox)
|
|
265
|
+
await expect(canvas.getByRole('option', { name: 'Arial' })).toBeVisible()
|
|
266
|
+
await expect(canvas.queryByRole('option', { name: 'Roboto' })).toBeNull()
|
|
267
|
+
await expect(listbox.querySelectorAll('[aria-hidden="true"] .animate-pulse')).toHaveLength(6)
|
|
268
|
+
|
|
269
|
+
// grouped rows and the skeletons start 12px in from the group heading; Huisstijl does not
|
|
270
|
+
const heading = canvas.getByText('Standaard')
|
|
271
|
+
const left = (element: Element) => Math.round(element.getBoundingClientRect().left)
|
|
272
|
+
const textOf = (name: string) =>
|
|
273
|
+
canvas
|
|
274
|
+
.getByRole('option', { name: new RegExp(`^${name}`) })
|
|
275
|
+
.querySelector('[data-font-family] > span')!
|
|
276
|
+
const step = Math.round(12 * (heading.getBoundingClientRect().width / heading.offsetWidth))
|
|
277
|
+
await expect(left(textOf('Arial'))).toBe(left(heading) + step)
|
|
278
|
+
await expect(left(textOf('Huisstijl'))).toBe(left(heading))
|
|
279
|
+
await expect(left(listbox.querySelector('.animate-pulse')!)).toBe(left(textOf('Arial')))
|
|
280
|
+
|
|
281
|
+
await userEvent.type(input, 'roboto')
|
|
282
|
+
await expect(canvas.queryByText('Geen lettertypes gevonden')).toBeNull()
|
|
283
|
+
await expect(listbox.querySelectorAll('.animate-pulse')).toHaveLength(6)
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The catalogue failed: only Huisstijl and "Standaard", and a notice under the list. "Opnieuw
|
|
289
|
+
* proberen" really fetches again here: it goes to loading, and after a moment to ready.
|
|
290
|
+
*/
|
|
291
|
+
export const CatalogueError: Story = {
|
|
292
|
+
args: {
|
|
293
|
+
ariaLabel: 'Lettertype',
|
|
294
|
+
inherit: { label: 'Huisstijl', family: 'Georgia' },
|
|
295
|
+
},
|
|
296
|
+
render: (args) => ({
|
|
297
|
+
components: { FontPicker },
|
|
298
|
+
setup() {
|
|
299
|
+
const font = ref<string | null>(null)
|
|
300
|
+
const status = ref<'loading' | 'ready' | 'error'>('error')
|
|
301
|
+
const fonts = ref<NexxtFontPickerFont[]>(WEB_SAFE)
|
|
302
|
+
// `args.onRetry` is bound through v-bind as well, so the spy sees the click on its own
|
|
303
|
+
const onRetry = () => {
|
|
304
|
+
status.value = 'loading'
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
fonts.value = FONTS
|
|
307
|
+
status.value = 'ready'
|
|
308
|
+
}, 600)
|
|
309
|
+
}
|
|
310
|
+
return { args, font, status, fonts, onRetry }
|
|
311
|
+
},
|
|
312
|
+
template: `
|
|
313
|
+
<div class="w-78">
|
|
314
|
+
<FontPicker v-bind="args" v-model="font" :fonts="fonts" :catalogue-status="status" @retry="onRetry" />
|
|
315
|
+
</div>
|
|
316
|
+
`,
|
|
317
|
+
}),
|
|
318
|
+
play: async ({ canvasElement, args }) => {
|
|
319
|
+
const canvas = within(canvasElement)
|
|
320
|
+
const input = await openPanel(canvasElement)
|
|
321
|
+
|
|
322
|
+
await expect(canvas.queryByRole('group', { name: 'Google Fonts' })).toBeNull()
|
|
323
|
+
const retry = canvas.getByRole('button', { name: 'Opnieuw proberen' })
|
|
324
|
+
await expect(retry.closest('[role="status"]')).toHaveTextContent(
|
|
325
|
+
'Google Fonts konden niet worden geladen.',
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
// search field → listbox → retry: three tab stops, the notice last
|
|
329
|
+
await expect(input).toHaveFocus()
|
|
330
|
+
await userEvent.tab()
|
|
331
|
+
await expect(canvas.getByRole('listbox')).toHaveFocus()
|
|
332
|
+
await userEvent.tab()
|
|
333
|
+
await expect(retry).toHaveFocus()
|
|
334
|
+
|
|
335
|
+
await userEvent.click(retry)
|
|
336
|
+
await expect(args.onRetry).toHaveBeenCalledOnce()
|
|
337
|
+
await waitFor(() =>
|
|
338
|
+
expect(canvas.getByRole('group', { name: 'Google Fonts' })).toBeInTheDocument(),
|
|
339
|
+
)
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Nothing matches: the empty state, with a button that clears the search. */
|
|
344
|
+
export const NoResults: Story = {
|
|
345
|
+
args: { ariaLabel: 'Lettertype' },
|
|
346
|
+
render: renderPicker('Arial'),
|
|
347
|
+
play: async ({ canvasElement }) => {
|
|
348
|
+
const canvas = within(canvasElement)
|
|
349
|
+
const input = await openPanel(canvasElement)
|
|
350
|
+
|
|
351
|
+
await userEvent.type(input, 'zzz')
|
|
352
|
+
await settled(input)
|
|
353
|
+
await expect(await canvas.findByText('Geen lettertypes gevonden')).toBeVisible()
|
|
354
|
+
await expect(canvas.getByText('Niets gevonden voor ‘zzz’.')).toBeVisible()
|
|
355
|
+
|
|
356
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Zoekopdracht wissen' }))
|
|
357
|
+
await expect(input).toHaveValue('')
|
|
358
|
+
await expect(input).toHaveFocus()
|
|
359
|
+
},
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* The keyboard from the search field: arrows and PageDown move the highlight, Enter chooses,
|
|
364
|
+
* Escape first clears and then closes, and Tab out of the panel closes it without moving focus.
|
|
365
|
+
*/
|
|
366
|
+
export const Keyboard: Story = {
|
|
367
|
+
args: { ariaLabel: 'Lettertype' },
|
|
368
|
+
render: (args) => ({
|
|
369
|
+
components: { FontPicker },
|
|
370
|
+
setup() {
|
|
371
|
+
const font = ref<string | null>('Arial')
|
|
372
|
+
return { args, font }
|
|
373
|
+
},
|
|
374
|
+
template: `
|
|
375
|
+
<div class="flex w-78 flex-col gap-3">
|
|
376
|
+
<FontPicker v-bind="args" v-model="font" />
|
|
377
|
+
<button type="button" class="self-start small-normal text-gray-900 underline">Volgende veld</button>
|
|
378
|
+
<code data-testid="model" class="extra-small-normal text-gray-700">{{ font }}</code>
|
|
379
|
+
</div>
|
|
380
|
+
`,
|
|
381
|
+
}),
|
|
382
|
+
play: async ({ canvasElement }) => {
|
|
383
|
+
const canvas = within(canvasElement)
|
|
384
|
+
const trigger = triggerOf(canvasElement)
|
|
385
|
+
|
|
386
|
+
trigger.focus()
|
|
387
|
+
await userEvent.keyboard('{ArrowDown}')
|
|
388
|
+
const input = await canvas.findByRole('combobox')
|
|
389
|
+
await waitFor(() => expect(input).toHaveFocus())
|
|
390
|
+
await expect(activeOption(input)).toHaveAccessibleName('Arial')
|
|
391
|
+
|
|
392
|
+
await userEvent.keyboard('{ArrowDown}{ArrowDown}')
|
|
393
|
+
await expect(activeOption(input)).toHaveAccessibleName('Verdana')
|
|
394
|
+
// eight down from Verdana: past the end of "Standaard" into "Google Fonts"
|
|
395
|
+
await userEvent.keyboard('{PageDown}')
|
|
396
|
+
await expect(activeOption(input)).toHaveAccessibleName('Open Sans')
|
|
397
|
+
await userEvent.keyboard('{ArrowUp}')
|
|
398
|
+
await expect(activeOption(input)).toHaveAccessibleName('Roboto')
|
|
399
|
+
await expect(canvas.getByTestId('model')).toHaveTextContent('Arial')
|
|
400
|
+
|
|
401
|
+
await userEvent.keyboard('{Enter}')
|
|
402
|
+
await waitFor(() => expect(canvas.queryByRole('listbox')).toBeNull())
|
|
403
|
+
await expect(trigger).toHaveFocus()
|
|
404
|
+
await expect(canvas.getByTestId('model')).toHaveTextContent('Roboto')
|
|
405
|
+
|
|
406
|
+
// Escape: clear, then close
|
|
407
|
+
await userEvent.keyboard('{Enter}')
|
|
408
|
+
const again = await canvas.findByRole('combobox')
|
|
409
|
+
await waitFor(() => expect(again).toHaveFocus())
|
|
410
|
+
await userEvent.type(again, 'ro')
|
|
411
|
+
await userEvent.keyboard('{Escape}')
|
|
412
|
+
await expect(again).toHaveValue('')
|
|
413
|
+
await expect(canvas.getByRole('listbox')).toBeInTheDocument()
|
|
414
|
+
await userEvent.keyboard('{Escape}')
|
|
415
|
+
await waitFor(() => expect(canvas.queryByRole('listbox')).toBeNull())
|
|
416
|
+
await expect(trigger).toHaveFocus()
|
|
417
|
+
|
|
418
|
+
// Tab out of the panel: closed, and focus is where Tab put it
|
|
419
|
+
await userEvent.keyboard('{Enter}')
|
|
420
|
+
await waitFor(() => expect(canvas.getByRole('combobox')).toHaveFocus())
|
|
421
|
+
await userEvent.tab()
|
|
422
|
+
await expect(canvas.getByRole('listbox')).toHaveFocus()
|
|
423
|
+
await userEvent.tab()
|
|
424
|
+
const next = canvas.getByRole('button', { name: 'Volgende veld' })
|
|
425
|
+
await waitFor(() => expect(canvas.queryByRole('listbox')).toBeNull())
|
|
426
|
+
await expect(next).toHaveFocus()
|
|
427
|
+
},
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Dimmed, and it does not open. */
|
|
431
|
+
export const Disabled: Story = {
|
|
432
|
+
args: { ariaLabel: 'Lettertype', disabled: true },
|
|
433
|
+
render: renderPicker('Georgia'),
|
|
434
|
+
play: async ({ canvasElement }) => {
|
|
435
|
+
const trigger = triggerOf(canvasElement)
|
|
436
|
+
|
|
437
|
+
await expect(trigger).toBeDisabled()
|
|
438
|
+
await userEvent.click(trigger, { pointerEventsCheck: 0 })
|
|
439
|
+
await expect(within(canvasElement).queryByRole('listbox')).toBeNull()
|
|
440
|
+
},
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* The app's flow: the catalogue is fetched on the first open, which takes a moment; after that it
|
|
445
|
+
* is cached and the panel opens with it in place.
|
|
446
|
+
*/
|
|
447
|
+
export const LazyCatalogue: Story = {
|
|
448
|
+
args: {
|
|
449
|
+
ariaLabel: 'Lettertype',
|
|
450
|
+
inherit: { label: 'Huisstijl', family: 'Georgia' },
|
|
451
|
+
},
|
|
452
|
+
render: (args) => ({
|
|
453
|
+
components: { FontPicker },
|
|
454
|
+
setup() {
|
|
455
|
+
const font = ref<string | null>(null)
|
|
456
|
+
const status = ref<'loading' | 'ready' | 'error'>('loading')
|
|
457
|
+
const fonts = ref<NexxtFontPickerFont[]>(WEB_SAFE)
|
|
458
|
+
let fetched = false
|
|
459
|
+
const onOpen = () => {
|
|
460
|
+
args.onOpen?.()
|
|
461
|
+
if (fetched) return
|
|
462
|
+
fetched = true
|
|
463
|
+
setTimeout(() => {
|
|
464
|
+
fonts.value = FONTS
|
|
465
|
+
status.value = 'ready'
|
|
466
|
+
}, 1200)
|
|
467
|
+
}
|
|
468
|
+
return { args, font, status, fonts, onOpen }
|
|
469
|
+
},
|
|
470
|
+
template: `
|
|
471
|
+
<div class="w-78">
|
|
472
|
+
<FontPicker v-bind="args" v-model="font" :fonts="fonts" :catalogue-status="status" @open="onOpen" />
|
|
473
|
+
</div>
|
|
474
|
+
`,
|
|
475
|
+
}),
|
|
476
|
+
}
|