mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-02-02 18:28:40 +08:00
perf: replace vue-sonner with the toast component of shadcn-ui
This commit is contained in:
parent
fd61d2efc4
commit
0eda99ec3b
@ -113,6 +113,8 @@
|
||||
"title": "Preferences",
|
||||
"subtitle": "Customize Preferences & Preview in Real Time",
|
||||
"reset-tip": "The data has changed, click to reset",
|
||||
"reset-title": "Preferences reset",
|
||||
"reset-success": "Preferences reset successfully",
|
||||
"appearance": "Appearance",
|
||||
"layout": "Layout",
|
||||
"content": "Content",
|
||||
@ -140,7 +142,6 @@
|
||||
"copy": "Copy Preferences",
|
||||
"copy-success": "Copy successful. Please replace in `src/preferences.ts` of the app",
|
||||
"clear-and-logout": "Clear Cache & Logout",
|
||||
"reset-success": "Preferences reset successfully",
|
||||
"mode": "Mode",
|
||||
"logo-visible": "Display Logo",
|
||||
"general": "General",
|
||||
|
@ -113,7 +113,9 @@
|
||||
"preferences": {
|
||||
"title": "偏好设置",
|
||||
"subtitle": "自定义偏好设置 & 实时预览",
|
||||
"reset-title": "重置偏好设置",
|
||||
"reset-tip": "数据有变化,点击可进行重置",
|
||||
"reset-success": "重置偏好设置成功",
|
||||
"appearance": "外观",
|
||||
"layout": "布局",
|
||||
"content": "内容",
|
||||
@ -137,9 +139,8 @@
|
||||
"plain": "朴素",
|
||||
"rounded": "圆润",
|
||||
"copy": "复制偏好设置",
|
||||
"copy-success": "拷贝成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
|
||||
"copy-success": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
|
||||
"clear-and-logout": "清空缓存 & 退出登录",
|
||||
"reset-success": "重置偏好设置成功",
|
||||
"mode": "模式",
|
||||
"logo-visible": "显示 Logo",
|
||||
"general": "通用",
|
||||
|
@ -2,8 +2,8 @@ import { h } from 'vue';
|
||||
|
||||
import { Icon } from '@iconify/vue';
|
||||
|
||||
function createIcon(icon: string) {
|
||||
function createIconifyIcon(icon: string) {
|
||||
return h(Icon, { icon });
|
||||
}
|
||||
|
||||
export { createIcon };
|
||||
export { createIconifyIcon };
|
||||
|
@ -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 = createIconifyIcon(
|
||||
'ic:round-fullscreen-exit',
|
||||
);
|
||||
|
||||
export const IcRoundFullscreenExit = createIcon('ic:round-fullscreen-exit');
|
||||
export const IcRoundClose = createIconifyIcon('ic:round-close');
|
||||
|
||||
export const IcRoundAutoAwesome = createIcon('ic:round-auto-awesome');
|
||||
export const IcRoundRestartAlt = createIconifyIcon('ic:round-restart-alt');
|
||||
|
||||
export const IcRoundClose = createIcon('ic:round-close');
|
||||
export const IcRoundLogout = createIconifyIcon('ic:round-logout');
|
||||
|
||||
export const IcRoundRestartAlt = createIcon('ic:round-restart-alt');
|
||||
export const IcOutlineVisibility = createIconifyIcon('ic:outline-visibility');
|
||||
|
||||
export const IcRoundLogout = createIcon('ic:round-logout');
|
||||
export const IcOutlineVisibilityOff = createIconifyIcon(
|
||||
'ic:outline-visibility-off',
|
||||
);
|
||||
|
||||
export const IcOutlineVisibility = createIcon('ic:outline-visibility');
|
||||
export const IcRoundSearch = createIconifyIcon('ic:round-search');
|
||||
|
||||
export const IcOutlineVisibilityOff = createIcon('ic:outline-visibility-off');
|
||||
export const IcRoundFolderCopy = createIconifyIcon('ic:round-folder-copy');
|
||||
|
||||
export const IcRoundSearch = createIcon('ic:round-search');
|
||||
|
||||
export const IcRoundFolderCopy = createIcon('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',
|
||||
);
|
||||
|
@ -51,7 +51,6 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"lucide-vue-next": "^0.400.0",
|
||||
"radix-vue": "^1.9.0",
|
||||
"vue": "^3.4.31",
|
||||
"vue-sonner": "^1.1.3"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ export * from './ui/popover';
|
||||
export * from './ui/scroll-area';
|
||||
export * from './ui/select';
|
||||
export * from './ui/sheet';
|
||||
export * from './ui/sonner';
|
||||
export * from './ui/switch';
|
||||
export * from './ui/tabs';
|
||||
export * from './ui/toast';
|
||||
export * from './ui/toggle';
|
||||
export * from './ui/toggle-group';
|
||||
export * from './ui/tooltip';
|
||||
|
@ -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 };
|
@ -2,3 +2,4 @@ export * from './about';
|
||||
export * from './authentication';
|
||||
export * from './dashboard';
|
||||
export * from './fallback';
|
||||
export { useToast } from '@vben-core/shadcn-ui';
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
VbenIconButton,
|
||||
VbenSegmented,
|
||||
VbenSheet,
|
||||
toast,
|
||||
useToast,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
@ -56,7 +56,7 @@ import Trigger from './trigger.vue';
|
||||
import { useOpenPreferences } from './use-open-preferences';
|
||||
|
||||
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
||||
|
||||
const { toast } = useToast();
|
||||
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
|
||||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||
const appAiAssistant = defineModel<boolean>('appAiAssistant');
|
||||
@ -177,7 +177,10 @@ const { openPreferences } = useOpenPreferences();
|
||||
async function handleCopy() {
|
||||
await copy(JSON.stringify(diffPreference.value, null, 2));
|
||||
|
||||
toast($t('preferences.copy-success'));
|
||||
toast({
|
||||
description: $t('preferences.copy'),
|
||||
title: $t('preferences.copy-success'),
|
||||
});
|
||||
}
|
||||
|
||||
async function handleClearCache() {
|
||||
@ -192,7 +195,14 @@ async function handleReset() {
|
||||
}
|
||||
resetPreferences();
|
||||
await loadLocaleMessages(preferences.app.locale);
|
||||
toast($t('preferences.reset-success'));
|
||||
toast({
|
||||
description: $t('preferences.reset-title'),
|
||||
title: $t('preferences.reset-success'),
|
||||
});
|
||||
toast({
|
||||
description: $t('preferences.reset-title'),
|
||||
title: $t('preferences.reset-success'),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createIcon } from '@vben-core/iconify';
|
||||
import { createIconifyIcon } from '@vben-core/iconify';
|
||||
|
||||
import { loadSvgIcons } from './load';
|
||||
|
||||
@ -8,14 +8,14 @@ if (!loaded) {
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
const SvgAvatar1Icon = createIcon('svg:avatar-1');
|
||||
const SvgAvatar2Icon = createIcon('svg:avatar-2');
|
||||
const SvgAvatar3Icon = createIcon('svg:avatar-3');
|
||||
const SvgAvatar4Icon = createIcon('svg:avatar-4');
|
||||
const SvgDownloadIcon = createIcon('svg:download');
|
||||
const SvgCardIcon = createIcon('svg:card');
|
||||
const SvgBellIcon = createIcon('svg:bell');
|
||||
const SvgCakeIcon = createIcon('svg:cake');
|
||||
const SvgAvatar1Icon = createIconifyIcon('svg:avatar-1');
|
||||
const SvgAvatar2Icon = createIconifyIcon('svg:avatar-2');
|
||||
const SvgAvatar3Icon = createIconifyIcon('svg:avatar-3');
|
||||
const SvgAvatar4Icon = createIconifyIcon('svg:avatar-4');
|
||||
const SvgDownloadIcon = createIconifyIcon('svg:download');
|
||||
const SvgCardIcon = createIconifyIcon('svg:card');
|
||||
const SvgBellIcon = createIconifyIcon('svg:bell');
|
||||
const SvgCakeIcon = createIconifyIcon('svg:cake');
|
||||
|
||||
export {
|
||||
SvgAvatar1Icon,
|
||||
|
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -784,9 +784,6 @@ importers:
|
||||
vue:
|
||||
specifier: ^3.4.31
|
||||
version: 3.4.31(typescript@5.5.3)
|
||||
vue-sonner:
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3
|
||||
|
||||
packages/@core/ui-kit/tabs-ui:
|
||||
dependencies:
|
||||
@ -9183,9 +9180,6 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.4.31
|
||||
|
||||
vue-sonner@1.1.3:
|
||||
resolution: {integrity: sha512-6I+5GNobKvE2nR5MPhO+T59d4j2LXRQoc/ZCmGtCoBWKDQr5nzSqjFaOOdPysHFI2p42wNLhQMafd0N540UW9Q==}
|
||||
|
||||
vue-template-compiler@2.7.16:
|
||||
resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
|
||||
|
||||
@ -18548,8 +18542,6 @@ snapshots:
|
||||
'@vue/devtools-api': 6.6.3
|
||||
vue: 3.4.31(typescript@5.5.3)
|
||||
|
||||
vue-sonner@1.1.3: {}
|
||||
|
||||
vue-template-compiler@2.7.16:
|
||||
dependencies:
|
||||
de-indent: 1.0.2
|
||||
|
Loading…
Reference in New Issue
Block a user