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