chore: update uikit -> ui-kit

This commit is contained in:
vben
2024-06-23 20:03:41 +08:00
parent 89586ef2c4
commit d4f61c283f
351 changed files with 341 additions and 391 deletions

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
interface Props {
title?: string;
}
defineOptions({
name: 'PreferenceBlock',
});
withDefaults(defineProps<Props>(), {
title: '',
});
</script>
<template>
<div class="flex flex-col py-4">
<h3 class="mb-3 font-semibold leading-none tracking-tight">
{{ title }}
</h3>
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceAnimation',
});
const transitionProgress = defineModel<boolean>('transitionProgress', {
// 默认值
default: false,
});
const transitionName = defineModel<string>('transitionName');
const transitionEnable = defineModel<boolean>('transitionEnable');
const transitionLoading = defineModel<boolean>('transitionLoading');
const transitionPreset = ['fade', 'fade-slide', 'fade-up', 'fade-down'];
function handleClick(value: string) {
transitionName.value = value;
}
</script>
<template>
<SwitchItem v-model="transitionProgress">
{{ $t('preferences.animation.progress') }}
</SwitchItem>
<SwitchItem v-model="transitionLoading">
{{ $t('preferences.animation.loading') }}
</SwitchItem>
<SwitchItem v-model="transitionEnable">
{{ $t('preferences.animation.transition') }}
</SwitchItem>
<div
v-if="transitionEnable"
class="mb-2 mt-3 flex justify-between gap-3 px-2"
>
<div
v-for="item in transitionPreset"
:key="item"
:class="{
'outline-box-active': transitionName === item,
}"
class="outline-box p-2"
@click="handleClick(item)"
>
<div :class="`${item}-slow`" class="bg-accent h-10 w-12 rounded-md"></div>
</div>
</div>
</template>

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { $t } from '@vben/locales';
import { SUPPORT_LANGUAGES } from '@vben-core/preferences';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceGeneralConfig',
});
const appLocale = defineModel<string>('appLocale');
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
const appAiAssistant = defineModel<boolean>('appAiAssistant');
const localeItems: SelectListItem[] = SUPPORT_LANGUAGES.map((item) => ({
label: item.text,
value: item.key,
}));
</script>
<template>
<SelectItem v-model="appLocale" :items="localeItems">
{{ $t('preferences.language') }}
</SelectItem>
<SwitchItem v-model="appDynamicTitle">
{{ $t('preferences.dynamic-title') }}
</SwitchItem>
<SwitchItem v-model="appAiAssistant">
{{ $t('preferences.ai-assistant') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,17 @@
export { default as Block } from './block.vue';
export { default as Animation } from './general/animation.vue';
export { default as General } from './general/general.vue';
export { default as Breadcrumb } from './layout/breadcrumb.vue';
export { default as Content } from './layout/content.vue';
export { default as Footer } from './layout/footer.vue';
export { default as Header } from './layout/header.vue';
export { default as Layout } from './layout/layout.vue';
export { default as Navigation } from './layout/navigation.vue';
export { default as Sidebar } from './layout/sidebar.vue';
export { default as Tabbar } from './layout/tabbar.vue';
export { default as GlobalShortcutKeys } from './shortcut-keys/global.vue';
export { default as SwitchItem } from './switch-item.vue';
export { default as BuiltinTheme } from './theme/builtin.vue';
export { default as ColorMode } from './theme/color-mode.vue';
export { default as Radius } from './theme/radius.vue';
export { default as Theme } from './theme/theme.vue';

View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { computed } from 'vue';
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
import ToggleItem from '../toggle-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
const props = defineProps<{ disabled?: boolean }>();
const breadcrumbEnable = defineModel<boolean>('breadcrumbEnable');
const breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon');
const breadcrumbStyleType = defineModel<string>('breadcrumbStyleType');
const breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome');
const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
const typeItems: SelectListItem[] = [
{ label: $t('preferences.normal'), value: 'normal' },
{ label: $t('preferences.breadcrumb-background'), value: 'background' },
];
const disableItem = computed(() => {
return !breadcrumbEnable.value || props.disabled;
});
</script>
<template>
<SwitchItem v-model="breadcrumbEnable" :disabled="disabled">
{{ $t('preferences.breadcrumb-enable') }}
</SwitchItem>
<SwitchItem v-model="breadcrumbHideOnlyOne" :disabled="disableItem">
{{ $t('preferences.breadcrumb-hide-only-one') }}
</SwitchItem>
<SwitchItem v-model="breadcrumbShowHome" :disabled="disableItem">
{{ $t('preferences.breadcrumb-home') }}
</SwitchItem>
<SwitchItem v-model="breadcrumbShowIcon" :disabled="disableItem">
{{ $t('preferences.breadcrumb-icon') }}
</SwitchItem>
<ToggleItem
v-model="breadcrumbStyleType"
:disabled="disableItem"
:items="typeItems"
>
{{ $t('preferences.breadcrumb-style') }}
</ToggleItem>
</template>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import { type Component, computed } from 'vue';
import { $t } from '@vben/locales';
import { ContentCompact, ContentWide } from '../../icons';
defineOptions({
name: 'PreferenceLayoutContent',
});
const modelValue = defineModel<string>({ default: 'wide' });
const components: Record<string, Component> = {
compact: ContentCompact,
wide: ContentWide,
};
const PRESET = computed(() => [
{
name: $t('preferences.wide'),
type: 'wide',
},
{
name: '定宽',
type: 'compact',
},
]);
function activeClass(theme: string): string[] {
return theme === modelValue.value ? ['outline-box-active'] : [];
}
</script>
<template>
<div class="flex w-full gap-5">
<template v-for="theme in PRESET" :key="theme.name">
<div
class="flex w-[100px] cursor-pointer flex-col"
@click="modelValue = theme.type"
>
<div :class="activeClass(theme.type)" class="outline-box flex-center">
<component :is="components[theme.type]" />
</div>
<div class="text-muted-foreground mt-2 text-center text-xs">
{{ theme.name }}
</div>
</div>
</template>
</div>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
const footerEnable = defineModel<boolean>('footerEnable');
const footerFixed = defineModel<boolean>('footerFixed');
</script>
<template>
<SwitchItem v-model="footerEnable">
{{ $t('preferences.footer.visible') }}
</SwitchItem>
<SwitchItem v-model="footerFixed" :disabled="!footerEnable">
{{ $t('preferences.footer.fixed') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import type { LayoutHeaderModeType, SelectListItem } from '@vben/types';
import { $t } from '@vben/locales';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
defineProps<{ disabled: boolean }>();
const headerEnable = defineModel<boolean>('headerEnable');
const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
const localeItems: SelectListItem[] = [
{
label: $t('preferences.header.mode-static'),
value: 'static',
},
{
label: $t('preferences.header.mode-fixed'),
value: 'fixed',
},
{
label: $t('preferences.header.mode-auto'),
value: 'auto',
},
{
label: $t('preferences.header.mode-auto-scroll'),
value: 'auto-scroll',
},
];
</script>
<template>
<SwitchItem v-model="headerEnable" :disabled="disabled">
{{ $t('preferences.header-visible') }}
</SwitchItem>
<SelectItem
v-model="headerMode"
:disabled="!headerEnable"
:items="localeItems"
>
{{ $t('preferences.mode') }}
</SelectItem>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceInterfaceControl',
});
const tabsVisible = defineModel<boolean>('tabsVisible');
const logoVisible = defineModel<boolean>('logoVisible');
</script>
<template>
<SwitchItem v-model="tabsVisible">
{{ $t('preferences.tabs-visible') }}
</SwitchItem>
<SwitchItem v-model="logoVisible">
{{ $t('preferences.logo-visible') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
import type { LayoutType } from '@vben/types';
import { type Component, computed } from 'vue';
import { $t } from '@vben/locales';
import { MdiQuestionMarkCircleOutline } from '@vben-core/iconify';
import { VbenTooltip } from '@vben-core/shadcn-ui';
import {
FullContent,
HeaderNav,
MixedNav,
SidebarMixedNav,
SidebarNav,
} from '../../icons';
interface PresetItem {
name: string;
tip: string;
type: LayoutType;
}
defineOptions({
name: 'PreferenceLayout',
});
const modelValue = defineModel<LayoutType>({ default: 'sidebar-nav' });
const components: Record<LayoutType, Component> = {
'full-content': FullContent,
'header-nav': HeaderNav,
'mixed-nav': MixedNav,
'sidebar-mixed-nav': SidebarMixedNav,
'sidebar-nav': SidebarNav,
};
const PRESET = computed((): PresetItem[] => [
{
name: $t('preferences.vertical'),
tip: $t('preferences.vertical-tip'),
type: 'sidebar-nav',
},
{
name: $t('preferences.two-column'),
tip: $t('preferences.two-column-tip'),
type: 'sidebar-mixed-nav',
},
{
name: $t('preferences.horizontal'),
tip: $t('preferences.vertical-tip'),
type: 'header-nav',
},
{
name: $t('preferences.mixed-menu'),
tip: $t('preferences.mixed-menu-tip'),
type: 'mixed-nav',
},
{
name: $t('preferences.full-content'),
tip: $t('preferences.full-content-tip'),
type: 'full-content',
},
]);
function activeClass(theme: string): string[] {
return theme === modelValue.value ? ['outline-box-active'] : [];
}
</script>
<template>
<div class="flex w-full flex-wrap gap-5">
<template v-for="theme in PRESET" :key="theme.name">
<div
class="flex w-[100px] cursor-pointer flex-col"
@click="modelValue = theme.type"
>
<div :class="activeClass(theme.type)" class="outline-box flex-center">
<component :is="components[theme.type]" />
</div>
<div
class="text-muted-foreground flex-center hover:text-foreground mt-2 text-center text-xs"
>
{{ theme.name }}
<VbenTooltip v-if="theme.tip" side="bottom">
<template #trigger>
<MdiQuestionMarkCircleOutline class="ml-1 cursor-help" />
</template>
{{ theme.tip }}
</VbenTooltip>
</div>
</div>
</template>
</div>
</template>

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
import ToggleItem from '../toggle-item.vue';
defineOptions({
name: 'PreferenceNavigationConfig',
});
defineProps<{ disabled?: boolean; disabledNavigationSplit?: boolean }>();
const navigationStyleType = defineModel<string>('navigationStyleType');
const navigationSplit = defineModel<boolean>('navigationSplit');
const navigationAccordion = defineModel<boolean>('navigationAccordion');
const stylesItems: SelectListItem[] = [
{ label: $t('preferences.rounded'), value: 'rounded' },
{ label: $t('preferences.plain'), value: 'plain' },
];
</script>
<template>
<ToggleItem
v-model="navigationStyleType"
:disabled="disabled"
:items="stylesItems"
>
{{ $t('preferences.navigation-style') }}
</ToggleItem>
<SwitchItem
v-model="navigationSplit"
:disabled="disabledNavigationSplit || disabled"
>
{{ $t('preferences.navigation-split') }}
<template #tip>
{{ $t('preferences.navigation-split-tip') }}
</template>
</SwitchItem>
<SwitchItem v-model="navigationAccordion" :disabled="disabled">
{{ $t('preferences.navigation-accordion') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
defineProps<{ disabled: boolean }>();
const sidebarEnable = defineModel<boolean>('sidebarEnable');
const sidebarCollapsedShowTitle = defineModel<boolean>(
'sidebarCollapsedShowTitle',
);
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
</script>
<template>
<SwitchItem v-model="sidebarEnable" :disabled="disabled">
{{ $t('preferences.side-visible') }}
</SwitchItem>
<SwitchItem v-model="sidebarCollapsed" :disabled="!sidebarEnable || disabled">
{{ $t('preferences.collapse') }}
</SwitchItem>
<SwitchItem
v-model="sidebarCollapsedShowTitle"
:disabled="!sidebarEnable || disabled"
>
{{ $t('preferences.collapse-show-title') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceTabsConfig',
});
defineProps<{ disabled?: boolean }>();
const tabbarEnable = defineModel<boolean>('tabbarEnable');
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
</script>
<template>
<SwitchItem v-model="tabbarEnable" :disabled="disabled">
{{ $t('preferences.tabs-visible') }}
</SwitchItem>
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
{{ $t('preferences.tabs-icon') }}
</SwitchItem>
<!-- <SwitchItem v-model="sideCollapseShowTitle" :disabled="!tabsVisible">
{{ $t('preferences.collapse-show-title') }}
</SwitchItem> -->
</template>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { useSlots } from 'vue';
import { MdiQuestionMarkCircleOutline } from '@vben-core/iconify';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
VbenTooltip,
} from '@vben-core/shadcn-ui';
defineOptions({
name: 'PreferenceSelectItem',
});
withDefaults(
defineProps<{
disabled?: boolean;
items?: SelectListItem[];
placeholder?: string;
}>(),
{
disabled: false,
placeholder: '',
items: () => [],
},
);
const selectValue = defineModel<string>();
const slots = useSlots();
</script>
<template>
<div
:class="{
'hover:bg-accent': !slots.tip,
'pointer-events-none opacity-50': disabled,
}"
class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1"
>
<span class="flex items-center text-sm">
<slot></slot>
<VbenTooltip v-if="slots.tip" side="bottom">
<template #trigger>
<MdiQuestionMarkCircleOutline class="ml-1 cursor-help" />
</template>
<slot name="tip"></slot>
</VbenTooltip>
</span>
<Select v-model="selectValue">
<SelectTrigger class="h-7 w-[140px]">
<SelectValue :placeholder="placeholder" />
</SelectTrigger>
<SelectContent>
<template v-for="item in items" :key="item.value">
<SelectItem :value="item.value"> {{ item.label }} </SelectItem>
</template>
</SelectContent>
</Select>
</div>
</template>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '@vben/locales';
import { isWindowsOs } from '@vben-core/toolkit';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceGeneralConfig',
});
const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
const shortcutKeysGlobalSearch = defineModel<boolean>(
'shortcutKeysGlobalSearch',
);
const shortcutKeysLogout = defineModel<boolean>('shortcutKeysLogout');
const shortcutKeysPreferences = defineModel<boolean>('shortcutKeysPreferences');
const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
</script>
<template>
<SwitchItem v-model="shortcutKeysEnable">
{{ $t('preferences.shortcut-keys.title') }}
</SwitchItem>
<SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysGlobalSearch">
{{ $t('preferences.shortcut-keys.search') }}
<template #shortcut>
{{ isWindowsOs() ? 'Ctrl' : '⌘' }}
<kbd> K </kbd>
</template>
</SwitchItem>
<SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysLogout">
{{ $t('preferences.shortcut-keys.logout') }}
<template #shortcut> {{ altView }} Q </template>
</SwitchItem>
<SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysPreferences">
{{ $t('preferences.shortcut-keys.preferences') }}
<template #shortcut> {{ altView }} , </template>
</SwitchItem>
</template>

View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import { useSlots } from 'vue';
import { MdiQuestionMarkCircleOutline } from '@vben-core/iconify';
import { Switch, VbenTooltip } from '@vben-core/shadcn-ui';
defineOptions({
name: 'PreferenceSwitchItem',
});
withDefaults(defineProps<{ disabled?: boolean }>(), {
disabled: false,
});
const checked = defineModel<boolean>();
const slots = useSlots();
function handleClick() {
checked.value = !checked.value;
}
</script>
<template>
<div
:class="{
'pointer-events-none opacity-50': disabled,
}"
class="hover:bg-accent my-1 flex w-full items-center justify-between rounded-md px-2 py-2"
@click="handleClick"
>
<span class="flex items-center text-sm">
<slot></slot>
<VbenTooltip v-if="slots.tip" side="bottom">
<template #trigger>
<MdiQuestionMarkCircleOutline class="ml-1 cursor-help" />
</template>
<slot name="tip"></slot>
</VbenTooltip>
</span>
<span v-if="$slots.shortcut" class="ml-auto mr-2 text-xs opacity-60">
<slot name="shortcut"></slot>
</span>
<Switch v-model:checked="checked" @click.stop />
</div>
</template>

View File

@@ -0,0 +1,137 @@
<script setup lang="ts">
import type { BuiltinThemeType } from '@vben/types';
import { computed, ref } from 'vue';
import { $t } from '@vben/locales';
import { TinyColor, convertToHsl } from '@vben-core/colorful';
import { MdiEditBoxOutline } from '@vben-core/iconify';
import {
BUILT_IN_THEME_PRESETS,
type BuiltinThemePreset,
} from '@vben-core/preferences';
defineOptions({
name: 'PreferenceBuiltinTheme',
});
const props = defineProps<{ isDark: boolean }>();
const colorInput = ref();
const modelValue = defineModel<BuiltinThemeType>({ default: 'default' });
const themeColorPrimary = defineModel<string>('themeColorPrimary');
const inputValue = computed(() => {
return new TinyColor(themeColorPrimary.value).toHexString();
});
function typeView(name: BuiltinThemeType) {
switch (name) {
case 'default': {
return $t('preferences.theme.default');
}
case 'violet': {
return $t('preferences.theme.violet');
}
case 'pink': {
return $t('preferences.theme.pink');
}
case 'rose': {
return $t('preferences.theme.rose');
}
case 'sky-blue': {
return $t('preferences.theme.sky-blue');
}
case 'deep-blue': {
return $t('preferences.theme.deep-blue');
}
case 'green': {
return $t('preferences.theme.green');
}
case 'deep-green': {
return $t('preferences.theme.deep-green');
}
case 'orange': {
return $t('preferences.theme.orange');
}
case 'yellow': {
return $t('preferences.theme.yellow');
}
case 'zinc': {
return $t('preferences.theme.zinc');
}
case 'neutral': {
return $t('preferences.theme.neutral');
}
case 'slate': {
return $t('preferences.theme.slate');
}
case 'gray': {
return $t('preferences.theme.gray');
}
case 'custom': {
return $t('preferences.theme.custom');
}
}
}
function handleSelect(theme: BuiltinThemePreset) {
modelValue.value = theme.type;
const primaryColor = props.isDark
? theme.darkPrimaryColor || theme.primaryColor
: theme.primaryColor;
themeColorPrimary.value = primaryColor || theme.color;
}
function handleInputChange(e: Event) {
const target = e.target as HTMLInputElement;
themeColorPrimary.value = convertToHsl(target.value);
}
function selectColor() {
colorInput.value?.[0]?.click?.();
}
</script>
<template>
<div class="flex w-full flex-wrap justify-between">
<template v-for="theme in BUILT_IN_THEME_PRESETS" :key="theme.type">
<div class="flex cursor-pointer flex-col" @click="handleSelect(theme)">
<div
:class="{
'outline-box-active': theme.type === modelValue,
}"
class="outline-box flex-center group cursor-pointer"
>
<template v-if="theme.type !== 'custom'">
<div
:style="{ backgroundColor: theme.color }"
class="mx-10 my-2 size-5 rounded-md"
></div>
</template>
<template v-else>
<div class="size-full px-10 py-2" @click.stop="selectColor">
<div class="flex-center relative size-5 rounded-sm">
<MdiEditBoxOutline
class="absolute z-10 size-5 opacity-60 group-hover:opacity-100"
/>
<input
ref="colorInput"
:value="inputValue"
class="absolute inset-0 opacity-0"
type="color"
@input="handleInputChange"
/>
</div>
</div>
</template>
</div>
<div class="text-muted-foreground my-2 text-center text-xs">
{{ typeView(theme.type) }}
</div>
</div>
</template>
</div>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceColorMode',
});
const appColorWeakMode = defineModel<boolean>('appColorWeakMode', {
default: false,
});
const appColorGrayMode = defineModel<boolean>('appColorGrayMode', {
default: false,
});
</script>
<template>
<SwitchItem v-model="appColorWeakMode">
{{ $t('preferences.weak-mode') }}
</SwitchItem>
<SwitchItem v-model="appColorGrayMode">
{{ $t('preferences.gray-mode') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import { ToggleGroup, ToggleGroupItem } from '@vben-core/shadcn-ui';
defineOptions({
name: 'PreferenceColorMode',
});
const modelValue = defineModel<string | undefined>('themeRadius', {
default: '0.5',
});
const items = [
{ label: '0', value: '0' },
{ label: '0.25', value: '0.25' },
{ label: '0.5', value: '0.5' },
{ label: '0.75', value: '0.75' },
{ label: '1', value: '1' },
];
</script>
<template>
<ToggleGroup
v-model="modelValue"
class="gap-2"
size="sm"
type="single"
variant="outline"
>
<template v-for="item in items" :key="item.value">
<ToggleGroupItem
:value="item.value"
class="data-[state=on]:bg-primary data-[state=on]:text-primary-foreground h-7 w-16 rounded-sm"
>
{{ item.label }}
</ToggleGroupItem>
</template>
</ToggleGroup>
</template>

View File

@@ -0,0 +1,85 @@
<script setup lang="ts">
import type { ThemeModeType } from '@vben-core/preferences';
import type { Component } from 'vue';
import { $t } from '@vben/locales';
import {
IcRoundMotionPhotosAuto,
IcRoundWbSunny,
MdiMoonAndStars,
} from '@vben-core/iconify';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceTheme',
});
const modelValue = defineModel<string>({ default: 'auto' });
const appSemiDarkMenu = defineModel<boolean>('appSemiDarkMenu', {
default: true,
});
const THEME_PRESET: Array<{ icon: Component; name: ThemeModeType }> = [
{
icon: IcRoundWbSunny,
name: 'light',
},
{
icon: MdiMoonAndStars,
name: 'dark',
},
{
icon: IcRoundMotionPhotosAuto,
name: 'auto',
},
];
function activeClass(theme: string): string[] {
return theme === modelValue.value ? ['outline-box-active'] : [];
}
function nameView(name: string) {
switch (name) {
case 'light': {
return $t('preferences.light');
}
case 'dark': {
return $t('preferences.dark');
}
case 'auto': {
return $t('preferences.follow-system');
}
}
}
</script>
<template>
<div class="flex w-full flex-wrap justify-between">
<template v-for="theme in THEME_PRESET" :key="theme.name">
<div
class="flex cursor-pointer flex-col"
@click="modelValue = theme.name"
>
<div
:class="activeClass(theme.name)"
class="outline-box flex-center py-4"
>
<component :is="theme.icon" class="mx-9 size-5" />
</div>
<div class="text-muted-foreground mt-2 text-center text-xs">
{{ nameView(theme.name) }}
</div>
</div>
</template>
<SwitchItem
v-model="appSemiDarkMenu"
:disabled="modelValue !== 'light'"
class="mt-6"
>
{{ $t('preferences.dark-menu') }}
</SwitchItem>
</div>
</template>

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { ToggleGroup, ToggleGroupItem } from '@vben-core/shadcn-ui';
defineOptions({
name: 'PreferenceToggleItem',
});
withDefaults(defineProps<{ disabled?: boolean; items: SelectListItem[] }>(), {
disabled: false,
items: () => [],
});
const modelValue = defineModel<string>();
</script>
<template>
<div
:class="{
'pointer-events-none opacity-50': disabled,
}"
class="hover:bg-accent flex w-full items-center justify-between rounded-md px-2 py-2"
disabled
>
<span class="text-sm"><slot></slot></span>
<ToggleGroup
v-model="modelValue"
class="gap-2"
size="sm"
type="single"
variant="outline"
>
<template v-for="item in items" :key="item.value">
<ToggleGroupItem
:value="item.value"
class="data-[state=on]:bg-primary data-[state=on]:text-primary-foreground h-7 rounded-sm"
>
{{ item.label }}
</ToggleGroupItem>
</template>
</ToggleGroup>
</div>
</template>