2024-05-19 21:20:42 +08:00
|
|
|
<script lang="ts" setup>
|
2024-06-08 19:49:06 +08:00
|
|
|
import { computed, nextTick } from 'vue';
|
|
|
|
|
2024-05-19 21:20:42 +08:00
|
|
|
import { VbenButton } from '@vben-core/shadcn-ui';
|
|
|
|
import { useNamespace } from '@vben-core/toolkit';
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
/**
|
|
|
|
* 类型
|
|
|
|
*/
|
|
|
|
type?: 'icon' | 'normal';
|
|
|
|
}
|
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
name: 'ThemeToggleButton',
|
|
|
|
});
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
type: 'normal',
|
|
|
|
});
|
|
|
|
|
|
|
|
const isDark = defineModel<boolean>();
|
|
|
|
|
|
|
|
const { b, e, is } = useNamespace('theme-toggle');
|
|
|
|
|
|
|
|
const theme = computed(() => {
|
|
|
|
return isDark.value ? 'light' : 'dark';
|
|
|
|
});
|
|
|
|
|
|
|
|
const bindProps = computed(() => {
|
|
|
|
const type = props.type;
|
|
|
|
|
|
|
|
return type === 'normal'
|
|
|
|
? {
|
2024-06-16 21:17:39 +08:00
|
|
|
variant: 'heavy' as const,
|
2024-05-19 21:20:42 +08:00
|
|
|
}
|
|
|
|
: {
|
|
|
|
class: 'rounded-full',
|
2024-06-16 21:17:39 +08:00
|
|
|
size: 'icon' as const,
|
2024-05-19 21:20:42 +08:00
|
|
|
style: { padding: '6px' },
|
2024-06-16 21:17:39 +08:00
|
|
|
variant: 'icon' as const,
|
2024-05-19 21:20:42 +08:00
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
function toggleTheme(event: MouseEvent) {
|
|
|
|
const isAppearanceTransition =
|
|
|
|
// @ts-expect-error
|
|
|
|
document.startViewTransition &&
|
|
|
|
!window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
|
|
if (!isAppearanceTransition || !event) {
|
|
|
|
isDark.value = !isDark.value;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const x = event.clientX;
|
|
|
|
const y = event.clientY;
|
|
|
|
const endRadius = Math.hypot(
|
|
|
|
Math.max(x, innerWidth - x),
|
|
|
|
Math.max(y, innerHeight - y),
|
|
|
|
);
|
|
|
|
// @ts-expect-error: Transition API
|
|
|
|
const transition = document.startViewTransition(async () => {
|
|
|
|
isDark.value = !isDark.value;
|
|
|
|
await nextTick();
|
|
|
|
});
|
|
|
|
transition.ready.then(() => {
|
|
|
|
const clipPath = [
|
|
|
|
`circle(0px at ${x}px ${y}px)`,
|
|
|
|
`circle(${endRadius}px at ${x}px ${y}px)`,
|
|
|
|
];
|
|
|
|
document.documentElement.animate(
|
|
|
|
{
|
|
|
|
clipPath: isDark.value ? [...clipPath].reverse() : clipPath,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
duration: 450,
|
|
|
|
easing: 'ease-in',
|
|
|
|
pseudoElement: isDark.value
|
|
|
|
? '::view-transition-old(root)'
|
|
|
|
: '::view-transition-new(root)',
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<VbenButton
|
|
|
|
:aria-label="theme"
|
2024-07-08 22:30:19 +08:00
|
|
|
:class="[b(), is(theme), `is-${theme}`]"
|
2024-05-19 21:20:42 +08:00
|
|
|
aria-live="polite"
|
2024-07-08 22:30:19 +08:00
|
|
|
class="theme-toggle cursor-pointer border-none bg-none"
|
2024-05-19 21:20:42 +08:00
|
|
|
v-bind="bindProps"
|
|
|
|
@click.stop="toggleTheme"
|
|
|
|
>
|
|
|
|
<svg aria-hidden="true" height="24" viewBox="0 0 24 24" width="24">
|
|
|
|
<mask
|
|
|
|
id="theme-toggle-moon"
|
|
|
|
:class="e('moon')"
|
2024-07-08 22:30:19 +08:00
|
|
|
class="theme-toggle__moon"
|
2024-06-23 14:21:27 +08:00
|
|
|
fill="hsl(var(--foreground)/80%)"
|
2024-05-19 21:20:42 +08:00
|
|
|
stroke="none"
|
|
|
|
>
|
|
|
|
<rect fill="white" height="100%" width="100%" x="0" y="0" />
|
|
|
|
<circle cx="40" cy="8" fill="black" r="11" />
|
|
|
|
</mask>
|
|
|
|
<circle
|
|
|
|
id="sun"
|
|
|
|
:class="e('sun')"
|
2024-07-08 22:30:19 +08:00
|
|
|
class="theme-toggle__sun"
|
2024-05-19 21:20:42 +08:00
|
|
|
cx="12"
|
|
|
|
cy="12"
|
|
|
|
mask="url(#theme-toggle-moon)"
|
|
|
|
r="11"
|
|
|
|
/>
|
2024-07-08 22:30:19 +08:00
|
|
|
<g :class="e('sun-beams')" class="theme-toggle__sun-beams">
|
2024-05-19 21:20:42 +08:00
|
|
|
<line x1="12" x2="12" y1="1" y2="3" />
|
|
|
|
<line x1="12" x2="12" y1="21" y2="23" />
|
|
|
|
<line x1="4.22" x2="5.64" y1="4.22" y2="5.64" />
|
|
|
|
<line x1="18.36" x2="19.78" y1="18.36" y2="19.78" />
|
|
|
|
<line x1="1" x2="3" y1="12" y2="12" />
|
|
|
|
<line x1="21" x2="23" y1="12" y2="12" />
|
|
|
|
<line x1="4.22" x2="5.64" y1="19.78" y2="18.36" />
|
|
|
|
<line x1="18.36" x2="19.78" y1="5.64" y2="4.22" />
|
|
|
|
</g>
|
|
|
|
</svg>
|
|
|
|
</VbenButton>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2024-07-08 22:30:19 +08:00
|
|
|
.theme-toggle {
|
|
|
|
&__moon {
|
2024-05-19 21:20:42 +08:00
|
|
|
& > circle {
|
|
|
|
transition: transform 0.5s cubic-bezier(0, 0, 0.3, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-08 22:30:19 +08:00
|
|
|
&__sun {
|
2024-06-23 14:21:27 +08:00
|
|
|
fill: hsl(var(--foreground) / 80%);
|
2024-05-19 21:20:42 +08:00
|
|
|
stroke: none;
|
|
|
|
transition: transform 1.6s cubic-bezier(0.25, 0, 0.2, 1);
|
|
|
|
transform-origin: center center;
|
|
|
|
|
|
|
|
&:hover > svg > & {
|
2024-06-23 14:21:27 +08:00
|
|
|
fill: hsl(var(--foreground));
|
2024-05-19 21:20:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-08 22:30:19 +08:00
|
|
|
&__sun-beams {
|
2024-06-23 14:21:27 +08:00
|
|
|
stroke: hsl(var(--foreground) / 80%);
|
2024-05-19 21:20:42 +08:00
|
|
|
stroke-width: 2px;
|
|
|
|
transition:
|
|
|
|
transform 1.6s cubic-bezier(0.5, 1.5, 0.75, 1.25),
|
|
|
|
opacity 0.6s cubic-bezier(0.25, 0, 0.3, 1);
|
|
|
|
transform-origin: center center;
|
|
|
|
|
|
|
|
&:hover > svg > & {
|
2024-06-23 14:21:27 +08:00
|
|
|
stroke: hsl(var(--foreground));
|
2024-05-19 21:20:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-08 22:30:19 +08:00
|
|
|
&.is-light {
|
|
|
|
.theme-toggle__sun {
|
|
|
|
transform: scale(0.5);
|
|
|
|
}
|
2024-05-19 21:20:42 +08:00
|
|
|
|
2024-07-08 22:30:19 +08:00
|
|
|
.theme-toggle__sun-beams {
|
|
|
|
transform: rotateZ(0.25turn);
|
2024-05-19 21:20:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-08 22:30:19 +08:00
|
|
|
&.is-dark {
|
|
|
|
.theme-toggle__moon {
|
|
|
|
& > circle {
|
|
|
|
transform: translateX(-20px);
|
2024-05-19 21:20:42 +08:00
|
|
|
}
|
2024-07-08 22:30:19 +08:00
|
|
|
}
|
2024-05-19 21:20:42 +08:00
|
|
|
|
2024-07-08 22:30:19 +08:00
|
|
|
.theme-toggle__sun-beams {
|
|
|
|
opacity: 0;
|
2024-05-19 21:20:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&:hover > svg {
|
2024-07-08 22:30:19 +08:00
|
|
|
.theme-toggle__sun,
|
|
|
|
.theme-toggle__moon {
|
|
|
|
fill: hsl(var(--foreground));
|
2024-05-19 21:20:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|