Skip to content

Commit af0680d

Browse files
committed
feat(toast): enhance Toast component with viewport context, new props, and demo examples
1 parent de4f28d commit af0680d

File tree

22 files changed

+609
-43
lines changed

22 files changed

+609
-43
lines changed

headless/src/components/toast/context.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { shallowRef } from 'vue';
2+
import type { ComputedRef } from 'vue';
3+
import type { ClassValue } from '../../types';
24
import { useCollection, useContext } from '../../composables';
35
import type { ToastProviderContextParams, ToastRootContextParams, ToastThemeContextParams } from './types';
46

@@ -48,3 +50,8 @@ export const [provideToastThemeContext, useToastThemeContext] = useContext(
4850
'ToastTheme',
4951
(params: ToastThemeContextParams) => params
5052
);
53+
54+
export const [provideToastViewportThemeContext, useToastViewportThemeContext] = useContext(
55+
'ToastViewportTheme',
56+
(cls: ComputedRef<ClassValue>) => cls
57+
);

headless/src/components/toast/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
export { default as ToastPortal } from '../portal/portal.vue';
2+
export { default as ToastProvider } from './toast-provider.vue';
23
export { default as ToastRoot } from './toast-root.vue';
34
export { default as ToastViewport } from './toast-viewport.vue';
45
export { default as ToastClose } from './toast-close.vue';
56
export { default as ToastAction } from './toast-action.vue';
67
export { default as ToastTitle } from './toast-title.vue';
78
export { default as ToastDescription } from './toast-description.vue';
89

9-
export { provideToastThemeContext } from './context';
10+
export { provideToastThemeContext, provideToastViewportThemeContext } from './context';
1011

