23 changed files with 536 additions and 131 deletions
@ -1,77 +1,84 @@ |
|||
import { createIcon } from './create-icon'; |
|||
import { createIconifyIcon } from './create-icon'; |
|||
|
|||
export const IconDefault = createIcon('ic:round-auto-awesome'); |
|||
export const IconDefault = createIconifyIcon('ic:round-auto-awesome'); |
|||
|
|||
export const IcRoundKeyboardArrowDown = createIcon( |
|||
export const IcRoundKeyboardArrowDown = createIconifyIcon( |
|||
'ic:round-keyboard-arrow-down', |
|||
); |
|||
|
|||
export const IcRoundChevronRight = createIcon('ic:round-chevron-right'); |
|||
export const IcRoundChevronRight = createIconifyIcon('ic:round-chevron-right'); |
|||
|
|||
export const IcRoundKeyboard = createIcon('ic:round-keyboard'); |
|||
// export const IcRoundMenuOpen = createIcon('ic:round-menu-open');
|
|||
export const IcRoundMenu = createIconifyIcon('ic:round-menu'); |
|||
|
|||
export const IcRoundMenu = createIcon('ic:round-menu'); |
|||
export const IcRoundMoreHoriz = createIconifyIcon('ic:round-more-horiz'); |
|||
|
|||
export const IcRoundMoreHoriz = createIcon('ic:round-more-horiz'); |
|||
export const IcRoundFitScreen = createIconifyIcon('ic:round-fit-screen'); |
|||
|
|||
export const IcRoundFitScreen = createIcon('ic:round-fit-screen'); |
|||
export const IcTwotoneFitScreen = createIconifyIcon('ic:twotone-fit-screen'); |
|||
|
|||
export const IcTwotoneFitScreen = createIcon('ic:twotone-fit-screen'); |
|||
export const IcRoundColorLens = createIconifyIcon('ic:round-color-lens'); |
|||
|
|||
export const IcRoundColorLens = createIcon('ic:round-color-lens'); |
|||
export const IcRoundMoreVert = createIconifyIcon('ic:round-more-vert'); |
|||
|
|||
export const IcRoundMoreVert = createIcon('ic:round-more-vert'); |
|||
export const IcRoundFullscreen = createIconifyIcon('ic:round-fullscreen'); |
|||
|
|||
export const IcRoundFullscreen = createIcon('ic:round-fullscreen'); |
|||
|
|||
export const IcRoundFullscreenExit = createIcon('ic:round-fullscreen-exit'); |
|||
|
|||
export const IcRoundAutoAwesome = createIcon('ic:round-auto-awesome'); |
|||
export const IcRoundFullscreenExit = createIconifyIcon( |
|||
'ic:round-fullscreen-exit', |
|||
); |
|||
|
|||
export const IcRoundClose = createIcon('ic:round-close'); |
|||
export const IcRoundClose = createIconifyIcon('ic:round-close'); |
|||
|
|||
export const IcRoundRestartAlt = createIcon('ic:round-restart-alt'); |
|||
export const IcRoundRestartAlt = createIconifyIcon('ic:round-restart-alt'); |
|||
|
|||
export const IcRoundLogout = createIcon('ic:round-logout'); |
|||
export const IcRoundLogout = createIconifyIcon('ic:round-logout'); |
|||
|
|||
export const IcOutlineVisibility = createIcon('ic:outline-visibility'); |
|||
export const IcOutlineVisibility = createIconifyIcon('ic:outline-visibility'); |
|||
|
|||
export const IcOutlineVisibilityOff = createIcon('ic:outline-visibility-off'); |
|||
export const IcOutlineVisibilityOff = createIconifyIcon( |
|||
'ic:outline-visibility-off', |
|||
); |
|||
|
|||
export const IcRoundSearch = createIcon('ic:round-search'); |
|||
export const IcRoundSearch = createIconifyIcon('ic:round-search'); |
|||
|
|||
export const IcRoundFolderCopy = createIcon('ic:round-folder-copy'); |
|||
export const IcRoundFolderCopy = createIconifyIcon('ic:round-folder-copy'); |
|||
|
|||
export const IcRoundSubdirectoryArrowLeft = createIcon( |
|||
export const IcRoundSubdirectoryArrowLeft = createIconifyIcon( |
|||
'ic:round-subdirectory-arrow-left', |
|||
); |
|||
export const IcRoundArrowUpward = createIcon('ic:round-arrow-upward'); |
|||
export const IcRoundArrowUpward = createIconifyIcon('ic:round-arrow-upward'); |
|||
|
|||
export const IcRoundArrowDownward = createIcon('ic:round-arrow-downward'); |
|||
export const IcRoundArrowDownward = createIconifyIcon( |
|||
'ic:round-arrow-downward', |
|||
); |
|||
|
|||
export const IcBaselineLanguage = createIcon('ic:baseline-language'); |
|||
export const IcBaselineLanguage = createIconifyIcon('ic:baseline-language'); |
|||
|
|||
export const IcRoundSearchOff = createIcon('ic:round-search-off'); |
|||
export const IcRoundSearchOff = createIconifyIcon('ic:round-search-off'); |
|||
|
|||
export const IcRoundNotificationsNone = createIcon( |
|||
export const IcRoundNotificationsNone = createIconifyIcon( |
|||
'ic:round-notifications-none', |
|||
); |
|||
|
|||
export const IcRoundMarkEmailRead = createIcon('ic:round-mark-email-read'); |
|||
export const IcRoundMarkEmailRead = createIconifyIcon( |
|||
'ic:round-mark-email-read', |
|||
); |
|||
|
|||
export const IcRoundWbSunny = createIcon('ic:round-wb-sunny'); |
|||
export const IcRoundWbSunny = createIconifyIcon('ic:round-wb-sunny'); |
|||
|
|||
export const IcRoundMotionPhotosAuto = createIcon( |
|||
export const IcRoundMotionPhotosAuto = createIconifyIcon( |
|||
'ic:round-motion-photos-auto', |
|||
); |
|||
|
|||
export const IcRoundSettingsSuggest = createIcon('ic:round-settings-suggest'); |
|||
export const IcRoundSettingsSuggest = createIconifyIcon( |
|||
'ic:round-settings-suggest', |
|||
); |
|||
|
|||
export const IcRoundArrowBackIosNew = createIcon('ic:round-arrow-back-ios-new'); |
|||
export const IcRoundArrowBackIosNew = createIconifyIcon( |
|||
'ic:round-arrow-back-ios-new', |
|||
); |
|||
|
|||
export const IcRoundMultipleStop = createIcon('ic:round-multiple-stop'); |
|||
export const IcRoundMultipleStop = createIconifyIcon('ic:round-multiple-stop'); |
|||
|
|||
export const IcRoundRefresh = createIcon('ic:round-refresh'); |
|||
export const IcRoundRefresh = createIconifyIcon('ic:round-refresh'); |
|||
|
|||
export const IcRoundCreditScore = createIcon('ic:round-credit-score'); |
|||
export const IcRoundCreditScore = createIconifyIcon('ic:round-credit-score'); |
|||
|
|||
@ -1,49 +1,49 @@ |
|||
import { createIcon } from './create-icon'; |
|||
import { createIconifyIcon } from './create-icon'; |
|||
|
|||
export const MdiKeyboardEsc = createIcon('mdi:keyboard-esc'); |
|||
export const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc'); |
|||
|
|||
export const MdiLoading = createIcon('mdi:loading'); |
|||
export const MdiLoading = createIconifyIcon('mdi:loading'); |
|||
|
|||
export const MdiWechat = createIcon('mdi:wechat'); |
|||
export const MdiWechat = createIconifyIcon('mdi:wechat'); |
|||
|
|||
export const MdiGithub = createIcon('mdi:github'); |
|||
export const MdiGithub = createIconifyIcon('mdi:github'); |
|||
|
|||
export const MdiGoogle = createIcon('mdi:google'); |
|||
export const MdiGoogle = createIconifyIcon('mdi:google'); |
|||
|
|||
export const MdiQqchat = createIcon('mdi:qqchat'); |
|||
export const MdiQqchat = createIconifyIcon('mdi:qqchat'); |
|||
|
|||
export const MdiPin = createIcon('mdi:pin'); |
|||
export const MdiPin = createIconifyIcon('mdi:pin'); |
|||
|
|||
export const MdiPinOff = createIcon('mdi:pin-off'); |
|||
export const MdiPinOff = createIconifyIcon('mdi:pin-off'); |
|||
|
|||
export const MdiFormatHorizontalAlignLeft = createIcon( |
|||
export const MdiFormatHorizontalAlignLeft = createIconifyIcon( |
|||
'mdi:format-horizontal-align-left', |
|||
); |
|||
|
|||
export const MdiFormatHorizontalAlignRight = createIcon( |
|||
export const MdiFormatHorizontalAlignRight = createIconifyIcon( |
|||
'mdi:format-horizontal-align-right', |
|||
); |
|||
|
|||
export const MdiArrowExpandHorizontal = createIcon( |
|||
export const MdiArrowExpandHorizontal = createIconifyIcon( |
|||
'mdi:arrow-expand-horizontal', |
|||
); |
|||
|
|||
export const MdiMenuClose = createIcon('mdi:menu-close'); |
|||
export const MdiMenuClose = createIconifyIcon('mdi:menu-close'); |
|||
|
|||
export const MdiMenuOpen = createIcon('mdi:menu-open'); |
|||
export const MdiMenuOpen = createIconifyIcon('mdi:menu-open'); |
|||
|
|||
export const MdiDockLeft = createIcon('mdi:dock-left'); |
|||
export const MdiDockLeft = createIconifyIcon('mdi:dock-left'); |
|||
|
|||
export const MdiDockRight = createIcon('mdi:dock-right'); |
|||
export const MdiDockRight = createIconifyIcon('mdi:dock-right'); |
|||
|
|||
export const MdiDockBottom = createIcon('mdi:dock-bottom'); |
|||
export const MdiDockBottom = createIconifyIcon('mdi:dock-bottom'); |
|||
|
|||
export const MdiDriveDocument = createIcon('mdi:drive-document'); |
|||
export const MdiDriveDocument = createIconifyIcon('mdi:drive-document'); |
|||
|
|||
export const MdiMoonAndStars = createIcon('mdi:moon-and-stars'); |
|||
export const MdiMoonAndStars = createIconifyIcon('mdi:moon-and-stars'); |
|||
|
|||
export const MdiEditBoxOutline = createIcon('mdi:edit-box-outline'); |
|||
export const MdiEditBoxOutline = createIconifyIcon('mdi:edit-box-outline'); |
|||
|
|||
export const MdiQuestionMarkCircleOutline = createIcon( |
|||
export const MdiQuestionMarkCircleOutline = createIconifyIcon( |
|||
'mdi:question-mark-circle-outline', |
|||
); |
|||
|
|||
@ -1,41 +0,0 @@ |
|||
<script lang="ts" setup> |
|||
import { Toaster as Sonner, type ToasterProps } from 'vue-sonner'; |
|||
|
|||
const props = withDefaults(defineProps<ToasterProps>(), { |
|||
closeButton: true, |
|||
duration: 2500, |
|||
position: 'top-right', |
|||
richColors: true, |
|||
visibleToasts: 3, |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Sonner |
|||
class="toaster group" |
|||
v-bind="props" |
|||
:toast-options="{ |
|||
classes: { |
|||
closeButton: |
|||
'!border-border group-[.toast]:bg-muted group-[.toast]:text-muted-foreground !bg-muted hover:!text-foreground', |
|||
toast: |
|||
'group toast group-[.toaster]:bg-background group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg', |
|||
description: 'group-[.toast]:text-muted-foreground', |
|||
actionButton: |
|||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground', |
|||
cancelButton: |
|||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground', |
|||
}, |
|||
}" |
|||
/> |
|||
</template> |
|||
|
|||
<!-- <style scoped> |
|||
:deep([data-sonner-toaster][data-theme='dark']), |
|||
:deep([data-sonner-toaster][data-theme='light']) { |
|||
--normal-bg: hsl(var(--background)); |
|||
--normal-border: theme('colors.border'); |
|||
--normal-text: theme('colors.popover.foreground'); |
|||
--border-radius: theme('borderRadius.md'); |
|||
} |
|||
</style> --> |
|||
@ -1,2 +0,0 @@ |
|||
export { default as Toaster } from './Sonner.vue'; |
|||
export { toast } from 'vue-sonner'; |
|||
@ -0,0 +1,35 @@ |
|||
<script setup lang="ts"> |
|||
import { computed } from 'vue'; |
|||
|
|||
import { cn } from '@vben-core/toolkit'; |
|||
|
|||
import { |
|||
ToastRoot, |
|||
type ToastRootEmits, |
|||
useForwardPropsEmits, |
|||
} from 'radix-vue'; |
|||
|
|||
import { type ToastProps, toastVariants } from '.'; |
|||
|
|||
const props = defineProps<ToastProps>(); |
|||
|
|||
const emits = defineEmits<ToastRootEmits>(); |
|||
|
|||
const delegatedProps = computed(() => { |
|||
const { class: _, ...delegated } = props; |
|||
|
|||
return delegated; |
|||
}); |
|||
|
|||
const forwarded = useForwardPropsEmits(delegatedProps, emits); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastRoot |
|||
v-bind="forwarded" |
|||
:class="cn(toastVariants({ variant }), props.class)" |
|||
@update:open="onOpenChange" |
|||
> |
|||
<slot></slot> |
|||
</ToastRoot> |
|||
</template> |
|||
@ -0,0 +1,31 @@ |
|||
<script setup lang="ts"> |
|||
import { type HTMLAttributes, computed } from 'vue'; |
|||
|
|||
import { cn } from '@vben-core/toolkit'; |
|||
|
|||
import { ToastAction, type ToastActionProps } from 'radix-vue'; |
|||
|
|||
const props = defineProps< |
|||
{ class?: HTMLAttributes['class'] } & ToastActionProps |
|||
>(); |
|||
|
|||
const delegatedProps = computed(() => { |
|||
const { class: _, ...delegated } = props; |
|||
|
|||
return delegated; |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastAction |
|||
v-bind="delegatedProps" |
|||
:class=" |
|||
cn( |
|||
'hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive border-border inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:outline-none focus:ring-1 disabled:pointer-events-none disabled:opacity-50', |
|||
props.class, |
|||
) |
|||
" |
|||
> |
|||
<slot></slot> |
|||
</ToastAction> |
|||
</template> |
|||
@ -0,0 +1,34 @@ |
|||
<script setup lang="ts"> |
|||
import { type HTMLAttributes, computed } from 'vue'; |
|||
|
|||
import { cn } from '@vben-core/toolkit'; |
|||
|
|||
import { Cross2Icon } from '@radix-icons/vue'; |
|||
import { ToastClose, type ToastCloseProps } from 'radix-vue'; |
|||
|
|||
const props = defineProps< |
|||
{ |
|||
class?: HTMLAttributes['class']; |
|||
} & ToastCloseProps |
|||
>(); |
|||
|
|||
const delegatedProps = computed(() => { |
|||
const { class: _, ...delegated } = props; |
|||
|
|||
return delegated; |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastClose |
|||
v-bind="delegatedProps" |
|||
:class=" |
|||
cn( |
|||
'text-foreground/50 hover:text-foreground absolute right-1 top-1 rounded-md p-1 opacity-0 transition-opacity focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', |
|||
props.class, |
|||
) |
|||
" |
|||
> |
|||
<Cross2Icon class="h-4 w-4" /> |
|||
</ToastClose> |
|||
</template> |
|||
@ -0,0 +1,26 @@ |
|||
<script setup lang="ts"> |
|||
import { type HTMLAttributes, computed } from 'vue'; |
|||
|
|||
import { cn } from '@vben-core/toolkit'; |
|||
|
|||
import { ToastDescription, type ToastDescriptionProps } from 'radix-vue'; |
|||
|
|||
const props = defineProps< |
|||
{ class?: HTMLAttributes['class'] } & ToastDescriptionProps |
|||
>(); |
|||
|
|||
const delegatedProps = computed(() => { |
|||
const { class: _, ...delegated } = props; |
|||
|
|||
return delegated; |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastDescription |
|||
:class="cn('text-sm opacity-90', props.class)" |
|||
v-bind="delegatedProps" |
|||
> |
|||
<slot></slot> |
|||
</ToastDescription> |
|||
</template> |
|||
@ -0,0 +1,11 @@ |
|||
<script setup lang="ts"> |
|||
import { ToastProvider, type ToastProviderProps } from 'radix-vue'; |
|||
|
|||
const props = defineProps<ToastProviderProps>(); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastProvider v-bind="props"> |
|||
<slot></slot> |
|||
</ToastProvider> |
|||
</template> |
|||
@ -0,0 +1,26 @@ |
|||
<script setup lang="ts"> |
|||
import { type HTMLAttributes, computed } from 'vue'; |
|||
|
|||
import { cn } from '@vben-core/toolkit'; |
|||
|
|||
import { ToastTitle, type ToastTitleProps } from 'radix-vue'; |
|||
|
|||
const props = defineProps< |
|||
{ class?: HTMLAttributes['class'] } & ToastTitleProps |
|||
>(); |
|||
|
|||
const delegatedProps = computed(() => { |
|||
const { class: _, ...delegated } = props; |
|||
|
|||
return delegated; |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastTitle |
|||
v-bind="delegatedProps" |
|||
:class="cn('text-sm font-semibold [&+div]:text-xs', props.class)" |
|||
> |
|||
<slot></slot> |
|||
</ToastTitle> |
|||
</template> |
|||
@ -0,0 +1,29 @@ |
|||
<script setup lang="ts"> |
|||
import { type HTMLAttributes, computed } from 'vue'; |
|||
|
|||
import { cn } from '@vben-core/toolkit'; |
|||
|
|||
import { ToastViewport, type ToastViewportProps } from 'radix-vue'; |
|||
|
|||
const props = defineProps< |
|||
{ class?: HTMLAttributes['class'] } & ToastViewportProps |
|||
>(); |
|||
|
|||
const delegatedProps = computed(() => { |
|||
const { class: _, ...delegated } = props; |
|||
|
|||
return delegated; |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastViewport |
|||
v-bind="delegatedProps" |
|||
:class=" |
|||
cn( |
|||
'fixed top-0 z-[1200] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', |
|||
props.class, |
|||
) |
|||
" |
|||
/> |
|||
</template> |
|||
@ -0,0 +1,38 @@ |
|||
<script setup lang="ts"> |
|||
import { isVNode } from 'vue'; |
|||
|
|||
import { |
|||
Toast, |
|||
ToastClose, |
|||
ToastDescription, |
|||
ToastProvider, |
|||
ToastTitle, |
|||
ToastViewport, |
|||
} from '.'; |
|||
import { useToast } from './use-toast'; |
|||
|
|||
const { toasts } = useToast(); |
|||
</script> |
|||
|
|||
<template> |
|||
<ToastProvider swipe-direction="down"> |
|||
<Toast v-for="toast in toasts" :key="toast.id" v-bind="toast"> |
|||
<div class="grid gap-1"> |
|||
<ToastTitle v-if="toast.title"> |
|||
{{ toast.title }} |
|||
</ToastTitle> |
|||
<template v-if="toast.description"> |
|||
<ToastDescription v-if="isVNode(toast.description)"> |
|||
<component :is="toast.description" /> |
|||
</ToastDescription> |
|||
<ToastDescription v-else> |
|||
{{ toast.description }} |
|||
</ToastDescription> |
|||
</template> |
|||
<ToastClose /> |
|||
</div> |
|||
<component :is="toast.action" /> |
|||
</Toast> |
|||
<ToastViewport /> |
|||
</ToastProvider> |
|||
</template> |
|||
@ -0,0 +1,39 @@ |
|||
import type { ToastRootProps } from 'radix-vue'; |
|||
|
|||
import type { HTMLAttributes } from 'vue'; |
|||
|
|||
import { type VariantProps, cva } from 'class-variance-authority'; |
|||
|
|||
export { default as Toast } from './Toast.vue'; |
|||
export { default as ToastAction } from './ToastAction.vue'; |
|||
export { default as ToastClose } from './ToastClose.vue'; |
|||
export { default as ToastDescription } from './ToastDescription.vue'; |
|||
export { default as ToastProvider } from './ToastProvider.vue'; |
|||
export { default as ToastTitle } from './ToastTitle.vue'; |
|||
export { default as ToastViewport } from './ToastViewport.vue'; |
|||
export { default as Toaster } from './Toaster.vue'; |
|||
export { toast, useToast } from './use-toast'; |
|||
|
|||
export const toastVariants = cva( |
|||
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full', |
|||
{ |
|||
defaultVariants: { |
|||
variant: 'default', |
|||
}, |
|||
variants: { |
|||
variant: { |
|||
default: 'border bg-background border-border text-foreground', |
|||
destructive: |
|||
'destructive group border-destructive bg-destructive text-destructive-foreground', |
|||
}, |
|||
}, |
|||
}, |
|||
); |
|||
|
|||
type ToastVariants = VariantProps<typeof toastVariants>; |
|||
|
|||
export interface ToastProps extends ToastRootProps { |
|||
class?: HTMLAttributes['class']; |
|||
onOpenChange?: ((value: boolean) => void) | undefined; |
|||
variant?: ToastVariants['variant']; |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
import type { ToastProps } from '.'; |
|||
|
|||
import { computed, ref } from 'vue'; |
|||
import type { Component, VNode } from 'vue'; |
|||
|
|||
const TOAST_LIMIT = 1; |
|||
const TOAST_REMOVE_DELAY = 1_000_000; |
|||
|
|||
export type StringOrVNode = (() => VNode) | VNode | string; |
|||
|
|||
type ToasterToast = { |
|||
action?: Component; |
|||
description?: StringOrVNode; |
|||
id: string; |
|||
title?: string; |
|||
} & ToastProps; |
|||
|
|||
const actionTypes = { |
|||
ADD_TOAST: 'ADD_TOAST', |
|||
DISMISS_TOAST: 'DISMISS_TOAST', |
|||
REMOVE_TOAST: 'REMOVE_TOAST', |
|||
UPDATE_TOAST: 'UPDATE_TOAST', |
|||
} as const; |
|||
|
|||
let count = 0; |
|||
|
|||
function genId() { |
|||
count = (count + 1) % Number.MAX_VALUE; |
|||
return count.toString(); |
|||
} |
|||
|
|||
type ActionType = typeof actionTypes; |
|||
|
|||
type Action = |
|||
| { |
|||
toast: Partial<ToasterToast>; |
|||
type: ActionType['UPDATE_TOAST']; |
|||
} |
|||
| { |
|||
toast: ToasterToast; |
|||
type: ActionType['ADD_TOAST']; |
|||
} |
|||
| { |
|||
toastId?: ToasterToast['id']; |
|||
type: ActionType['DISMISS_TOAST']; |
|||
} |
|||
| { |
|||
toastId?: ToasterToast['id']; |
|||
type: ActionType['REMOVE_TOAST']; |
|||
}; |
|||
|
|||
interface State { |
|||
toasts: ToasterToast[]; |
|||
} |
|||
|
|||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>(); |
|||
|
|||
function addToRemoveQueue(toastId: string) { |
|||
if (toastTimeouts.has(toastId)) return; |
|||
|
|||
const timeout = setTimeout(() => { |
|||
toastTimeouts.delete(toastId); |
|||
dispatch({ |
|||
toastId, |
|||
type: actionTypes.REMOVE_TOAST, |
|||
}); |
|||
}, TOAST_REMOVE_DELAY); |
|||
|
|||
toastTimeouts.set(toastId, timeout); |
|||
} |
|||
|
|||
const state = ref<State>({ |
|||
toasts: [], |
|||
}); |
|||
|
|||
function dispatch(action: Action) { |
|||
switch (action.type) { |
|||
case actionTypes.ADD_TOAST: { |
|||
state.value.toasts = [action.toast, ...state.value.toasts].slice( |
|||
0, |
|||
TOAST_LIMIT, |
|||
); |
|||
break; |
|||
} |
|||
|
|||
case actionTypes.UPDATE_TOAST: { |
|||
state.value.toasts = state.value.toasts.map((t) => |
|||
t.id === action.toast.id ? { ...t, ...action.toast } : t, |
|||
); |
|||
break; |
|||
} |
|||
|
|||
case actionTypes.DISMISS_TOAST: { |
|||
const { toastId } = action; |
|||
|
|||
if (toastId) { |
|||
addToRemoveQueue(toastId); |
|||
} else { |
|||
state.value.toasts.forEach((toast) => { |
|||
addToRemoveQueue(toast.id); |
|||
}); |
|||
} |
|||
|
|||
state.value.toasts = state.value.toasts.map((t) => |
|||
t.id === toastId || toastId === undefined |
|||
? { |
|||
...t, |
|||
open: false, |
|||
} |
|||
: t, |
|||
); |
|||
break; |
|||
} |
|||
|
|||
case actionTypes.REMOVE_TOAST: { |
|||
state.value.toasts = |
|||
action.toastId === undefined |
|||
? [] |
|||
: state.value.toasts.filter((t) => t.id !== action.toastId); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
function useToast() { |
|||
return { |
|||
dismiss: (toastId?: string) => |
|||
dispatch({ toastId, type: actionTypes.DISMISS_TOAST }), |
|||
toast, |
|||
toasts: computed(() => state.value.toasts), |
|||
}; |
|||
} |
|||
|
|||
type Toast = Omit<ToasterToast, 'id'>; |
|||
|
|||
function toast(props: Toast) { |
|||
const id = genId(); |
|||
|
|||
const update = (props: ToasterToast) => |
|||
dispatch({ |
|||
toast: { ...props, id }, |
|||
type: actionTypes.UPDATE_TOAST, |
|||
}); |
|||
|
|||
const dismiss = () => |
|||
dispatch({ toastId: id, type: actionTypes.DISMISS_TOAST }); |
|||
|
|||
dispatch({ |
|||
toast: { |
|||
...props, |
|||
id, |
|||
onOpenChange: (open: boolean) => { |
|||
if (!open) dismiss(); |
|||
}, |
|||
open: true, |
|||
}, |
|||
type: actionTypes.ADD_TOAST, |
|||
}); |
|||
|
|||
return { |
|||
dismiss, |
|||
id, |
|||
update, |
|||
}; |
|||
} |
|||
|
|||
export { toast, useToast }; |
|||
Loading…
Reference in new issue