srcdev-nuxt-components 9.5.7 → 9.6.1
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/.claude/skills/component-compliance-checklist.md +8 -2
- package/.claude/skills/component-dynamic-slots.md +3 -3
- package/.claude/skills/component-export-types.md +10 -16
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/alert-content.md +9 -1
- package/.claude/skills/components/breadcrumb.md +5 -0
- package/.claude/skills/components/container-glow.md +21 -8
- package/.claude/skills/components/display-avatar.md +13 -7
- package/.claude/skills/components/display-chip.md +37 -24
- package/.claude/skills/components/display-pill.md +58 -8
- package/.claude/skills/components/display-prompt.md +8 -5
- package/.claude/skills/components/display-toast.md +21 -4
- package/.claude/skills/components/glowing-border.md +20 -2
- package/.claude/skills/components/input-button.md +3 -0
- package/.claude/skills/components/responsive-header.md +59 -0
- package/.claude/skills/components/select-menu.md +54 -5
- package/.claude/skills/components/service-detail.md +32 -0
- package/.claude/skills/components/service-summary.md +8 -0
- package/.claude/skills/components/services-card-grid.md +5 -3
- package/.claude/skills/components/services-card.md +22 -12
- package/.claude/skills/components/site-header.md +2 -2
- package/.claude/skills/components/slider-gallery.md +10 -4
- package/.claude/skills/components/tabbed-content.md +300 -0
- package/.claude/skills/composable-anchor-scroll.md +16 -15
- package/.claude/skills/index.md +4 -5
- package/.claude/skills/storybook-add-story.md +53 -0
- package/.claude/skills/theming-status-tokens.md +60 -0
- package/.vscode/srcdev-component-display-avatar.code-snippets +2 -1
- package/.vscode/srcdev-component-display-chip.code-snippets +3 -2
- package/.vscode/srcdev-component-display-pill.code-snippets +10 -0
- package/.vscode/srcdev-component-responsive-header.code-snippets +16 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +28 -0
- package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
- package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
- package/.vscode/srcdev-component-services-card.code-snippets +38 -36
- package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
- package/.vscode/{srcdev-component-tabs-core.code-snippets → srcdev-component-tabbed-content.code-snippets} +35 -16
- package/app/assets/styles/setup/03.theming/_status.css +24 -0
- package/app/assets/styles/setup/03.theming/index.css +1 -0
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +39 -35
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +25 -15
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +130 -24
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +45 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +2 -2
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +76 -19
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +12 -10
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +201 -42
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +15 -4
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +1 -1
- package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +78 -9
- package/app/components/01.atoms/display-pill/DisplayPill.vue +59 -23
- package/app/components/01.atoms/display-pill/stories/DisplayPill.stories.ts +240 -16
- package/app/components/01.atoms/display-pill/tests/DisplayPill.spec.ts +43 -14
- package/app/components/01.atoms/display-pill/tests/__snapshots__/DisplayPill.spec.ts.snap +3 -3
- package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
- package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
- package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
- package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
- package/app/components/01.atoms/tabbed-content/CONSUMER-STYLING.md +201 -0
- package/app/components/01.atoms/tabbed-content/TabbedContent.vue +750 -0
- package/app/components/01.atoms/tabbed-content/stories/TabbedContent.stories.ts +279 -0
- package/app/components/01.atoms/tabbed-content/tests/TabbedContent.spec.ts +816 -0
- package/app/components/02.molecules/alert-content/AlertContent.vue +11 -2
- package/app/components/02.molecules/alert-content/AlertContentInner.vue +19 -0
- package/app/components/02.molecules/alert-content/CONSUMER-STYLING.md +17 -4
- package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +200 -17
- package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +33 -0
- package/app/components/02.molecules/display-avatar/CONSUMER-STYLING.md +7 -5
- package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +6 -2
- package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +24 -3
- package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +19 -0
- package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +2 -1
- package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +52 -19
- package/app/components/02.molecules/display-chip/DisplayChip.vue +72 -54
- package/app/components/02.molecules/display-chip/stories/DisplayChip.stories.ts +103 -6
- package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +98 -29
- package/app/components/02.molecules/display-chip/tests/__snapshots__/DisplayChip.spec.ts.snap +8 -4
- package/app/components/02.molecules/prompt/CONSUMER-STYLING.md +9 -1
- package/app/components/02.molecules/prompt/DisplayPrompt.vue +2 -2
- package/app/components/02.molecules/prompt/stories/DisplayPrompt.stories.ts +208 -71
- package/app/components/02.molecules/prompt/tests/DisplayPrompt.spec.ts +19 -6
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +66 -8
- package/app/components/02.molecules/select-menu/SelectMenu.vue +141 -18
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +232 -2
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +151 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +6 -2
- package/app/components/02.molecules/toast/CONSUMER-STYLING.md +91 -27
- package/app/components/02.molecules/toast/DisplayToast.vue +109 -50
- package/app/components/02.molecules/toast/DisplayToastProvider.vue +109 -45
- package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +23 -0
- package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +73 -0
- package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +132 -18
- package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +105 -39
- package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
- package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
- package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +42 -11
- package/app/components/03.organisms/responsive-header/NavigationItems.vue +60 -16
- package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +86 -24
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +52 -0
- package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +20 -0
- package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +40 -0
- package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
- package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
- package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +5 -4
- package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
- package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
- package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
- package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
- package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
- package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
- package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
- package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
- package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
- package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
- package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
- package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
- package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +6 -6
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/input-button/InputButton.vue +3 -1
- package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
- package/app/composables/useAnchorScroll.ts +1 -1
- package/app/composables/useTabs.ts +148 -206
- package/app/types/components/display-chip.d.ts +8 -4
- package/app/types/components/display-toast.d.ts +2 -0
- package/app/types/components/index.ts +0 -1
- package/app/types/components/select-menu.d.ts +2 -0
- package/app/utils/pausableTimeout.ts +41 -0
- package/nuxt.config.ts +1 -1
- package/package.json +1 -1
- package/.claude/skills/components/site-navigation.md +0 -145
- package/.claude/skills/components/tab-navigation.md +0 -272
- package/.claude/skills/components/tabs-core.md +0 -187
- package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
- package/.vscode/srcdev-component-tab-navigation.code-snippets +0 -30
- package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +0 -120
- package/app/components/01.atoms/navigation/tabs/TabsCore.vue +0 -272
- package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +0 -199
- package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +0 -274
- package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
- package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
- package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
- package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
- package/app/components/02.molecules/navigation/site-navigation/tests/__snapshots__/SiteNavigation.spec.ts.snap +0 -30
- package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +0 -150
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +0 -518
- package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +0 -340
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +0 -339
- package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +0 -33
- package/app/composables/useNavCollapse.ts +0 -86
- package/app/types/components/nav-item.d.ts +0 -11
|
@@ -4,13 +4,21 @@
|
|
|
4
4
|
v-if="privateDisplayToast"
|
|
5
5
|
ref="toastElementRef"
|
|
6
6
|
class="display-toast"
|
|
7
|
-
:class="
|
|
7
|
+
:class="elementClasses"
|
|
8
|
+
:data-state="toastState"
|
|
9
|
+
:data-position="position"
|
|
10
|
+
:data-alignment="fullWidth ? 'full-width' : alignment"
|
|
11
|
+
:data-paused="isPaused ? '' : undefined"
|
|
8
12
|
:data-theme="theme"
|
|
9
13
|
:role="toastRole"
|
|
10
14
|
:aria-live="ariaLive"
|
|
11
15
|
:tabindex="slots.default ? undefined : '0'"
|
|
12
16
|
:aria-describedby="slots.default ? undefined : 'toast-message-' + toastId"
|
|
13
17
|
@keydown.escape="setDismissToast"
|
|
18
|
+
@pointerenter="isHovered = true"
|
|
19
|
+
@pointerleave="isHovered = false"
|
|
20
|
+
@focusin="handleFocusIn"
|
|
21
|
+
@focusout="handleFocusOut"
|
|
14
22
|
>
|
|
15
23
|
<slot v-if="slots.default"></slot>
|
|
16
24
|
|
|
@@ -32,6 +40,7 @@
|
|
|
32
40
|
<template v-if="slots.description || toastDescription" #content>
|
|
33
41
|
<slot name="description">{{ toastDescription }}</slot>
|
|
34
42
|
</template>
|
|
43
|
+
<template v-if="dismissLabel" #dismissLabel>{{ dismissLabel }}</template>
|
|
35
44
|
</component>
|
|
36
45
|
<div v-if="autoDismiss" class="display-toast-progress"></div>
|
|
37
46
|
</div>
|
|
@@ -75,18 +84,7 @@ const toastDisplayText = computed(() => props.config?.content?.text ?? "");
|
|
|
75
84
|
const toastTitle = computed(() => props.config?.content?.title ?? "");
|
|
76
85
|
const toastDescription = computed(() => props.config?.content?.description ?? "");
|
|
77
86
|
const customIcon = computed(() => props.config?.content?.customIcon);
|
|
78
|
-
|
|
79
|
-
// Computed classes for positioning
|
|
80
|
-
const positionClasses = computed(() => {
|
|
81
|
-
const classes = [];
|
|
82
|
-
classes.push(position.value);
|
|
83
|
-
if (fullWidth.value) {
|
|
84
|
-
classes.push("full-width");
|
|
85
|
-
} else {
|
|
86
|
-
classes.push(alignment.value);
|
|
87
|
-
}
|
|
88
|
-
return classes;
|
|
89
|
-
});
|
|
87
|
+
const dismissLabel = computed(() => props.config?.content?.dismissLabel);
|
|
90
88
|
|
|
91
89
|
/*
|
|
92
90
|
* Accessibility setup
|
|
@@ -109,9 +107,30 @@ const ariaLive = computed(() => {
|
|
|
109
107
|
const externalTriggerModel = defineModel<boolean>({ default: false });
|
|
110
108
|
const privateDisplayToast = ref(false);
|
|
111
109
|
const transitionalState = ref(false);
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
const toastState = computed(() => (transitionalState.value ? "show" : "hide"));
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* Auto-dismiss pauses while hovered or keyboard-focused (WCAG 2.2.2)
|
|
114
|
+
*/
|
|
115
|
+
const isHovered = ref(false);
|
|
116
|
+
const isFocusPaused = ref(false);
|
|
117
|
+
const isPaused = computed(() => autoDismiss.value && (isHovered.value || isFocusPaused.value));
|
|
118
|
+
let dismissTimer: ReturnType<typeof createPausableTimeout> | null = null;
|
|
119
|
+
|
|
120
|
+
const handleFocusIn = (event: FocusEvent) => {
|
|
121
|
+
if (toastElementRef.value) isFocusPaused.value = focusPausesAutoDismiss(toastElementRef.value, event.target);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleFocusOut = (event: FocusEvent) => {
|
|
125
|
+
if (!toastElementRef.value?.contains(event.relatedTarget as Node | null)) isFocusPaused.value = false;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
watch(isPaused, (paused) => (paused ? dismissTimer?.pause() : dismissTimer?.resume()));
|
|
129
|
+
|
|
130
|
+
const clearDismissTimer = () => {
|
|
131
|
+
dismissTimer?.clear();
|
|
132
|
+
dismissTimer = null;
|
|
133
|
+
};
|
|
115
134
|
|
|
116
135
|
/*
|
|
117
136
|
* Computed properties for durations (in ms for CSS)
|
|
@@ -123,6 +142,7 @@ const displayDurationMs = computed(() => duration.value + "ms");
|
|
|
123
142
|
* Lifecycle hooks
|
|
124
143
|
*/
|
|
125
144
|
const setDismissToast = async () => {
|
|
145
|
+
clearDismissTimer();
|
|
126
146
|
transitionalState.value = false;
|
|
127
147
|
await useSleep(revealDuration.value);
|
|
128
148
|
|
|
@@ -144,6 +164,8 @@ const setDismissToast = async () => {
|
|
|
144
164
|
|
|
145
165
|
externalTriggerModel.value = false;
|
|
146
166
|
privateDisplayToast.value = false;
|
|
167
|
+
isHovered.value = false;
|
|
168
|
+
isFocusPaused.value = false;
|
|
147
169
|
};
|
|
148
170
|
|
|
149
171
|
watch(
|
|
@@ -170,8 +192,9 @@ watch(
|
|
|
170
192
|
}
|
|
171
193
|
|
|
172
194
|
if (autoDismiss.value) {
|
|
173
|
-
|
|
174
|
-
setDismissToast
|
|
195
|
+
clearDismissTimer();
|
|
196
|
+
dismissTimer = createPausableTimeout(setDismissToast, duration.value);
|
|
197
|
+
if (isPaused.value) dismissTimer.pause();
|
|
175
198
|
}
|
|
176
199
|
} else if (!newValue && previousValue) {
|
|
177
200
|
// If external model is set to false, dismiss the toast
|
|
@@ -183,18 +206,20 @@ watch(
|
|
|
183
206
|
onBeforeRouteLeave(() => {
|
|
184
207
|
setDismissToast();
|
|
185
208
|
});
|
|
209
|
+
|
|
210
|
+
onBeforeUnmount(clearDismissTimer);
|
|
186
211
|
</script>
|
|
187
212
|
|
|
188
213
|
<style lang="css">
|
|
189
214
|
@layer components {
|
|
190
|
-
@keyframes show {
|
|
215
|
+
@keyframes display-toast-show {
|
|
191
216
|
to {
|
|
192
217
|
opacity: 1;
|
|
193
218
|
transform: translateY(0);
|
|
194
219
|
}
|
|
195
220
|
}
|
|
196
221
|
|
|
197
|
-
@keyframes
|
|
222
|
+
@keyframes display-toast-hide-top {
|
|
198
223
|
0% {
|
|
199
224
|
opacity: 1;
|
|
200
225
|
transform: translateY(0);
|
|
@@ -205,7 +230,7 @@ onBeforeRouteLeave(() => {
|
|
|
205
230
|
}
|
|
206
231
|
}
|
|
207
232
|
|
|
208
|
-
@keyframes
|
|
233
|
+
@keyframes display-toast-hide-bottom {
|
|
209
234
|
0% {
|
|
210
235
|
opacity: 1;
|
|
211
236
|
transform: translateY(0);
|
|
@@ -216,7 +241,22 @@ onBeforeRouteLeave(() => {
|
|
|
216
241
|
}
|
|
217
242
|
}
|
|
218
243
|
|
|
219
|
-
@keyframes
|
|
244
|
+
@keyframes display-toast-fade-in {
|
|
245
|
+
to {
|
|
246
|
+
opacity: 1;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@keyframes display-toast-fade-out {
|
|
251
|
+
from {
|
|
252
|
+
opacity: 1;
|
|
253
|
+
}
|
|
254
|
+
to {
|
|
255
|
+
opacity: 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@keyframes display-toast-progress {
|
|
220
260
|
to {
|
|
221
261
|
transform: scaleX(1);
|
|
222
262
|
}
|
|
@@ -228,19 +268,18 @@ onBeforeRouteLeave(() => {
|
|
|
228
268
|
--_toast-gutter: 24px;
|
|
229
269
|
}
|
|
230
270
|
|
|
231
|
-
/* See the matching comment in DisplayToastProvider.vue — same fix, same rationale. */
|
|
232
|
-
|
|
233
271
|
display: block;
|
|
234
272
|
overflow: hidden;
|
|
273
|
+
overflow-wrap: anywhere;
|
|
235
274
|
position: fixed;
|
|
236
275
|
margin: 0;
|
|
237
276
|
opacity: 0;
|
|
277
|
+
max-inline-size: var(--display-toast-max-width, min(48rem, 100% - 2 * var(--_toast-gutter)));
|
|
238
278
|
|
|
239
279
|
z-index: var(--display-toast-z-index, 999999);
|
|
240
280
|
|
|
241
|
-
/* Focus styles for accessibility */
|
|
242
281
|
&:focus {
|
|
243
|
-
outline: 2px solid var(--theme-ring);
|
|
282
|
+
outline: 2px solid var(--display-toast-focus-ring-colour, var(--theme-ring));
|
|
244
283
|
outline-offset: 2px;
|
|
245
284
|
}
|
|
246
285
|
|
|
@@ -248,71 +287,91 @@ onBeforeRouteLeave(() => {
|
|
|
248
287
|
outline: none;
|
|
249
288
|
}
|
|
250
289
|
|
|
251
|
-
|
|
290
|
+
&[data-state="show"] {
|
|
252
291
|
@supports (animation-timing-function: linear(0, 1)) {
|
|
253
|
-
animation: show v-bind(revealDurationMs) var(--spring-easing) forwards;
|
|
292
|
+
animation: display-toast-show v-bind(revealDurationMs) var(--spring-easing) forwards;
|
|
254
293
|
}
|
|
255
294
|
|
|
256
295
|
@supports not (animation-timing-function: linear(0, 1)) {
|
|
257
|
-
animation: show calc(v-bind(revealDurationMs) / 2) linear forwards;
|
|
296
|
+
animation: display-toast-show calc(v-bind(revealDurationMs) / 2) linear forwards;
|
|
258
297
|
}
|
|
259
298
|
}
|
|
260
299
|
|
|
261
|
-
|
|
300
|
+
&[data-state="hide"] {
|
|
262
301
|
@supports (animation-timing-function: linear(0, 1)) {
|
|
263
|
-
animation:
|
|
302
|
+
animation: display-toast-hide-top v-bind(revealDurationMs) var(--spring-easing) forwards;
|
|
264
303
|
}
|
|
265
304
|
|
|
266
305
|
@supports not (animation-timing-function: linear(0, 1)) {
|
|
267
|
-
animation:
|
|
306
|
+
animation: display-toast-hide-top calc(v-bind(revealDurationMs) / 2) linear forwards;
|
|
268
307
|
}
|
|
269
308
|
|
|
270
|
-
|
|
309
|
+
&[data-position="bottom"] {
|
|
271
310
|
@supports (animation-timing-function: linear(0, 1)) {
|
|
272
|
-
animation:
|
|
311
|
+
animation: display-toast-hide-bottom v-bind(revealDurationMs) var(--spring-easing) forwards;
|
|
273
312
|
}
|
|
274
313
|
|
|
275
314
|
@supports not (animation-timing-function: linear(0, 1)) {
|
|
276
|
-
animation:
|
|
315
|
+
animation: display-toast-hide-bottom calc(v-bind(revealDurationMs) / 2) linear forwards;
|
|
277
316
|
}
|
|
278
317
|
}
|
|
279
318
|
}
|
|
280
319
|
|
|
281
|
-
/*
|
|
282
|
-
* Default is centre for smaller screens
|
|
283
|
-
*/
|
|
320
|
+
/* Centred on small screens */
|
|
284
321
|
inset-inline: var(--_toast-gutter);
|
|
285
322
|
margin-inline: auto;
|
|
286
323
|
|
|
287
324
|
@media (width >= 600px) {
|
|
288
|
-
|
|
325
|
+
&[data-alignment="left"] {
|
|
289
326
|
inset-inline-start: var(--_toast-gutter);
|
|
290
327
|
inset-inline-end: unset;
|
|
291
328
|
}
|
|
292
329
|
|
|
293
|
-
|
|
330
|
+
&[data-alignment="right"] {
|
|
294
331
|
inset-inline-end: var(--_toast-gutter);
|
|
295
332
|
inset-inline-start: unset;
|
|
296
333
|
}
|
|
297
334
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
width: max-content;
|
|
303
|
-
}
|
|
335
|
+
&[data-alignment="center"] {
|
|
336
|
+
inset-inline: 0;
|
|
337
|
+
margin-inline: auto;
|
|
338
|
+
width: fit-content;
|
|
304
339
|
}
|
|
305
340
|
}
|
|
306
341
|
|
|
307
|
-
|
|
342
|
+
&[data-alignment="full-width"] {
|
|
343
|
+
max-inline-size: none;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
&[data-position="top"] {
|
|
308
347
|
inset-block-start: var(--_toast-gutter);
|
|
309
348
|
transform: translateY(-30px);
|
|
310
349
|
}
|
|
311
|
-
|
|
350
|
+
|
|
351
|
+
&[data-position="bottom"] {
|
|
312
352
|
inset-block-end: var(--_toast-gutter);
|
|
313
353
|
transform: translateY(30px);
|
|
314
354
|
}
|
|
315
355
|
|
|
356
|
+
/* After the position and state rules so it wins at equal specificity. */
|
|
357
|
+
@media (prefers-reduced-motion: reduce) {
|
|
358
|
+
&[data-position] {
|
|
359
|
+
transform: none;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
&[data-state="show"][data-position] {
|
|
363
|
+
animation: display-toast-fade-in v-bind(revealDurationMs) linear forwards;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
&[data-state="hide"][data-position] {
|
|
367
|
+
animation: display-toast-fade-out v-bind(revealDurationMs) linear forwards;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
&[data-paused] .display-toast-progress {
|
|
372
|
+
animation-play-state: paused;
|
|
373
|
+
}
|
|
374
|
+
|
|
316
375
|
.display-toast-progress {
|
|
317
376
|
position: absolute;
|
|
318
377
|
inset-block-end: 4px;
|
|
@@ -320,9 +379,9 @@ onBeforeRouteLeave(() => {
|
|
|
320
379
|
height: 3px;
|
|
321
380
|
transform: scaleX(0);
|
|
322
381
|
transform-origin: right;
|
|
323
|
-
background: var(--theme-accent);
|
|
382
|
+
background: var(--display-toast-progress-colour, var(--theme-accent));
|
|
324
383
|
border-radius: inherit;
|
|
325
|
-
animation: progress v-bind(displayDurationMs) linear forwards;
|
|
384
|
+
animation: display-toast-progress v-bind(displayDurationMs) linear forwards;
|
|
326
385
|
}
|
|
327
386
|
}
|
|
328
387
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
<TransitionGroup
|
|
4
4
|
tag="div"
|
|
5
5
|
class="display-toast-provider"
|
|
6
|
-
:
|
|
6
|
+
:data-position="position"
|
|
7
|
+
:data-alignment="fullWidth ? 'full-width' : alignment"
|
|
7
8
|
@before-leave="onBeforeLeave"
|
|
8
9
|
@after-leave="onAfterLeave"
|
|
9
10
|
@after-enter="onAfterEnter"
|
|
@@ -21,7 +22,12 @@
|
|
|
21
22
|
:aria-live="ariaLiveFor(entry)"
|
|
22
23
|
tabindex="0"
|
|
23
24
|
:aria-describedby="'toast-message-' + entry.id"
|
|
25
|
+
:data-paused="isPaused(entry) ? '' : undefined"
|
|
24
26
|
@keydown.escape="handleDismiss(entry.id)"
|
|
27
|
+
@pointerenter="setPauseReason(entry.id, 'hover', true)"
|
|
28
|
+
@pointerleave="setPauseReason(entry.id, 'hover', false)"
|
|
29
|
+
@focusin="handleFocusIn(entry.id, $event)"
|
|
30
|
+
@focusout="handleFocusOut(entry.id, $event)"
|
|
25
31
|
>
|
|
26
32
|
<component
|
|
27
33
|
:is="maskedFor(entry) ? AlertMaskedContent : AlertContent"
|
|
@@ -37,6 +43,9 @@
|
|
|
37
43
|
<template v-if="entry.config.content?.description" #content>
|
|
38
44
|
{{ entry.config.content?.description }}
|
|
39
45
|
</template>
|
|
46
|
+
<template v-if="entry.config.content?.dismissLabel" #dismissLabel>
|
|
47
|
+
{{ entry.config.content?.dismissLabel }}
|
|
48
|
+
</template>
|
|
40
49
|
</component>
|
|
41
50
|
<div v-if="autoDismissFor(entry)" class="display-toast-provider-progress"></div>
|
|
42
51
|
</div>
|
|
@@ -83,21 +92,50 @@ const roleFor = (entry: ToastQueueEntry) => (["error", "warning"].includes(theme
|
|
|
83
92
|
const ariaLiveFor = (entry: ToastQueueEntry) =>
|
|
84
93
|
["error", "warning"].includes(themeFor(entry)) ? "assertive" : "polite";
|
|
85
94
|
|
|
86
|
-
const timers = new Map<string, ReturnType<typeof
|
|
95
|
+
const timers = new Map<string, ReturnType<typeof createPausableTimeout>>();
|
|
87
96
|
|
|
88
|
-
|
|
97
|
+
// Auto-dismiss pauses while hovered or keyboard-focused (WCAG 2.2.2).
|
|
98
|
+
const pauseReasons = ref<Record<string, { hover: boolean; focus: boolean }>>({});
|
|
99
|
+
|
|
100
|
+
const isPaused = (entry: ToastQueueEntry) => {
|
|
101
|
+
const reasons = pauseReasons.value[entry.id];
|
|
102
|
+
return autoDismissFor(entry) && !!reasons && (reasons.hover || reasons.focus);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const setPauseReason = (id: string, reason: "hover" | "focus", active: boolean) => {
|
|
106
|
+
const reasons = { hover: false, focus: false, ...pauseReasons.value[id], [reason]: active };
|
|
107
|
+
pauseReasons.value = { ...pauseReasons.value, [id]: reasons };
|
|
89
108
|
const timer = timers.get(id);
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
if (reasons.hover || reasons.focus) timer?.pause();
|
|
110
|
+
else timer?.resume();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleFocusIn = (id: string, event: FocusEvent) => {
|
|
114
|
+
setPauseReason(id, "focus", focusPausesAutoDismiss(event.currentTarget as HTMLElement, event.target));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleFocusOut = (id: string, event: FocusEvent) => {
|
|
118
|
+
if (!(event.currentTarget as HTMLElement).contains(event.relatedTarget as Node | null)) {
|
|
119
|
+
setPauseReason(id, "focus", false);
|
|
93
120
|
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const forgetEntry = (id: string) => {
|
|
124
|
+
timers.get(id)?.clear();
|
|
125
|
+
timers.delete(id);
|
|
126
|
+
const { [id]: _removed, ...rest } = pauseReasons.value;
|
|
127
|
+
pauseReasons.value = rest;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleDismiss = (id: string) => {
|
|
131
|
+
forgetEntry(id);
|
|
94
132
|
dismiss(id);
|
|
95
133
|
};
|
|
96
134
|
|
|
97
135
|
const startTimer = (entry: ToastQueueEntry) => {
|
|
98
136
|
if (!autoDismissFor(entry)) return;
|
|
99
|
-
const timer =
|
|
100
|
-
|
|
137
|
+
const timer = createPausableTimeout(() => {
|
|
138
|
+
forgetEntry(entry.id);
|
|
101
139
|
dismiss(entry.id);
|
|
102
140
|
}, displayDurationFor(entry));
|
|
103
141
|
timers.set(entry.id, timer);
|
|
@@ -110,11 +148,8 @@ watch(
|
|
|
110
148
|
|
|
111
149
|
// Clear timers for entries that no longer exist (e.g. clear()).
|
|
112
150
|
const entryIds = new Set(entries.map((e) => e.id));
|
|
113
|
-
for (const
|
|
114
|
-
if (!entryIds.has(id))
|
|
115
|
-
clearTimeout(timer);
|
|
116
|
-
timers.delete(id);
|
|
117
|
-
}
|
|
151
|
+
for (const id of timers.keys()) {
|
|
152
|
+
if (!entryIds.has(id)) forgetEntry(id);
|
|
118
153
|
}
|
|
119
154
|
|
|
120
155
|
const visibleCount = entries.filter((e) => e.status === "visible").length;
|
|
@@ -157,14 +192,14 @@ const onAfterEnter = (el: Element) => {
|
|
|
157
192
|
};
|
|
158
193
|
|
|
159
194
|
onUnmounted(() => {
|
|
160
|
-
timers.forEach(
|
|
195
|
+
timers.forEach((timer) => timer.clear());
|
|
161
196
|
timers.clear();
|
|
162
197
|
});
|
|
163
198
|
</script>
|
|
164
199
|
|
|
165
200
|
<style lang="css">
|
|
166
201
|
@layer components {
|
|
167
|
-
@keyframes
|
|
202
|
+
@keyframes display-toast-provider-hide-top {
|
|
168
203
|
0% {
|
|
169
204
|
opacity: 1;
|
|
170
205
|
transform: translateY(0);
|
|
@@ -175,7 +210,7 @@ onUnmounted(() => {
|
|
|
175
210
|
}
|
|
176
211
|
}
|
|
177
212
|
|
|
178
|
-
@keyframes
|
|
213
|
+
@keyframes display-toast-provider-hide-bottom {
|
|
179
214
|
0% {
|
|
180
215
|
opacity: 1;
|
|
181
216
|
transform: translateY(0);
|
|
@@ -186,13 +221,13 @@ onUnmounted(() => {
|
|
|
186
221
|
}
|
|
187
222
|
}
|
|
188
223
|
|
|
189
|
-
@keyframes progress {
|
|
224
|
+
@keyframes display-toast-provider-progress {
|
|
190
225
|
to {
|
|
191
226
|
transform: scaleX(1);
|
|
192
227
|
}
|
|
193
228
|
}
|
|
194
229
|
|
|
195
|
-
@keyframes
|
|
230
|
+
@keyframes display-toast-provider-show-top {
|
|
196
231
|
from {
|
|
197
232
|
opacity: 0;
|
|
198
233
|
transform: translateY(-30px);
|
|
@@ -203,7 +238,16 @@ onUnmounted(() => {
|
|
|
203
238
|
}
|
|
204
239
|
}
|
|
205
240
|
|
|
206
|
-
@keyframes
|
|
241
|
+
@keyframes display-toast-provider-fade {
|
|
242
|
+
from {
|
|
243
|
+
opacity: 0;
|
|
244
|
+
}
|
|
245
|
+
to {
|
|
246
|
+
opacity: 1;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@keyframes display-toast-provider-show-bottom {
|
|
207
251
|
from {
|
|
208
252
|
opacity: 0;
|
|
209
253
|
transform: translateY(30px);
|
|
@@ -220,12 +264,6 @@ onUnmounted(() => {
|
|
|
220
264
|
--_gutter: 24px;
|
|
221
265
|
}
|
|
222
266
|
|
|
223
|
-
/* No public token before 2026-08-22 and hardcoded far below DisplayDialog's own
|
|
224
|
-
--display-dialog-z-index default (999999) — a toast is meant to float above ordinary page
|
|
225
|
-
chrome, but 100 loses to any consumer's sticky header. Matches DisplayDialog's default so
|
|
226
|
-
toasts also clear a modal's own overlay; the two ties on DOM/paint order if both happen to
|
|
227
|
-
be visible at once, which is an acceptable edge case for a transient notification. */
|
|
228
|
-
|
|
229
267
|
position: fixed;
|
|
230
268
|
display: flex;
|
|
231
269
|
flex-direction: column;
|
|
@@ -235,36 +273,36 @@ onUnmounted(() => {
|
|
|
235
273
|
inset-inline: var(--_gutter);
|
|
236
274
|
margin-inline: auto;
|
|
237
275
|
width: max-content;
|
|
238
|
-
max-width:
|
|
276
|
+
max-width: var(--display-toast-provider-max-width, min(48rem, 100% - 2 * var(--_gutter)));
|
|
239
277
|
|
|
240
|
-
|
|
278
|
+
&[data-position="top"] {
|
|
241
279
|
inset-block-start: var(--_gutter);
|
|
242
280
|
}
|
|
243
281
|
|
|
244
|
-
|
|
282
|
+
&[data-position="bottom"] {
|
|
245
283
|
inset-block-end: var(--_gutter);
|
|
246
284
|
flex-direction: column-reverse;
|
|
247
285
|
}
|
|
248
286
|
|
|
249
287
|
@media (width >= 600px) {
|
|
250
|
-
|
|
288
|
+
&[data-alignment="left"] {
|
|
251
289
|
inset-inline-start: var(--_gutter);
|
|
252
290
|
inset-inline-end: unset;
|
|
253
291
|
margin-inline: 0;
|
|
254
292
|
}
|
|
255
293
|
|
|
256
|
-
|
|
294
|
+
&[data-alignment="right"] {
|
|
257
295
|
inset-inline-end: var(--_gutter);
|
|
258
296
|
inset-inline-start: unset;
|
|
259
297
|
margin-inline: 0;
|
|
260
298
|
}
|
|
261
299
|
|
|
262
|
-
|
|
300
|
+
&[data-alignment="center"] {
|
|
263
301
|
inset-inline: 0;
|
|
264
302
|
margin-inline: auto;
|
|
265
303
|
}
|
|
266
304
|
|
|
267
|
-
|
|
305
|
+
&[data-alignment="full-width"] {
|
|
268
306
|
inset-inline: var(--_gutter);
|
|
269
307
|
width: unset;
|
|
270
308
|
max-width: unset;
|
|
@@ -279,9 +317,10 @@ onUnmounted(() => {
|
|
|
279
317
|
display: block;
|
|
280
318
|
position: relative;
|
|
281
319
|
overflow: hidden;
|
|
320
|
+
overflow-wrap: anywhere;
|
|
282
321
|
|
|
283
322
|
&:focus {
|
|
284
|
-
outline: 2px solid var(--theme-ring);
|
|
323
|
+
outline: 2px solid var(--display-toast-provider-focus-ring-colour, var(--theme-ring));
|
|
285
324
|
outline-offset: 2px;
|
|
286
325
|
}
|
|
287
326
|
|
|
@@ -290,7 +329,7 @@ onUnmounted(() => {
|
|
|
290
329
|
}
|
|
291
330
|
|
|
292
331
|
/* TransitionGroup enter/leave */
|
|
293
|
-
.display-toast-provider
|
|
332
|
+
.display-toast-provider[data-position="top"] & {
|
|
294
333
|
&.v-enter-from {
|
|
295
334
|
opacity: 0;
|
|
296
335
|
transform: translateY(-30px);
|
|
@@ -298,11 +337,11 @@ onUnmounted(() => {
|
|
|
298
337
|
|
|
299
338
|
&.v-enter-active {
|
|
300
339
|
@supports (animation-timing-function: linear(0, 1)) {
|
|
301
|
-
animation:
|
|
340
|
+
animation: display-toast-provider-show-top var(--_reveal) var(--spring-easing) forwards;
|
|
302
341
|
}
|
|
303
342
|
|
|
304
343
|
@supports not (animation-timing-function: linear(0, 1)) {
|
|
305
|
-
animation:
|
|
344
|
+
animation: display-toast-provider-show-top calc(var(--_reveal) / 2) linear forwards;
|
|
306
345
|
}
|
|
307
346
|
}
|
|
308
347
|
|
|
@@ -310,16 +349,16 @@ onUnmounted(() => {
|
|
|
310
349
|
position: absolute;
|
|
311
350
|
|
|
312
351
|
@supports (animation-timing-function: linear(0, 1)) {
|
|
313
|
-
animation:
|
|
352
|
+
animation: display-toast-provider-hide-top var(--_reveal) var(--spring-easing) forwards;
|
|
314
353
|
}
|
|
315
354
|
|
|
316
355
|
@supports not (animation-timing-function: linear(0, 1)) {
|
|
317
|
-
animation:
|
|
356
|
+
animation: display-toast-provider-hide-top calc(var(--_reveal) / 2) linear forwards;
|
|
318
357
|
}
|
|
319
358
|
}
|
|
320
359
|
}
|
|
321
360
|
|
|
322
|
-
.display-toast-provider
|
|
361
|
+
.display-toast-provider[data-position="bottom"] & {
|
|
323
362
|
&.v-enter-from {
|
|
324
363
|
opacity: 0;
|
|
325
364
|
transform: translateY(30px);
|
|
@@ -327,11 +366,11 @@ onUnmounted(() => {
|
|
|
327
366
|
|
|
328
367
|
&.v-enter-active {
|
|
329
368
|
@supports (animation-timing-function: linear(0, 1)) {
|
|
330
|
-
animation:
|
|
369
|
+
animation: display-toast-provider-show-bottom var(--_reveal) var(--spring-easing) forwards;
|
|
331
370
|
}
|
|
332
371
|
|
|
333
372
|
@supports not (animation-timing-function: linear(0, 1)) {
|
|
334
|
-
animation:
|
|
373
|
+
animation: display-toast-provider-show-bottom calc(var(--_reveal) / 2) linear forwards;
|
|
335
374
|
}
|
|
336
375
|
}
|
|
337
376
|
|
|
@@ -339,11 +378,11 @@ onUnmounted(() => {
|
|
|
339
378
|
position: absolute;
|
|
340
379
|
|
|
341
380
|
@supports (animation-timing-function: linear(0, 1)) {
|
|
342
|
-
animation:
|
|
381
|
+
animation: display-toast-provider-hide-bottom var(--_reveal) var(--spring-easing) forwards;
|
|
343
382
|
}
|
|
344
383
|
|
|
345
384
|
@supports not (animation-timing-function: linear(0, 1)) {
|
|
346
|
-
animation:
|
|
385
|
+
animation: display-toast-provider-hide-bottom calc(var(--_reveal) / 2) linear forwards;
|
|
347
386
|
}
|
|
348
387
|
}
|
|
349
388
|
}
|
|
@@ -352,6 +391,31 @@ onUnmounted(() => {
|
|
|
352
391
|
transition: transform 0.3s ease;
|
|
353
392
|
}
|
|
354
393
|
|
|
394
|
+
/* Same selector shape as the position rules above, later in source, so it wins. */
|
|
395
|
+
@media (prefers-reduced-motion: reduce) {
|
|
396
|
+
.display-toast-provider[data-position] & {
|
|
397
|
+
&.v-enter-from {
|
|
398
|
+
transform: none;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
&.v-enter-active {
|
|
402
|
+
animation: display-toast-provider-fade var(--_reveal) linear forwards;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
&.v-leave-active {
|
|
406
|
+
animation: display-toast-provider-fade var(--_reveal) linear reverse forwards;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
&.v-move {
|
|
410
|
+
transition: none;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
&[data-paused] .display-toast-provider-progress {
|
|
416
|
+
animation-play-state: paused;
|
|
417
|
+
}
|
|
418
|
+
|
|
355
419
|
.display-toast-provider-progress {
|
|
356
420
|
position: absolute;
|
|
357
421
|
inset-block-end: 4px;
|
|
@@ -359,9 +423,9 @@ onUnmounted(() => {
|
|
|
359
423
|
height: 3px;
|
|
360
424
|
transform: scaleX(0);
|
|
361
425
|
transform-origin: right;
|
|
362
|
-
background: var(--theme-accent);
|
|
426
|
+
background: var(--display-toast-provider-progress-colour, var(--theme-accent));
|
|
363
427
|
border-radius: inherit;
|
|
364
|
-
animation: progress var(--_duration) linear forwards;
|
|
428
|
+
animation: display-toast-provider-progress var(--_duration) linear forwards;
|
|
365
429
|
}
|
|
366
430
|
}
|
|
367
431
|
}
|
|
@@ -193,3 +193,26 @@ FullWidth.args = {
|
|
|
193
193
|
position: "top",
|
|
194
194
|
autoDismiss: false,
|
|
195
195
|
};
|
|
196
|
+
|
|
197
|
+
export const StressTest = Template.bind({});
|
|
198
|
+
StressTest.storyName = "Stress Test (Worst-Case Data)";
|
|
199
|
+
StressTest.args = {
|
|
200
|
+
theme: "error",
|
|
201
|
+
alignment: "center",
|
|
202
|
+
autoDismiss: true,
|
|
203
|
+
duration: 10000,
|
|
204
|
+
title: "Donaudampfschifffahrtselektrizitätenhauptbetriebswerkbauunterbeamtengesellschaft",
|
|
205
|
+
description:
|
|
206
|
+
"Ihre Zahlung konnte leider nicht verarbeitet werden. Bitte überprüfen Sie Ihre Kartendaten und versuchen Sie es erneut, oder wenden Sie sich an Ihre Bank. https://example.com/a-very-long-path-segment-without-any-spaces-at-all-in-it",
|
|
207
|
+
};
|
|
208
|
+
StressTest.parameters = {
|
|
209
|
+
docs: {
|
|
210
|
+
description: {
|
|
211
|
+
story:
|
|
212
|
+
"Long unbroken title, long German description and a long URL, centred (the alignment most likely to overflow). " +
|
|
213
|
+
"Toasts are fixed to the viewport, so use Storybook's viewport toolbar to check narrow screens. Try every alignment " +
|
|
214
|
+
"and fullWidth: the toast should stay inside the viewport, cap at 48rem on wide screens, and wrap the unbroken text. " +
|
|
215
|
+
"Hover it or tab to it to check the progress bar and the 10s timer pause.",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|