1112
export type { PortalProps as ToastPortalProps } from '../portal/types';
1213
export type {

headless/src/components/toast/toast-root.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ defineOptions({
3434
3535
const props = withDefaults(defineProps<ToastRootProps>(), {
3636
as: 'li',
37-
type: 'foreground',
37+
liveType: 'foreground',
3838
open: undefined,
3939
defaultOpen: true
4040
});
@@ -295,7 +295,7 @@ onUnmounted(() => {
295295
<ToastAnnounce
296296
v-if="announceTextContent"
297297
role="alert"
298-
:aria-live="type === 'foreground' ? 'assertive' : 'polite'"
298+
:aria-live="liveType === 'foreground' ? 'assertive' : 'polite'"
299299
aria-atomic="true"
300300
>
301301
{{ announceTextContent }}

headless/src/components/toast/toast-viewport.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { onKeyStroke } from '@vueuse/core';
44
import { useForwardElement } from '../../composables';
55
import { getActiveElement, getTabbableCandidates, tryFocusFirst } from '../../shared';
66
import { Primitive } from '../primitive';
7-
import { useCollectionContext, useToastProviderContext, useToastThemeContext } from './context';
7+
import { useCollectionContext, useToastProviderContext, useToastViewportThemeContext } from './context';
88
import ToastFocusProxy from './toast-focus-proxy.vue';
99
import { VIEWPORT_PAUSE, VIEWPORT_RESUME } from './shared';
1010
import type { TabbingDirection, ToastViewportProps } from './types';
@@ -22,7 +22,9 @@ const props = withDefaults(defineProps<ToastViewportProps>(), {
2222
2323
const attrs = useAttrs();
2424
25-
const themeContext = useToastThemeContext();
25+
const themeContext = useToastViewportThemeContext();
26+
const cls = computed(() => themeContext?.value);
27+
2628
const { onViewportElementChange, toastCount, isClosePausedRef } = useToastProviderContext('ToastViewport');
2729
const { onContainerElementChange, getOrderedElements } = useCollectionContext('ToastViewport');
2830
const [headFocusProxyElement, setHeadFocusProxyElement] = useForwardElement();
@@ -32,8 +34,6 @@ const [viewportElement, setViewportElement] = useForwardElement(el => {
3234
onContainerElementChange(el);
3335
});
3436
35-
const cls = computed(() => themeContext?.ui?.value?.viewport);
36-
3737
const hasToasts = computed(() => toastCount.value > 0);
3838
const hotkeyMessage = computed(() => props.hotkey.join('+').replace(/Key/g, '').replace(/Digit/g, ''));
3939
const ariaLabel = computed(() =>

headless/src/components/toast/types.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,19 @@ export interface ToastProviderProps {
2929
swipeThreshold?: number;
3030
}
3131

32-
export interface ToastRootProps extends ForceMountProps, PrimitiveProps, /** @vue-ignore */ HTMLAttributes {
32+
export type ToastLiveType = 'foreground' | 'background';
33+
34+
export interface ToastRootProps
35+
extends ForceMountProps,
36+
PrimitiveProps,
37+
/** @vue-ignore */ Omit<HTMLAttributes, 'onPause'> {
3338
/**
3439
* Control the sensitivity of the toast for accessibility purposes.
3540
*
3641
* For toasts that are the result of a user action, choose `foreground`. Toasts generated from background tasks should
3742
* use `background`.
3843
*/
39-
type?: 'foreground' | 'background';
44+
liveType?: ToastLiveType;
4045
/** The controlled open state of the dialog. Can be bind as `v-model:open`. */
4146
open?: boolean;
4247
/** The open state of the dialog when it is initially rendered. Use when you do not need to control its open state. */
@@ -117,7 +122,7 @@ export interface ToastRootContextParams {
117122
onClose: () => void;
118123
}
119124

120-
export type ToastThemeSlot = 'root' | 'viewport' | 'title' | 'description' | 'action' | 'close';
125+
export type ToastThemeSlot = 'root' | 'title' | 'description' | 'action' | 'close';
121126

122127
export type ToastUi = Record<ToastThemeSlot, ClassValue>;
123128

playground/examples/toast/base.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script setup lang="ts">
2+
import { SButton, SCard, useToast } from '@ui';
3+
4+
const toast = useToast();
5+
</script>
6+
7+
<template>
8+
<SCard title="Base" :ui="{ content: 'flex gap-3' }">
9+
<SButton variant="pure" @click="toast({ title: 'Toast Title', description: 'Toast Description' })">Default</SButton>
10+
</SCard>
11+
</template>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<script setup lang="ts">
2+
import { SButton, SCard, useToast } from '@ui';
3+
4+
const toast = useToast();
5+
</script>
6+
7+
<template>
8+
<SCard title="Color" :ui="{ content: 'flex gap-3' }">
9+
<SButton
10+
color="success"
11+
@click="toast({ color: 'success', title: 'Toast Title', description: 'Toast Description' })"
12+
>
13+
Success
14+
</SButton>
15+
<SButton
16+
color="destructive"
17+
@click="toast({ color: 'destructive', title: 'Toast Title', description: 'Toast Description' })"
18+
>
19+
Destructive
20+
</SButton>
21+
<SButton
22+
color="warning"
23+
@click="toast({ color: 'warning', title: 'Toast Title', description: 'Toast Description' })"
24+
>
25+
Warning
26+
</SButton>
27+
<SButton color="info" @click="toast({ color: 'info', title: 'Toast Title', description: 'Toast Description' })">
28+
Info
29+
</SButton>
30+
<SButton color="carbon" @click="toast({ color: 'carbon', title: 'Toast Title', description: 'Toast Description' })">
31+
Carbon
32+
</SButton>
33+
<SButton
34+
color="secondary"
35+
@click="toast({ color: 'secondary', title: 'Toast Title', description: 'Toast Description' })"
36+
>
37+
Secondary
38+
</SButton>
39+
<SButton color="accent" @click="toast({ color: 'accent', title: 'Toast Title', description: 'Toast Description' })">
40+
Accent
41+
</SButton>
42+
</SCard>
43+
</template>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
import { defineComponent, h } from 'vue';
3+
import { SButton, SCard, SToastAction, useToast } from '@ui';
4+
5+
const toast = useToast();
6+
7+
const Content = defineComponent({
8+
setup() {
9+
return () =>
10+
h('div', [
11+
h(SToastAction, { altText: 'Action', asChild: true }, () =>
12+
h(SButton, { variant: 'pure', size: 'sm' }, () => 'action')
13+
)
14+
]);
15+
}
16+
});
17+
18+
const openWithContent = () => {
19+
toast({
20+
title: 'Toast Title',
21+
description: 'Toast Description',
22+
content: h(Content)
23+
});
24+
};
25+
</script>
26+
27+
<template>
28+
<SCard title="Custom Content" :ui="{ content: 'flex gap-3' }">
29+
<SButton variant="pure" @click="openWithContent">Default</SButton>
30+
</SCard>
31+
</template>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script setup lang="ts">
2+
import { SCard } from '@ui';
3+
import DemoToastBase from './base.vue';
4+
import DemoToastType from './type.vue';
5+
import DemoToastColor from './color.vue';
6+
import DemoToastPosition from './position.vue';
7+
import DemoToastContent from './content.vue';
8+
</script>
9+
10+
<template>
11+
<SCard title="Toast" :ui="{ content: 'flex-c gap-3' }">
12+
<DemoToastBase />
13+
<DemoToastType />
14+
<DemoToastColor />
15+
<DemoToastPosition />
16+
<DemoToastContent />
17+
</SCard>
18+
</template>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<script setup lang="ts">
2+
import { SButton, SCard, useToast } from '@ui';
3+
4+
const toast = useToast();
5+
</script>
6+
7+
<template>
8+
<SCard title="Position" :ui="{ content: 'flex gap-3' }">
9+
<SButton
10+
variant="pure"
11+
@click="toast({ position: 'top-left', title: 'Toast Title', description: 'Toast Description' })"
12+
>
13+
Top Left
14+
</SButton>
15+
<SButton variant="pure" @click="toast({ position: 'top', title: 'Toast Title', description: 'Toast Description' })">
16+
Top
17+
</SButton>
18+
<SButton
19+
variant="pure"
20+
@click="toast({ position: 'top-right', title: 'Toast Title', description: 'Toast Description' })"
21+
>
22+
Top Right
23+
</SButton>
24+
<SButton
25+
variant="pure"
26+
@click="toast({ position: 'bottom-left', title: 'Toast Title', description: 'Toast Description' })"
27+
>
28+
Bottom Left
29+
</SButton>
30+
<SButton
31+
variant="pure"
32+
@click="toast({ position: 'bottom', title: 'Toast Title', description: 'Toast Description' })"
33+
>
34+
Bottom
35+
</SButton>
36+
<SButton
37+
variant="pure"
38+
@click="toast({ position: 'bottom-right', title: 'Toast Title', description: 'Toast Description' })"
39+
>
40+
Bottom Right
41+
</SButton>
42+
</SCard>
43+
</template>

0 commit comments

Comments
 (0)