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', |
'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 IcRoundMenu = createIconifyIcon('ic:round-menu'); |
||||
// export const IcRoundMenuOpen = createIcon('ic:round-menu-open');
|
|
||||
|
|
||||
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 = createIconifyIcon( |
||||
|
'ic:round-fullscreen-exit', |
||||
export const IcRoundFullscreenExit = createIcon('ic:round-fullscreen-exit'); |
); |
||||
|
|
||||
export const IcRoundAutoAwesome = createIcon('ic:round-auto-awesome'); |
|
||||
|
|
||||
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', |
'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', |
'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', |
'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', |
'mdi:format-horizontal-align-left', |
||||
); |
); |
||||
|
|
||||
export const MdiFormatHorizontalAlignRight = createIcon( |
export const MdiFormatHorizontalAlignRight = createIconifyIcon( |
||||
'mdi:format-horizontal-align-right', |
'mdi:format-horizontal-align-right', |
||||
); |
); |
||||
|
|
||||
export const MdiArrowExpandHorizontal = createIcon( |
export const MdiArrowExpandHorizontal = createIconifyIcon( |
||||
'mdi:arrow-expand-horizontal', |
'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', |
'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