feat: InspiraUI
Some checks failed
/ surge (push) Successful in 2m41s
/ build-and-deploy-to-vercel (push) Successful in 2m59s
/ lint-build-and-check (push) Has been cancelled
/ playwright (push) Has been cancelled

This commit is contained in:
严浩
2025-04-01 11:55:16 +08:00
parent db2c210a09
commit 8f2a77702b
36 changed files with 1053 additions and 372 deletions

View File

@ -0,0 +1,76 @@
<template>
<div
ref="dockRef"
:class="
cn(
'supports-backdrop-blur:bg-white/10 supports-backdrop-blur:dark:bg-black/10 mx-auto mt-8 flex h-[58px] w-max rounded-2xl border p-2 backdrop-blur-md transition-all gap-4',
orientation === 'vertical' && 'flex-col w-[58px] h-max',
props.class,
dockClass,
)
"
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { DataOrientation, Direction } from "./types";
import {
MOUSE_X_INJECTION_KEY,
MOUSE_Y_INJECTION_KEY,
MAGNIFICATION_INJECTION_KEY,
DISTANCE_INJECTION_KEY,
ORIENTATION_INJECTION_KEY,
} from "./injectionKeys";
import { cn } from "@/shadcn/lib/utils";
import type { HTMLAttributes } from "vue";
interface DockProps {
class?: HTMLAttributes["class"];
magnification?: number;
distance?: number;
direction?: Direction;
orientation?: DataOrientation;
}
const props = withDefaults(defineProps<DockProps>(), {
magnification: 60,
distance: 140,
direction: "middle",
orientation: "horizontal",
});
const dockRef = ref<HTMLElement | null>(null);
const mouseX = ref(Infinity);
const mouseY = ref(Infinity);
const magnification = computed(() => props.magnification);
const distance = computed(() => props.distance);
const dockClass = computed(() => ({
"items-start": props.direction === "top",
"items-center": props.direction === "middle",
"items-end": props.direction === "bottom",
}));
function onMouseMove(e: MouseEvent) {
requestAnimationFrame(() => {
mouseX.value = e.pageX;
mouseY.value = e.pageY;
});
}
function onMouseLeave() {
requestAnimationFrame(() => {
mouseX.value = Infinity;
mouseY.value = Infinity;
});
}
provide(MOUSE_X_INJECTION_KEY, mouseX);
provide(MOUSE_Y_INJECTION_KEY, mouseY);
provide(ORIENTATION_INJECTION_KEY, props.orientation);
provide(MAGNIFICATION_INJECTION_KEY, magnification);
provide(DISTANCE_INJECTION_KEY, distance);
</script>

View File

@ -0,0 +1,61 @@
<template>
<div
ref="iconRef"
class="flex aspect-square cursor-pointer items-center justify-center rounded-full transition-all duration-200 ease-out"
:style="{
width: `${iconWidth}px`,
height: `${iconWidth}px`,
}"
:hovered="{
marginLeft: margin,
marginRight: margin,
}"
>
<slot />
</div>
</template>
<script setup lang="ts">
import {
MOUSE_X_INJECTION_KEY,
MOUSE_Y_INJECTION_KEY,
MAGNIFICATION_INJECTION_KEY,
DISTANCE_INJECTION_KEY,
ORIENTATION_INJECTION_KEY,
} from "./injectionKeys";
const iconRef = ref<HTMLDivElement | null>(null);
const mouseX = inject(MOUSE_X_INJECTION_KEY, ref(Infinity));
const mouseY = inject(MOUSE_Y_INJECTION_KEY, ref(Infinity));
const distance = inject(DISTANCE_INJECTION_KEY);
const orientation = inject(ORIENTATION_INJECTION_KEY, "vertical");
const magnification = inject(MAGNIFICATION_INJECTION_KEY);
const isVertical = computed(() => orientation === "vertical");
const margin = ref(0);
function calculateDistance(val: number) {
if (isVertical.value) {
const bounds = iconRef.value?.getBoundingClientRect() || {
y: 0,
height: 0,
};
return val - bounds.y - bounds.height / 2;
}
const bounds = iconRef.value?.getBoundingClientRect() || { x: 0, width: 0 };
return val - bounds.x - bounds.width / 2;
}
const iconWidth = computed(() => {
const distanceCalc = isVertical.value
? calculateDistance(mouseY.value)
: calculateDistance(mouseX.value);
if (!distance?.value || !magnification?.value) return 40;
if (Math.abs(distanceCalc) < distance?.value) {
return (1 - Math.abs(distanceCalc) / distance?.value) * magnification?.value + 40;
}
return 40;
});
</script>

View File

@ -0,0 +1,14 @@
<template>
<div
:class="
cn('relative block bg-secondary', orientation === 'vertical' ? 'w-4/5 h-0.5' : 'h-4/5 w-0.5')
"
></div>
</template>
<script setup lang="ts">
import { cn } from "@/shadcn/lib/utils";
import { ORIENTATION_INJECTION_KEY } from "./injectionKeys";
const orientation = inject(ORIENTATION_INJECTION_KEY, "vertical");
</script>

View File

@ -0,0 +1,5 @@
export { default as Dock } from "./Dock.vue";
export { default as DockIcon } from "./DockIcon.vue";
export { default as DockSeparator } from "./DockSeparator.vue";
export type DataOrientation = "vertical" | "horizontal";

View File

@ -0,0 +1,11 @@
import type { Ref, InjectionKey, ComputedRef } from "vue";
import type { DataOrientation } from "./types";
export const MOUSE_X_INJECTION_KEY = Symbol() as InjectionKey<Ref<number>>;
export const MOUSE_Y_INJECTION_KEY = Symbol() as InjectionKey<Ref<number>>;
export const MAGNIFICATION_INJECTION_KEY = Symbol() as InjectionKey<ComputedRef<number>>;
export const DISTANCE_INJECTION_KEY = Symbol() as InjectionKey<ComputedRef<number>>;
export const ORIENTATION_INJECTION_KEY = Symbol() as InjectionKey<DataOrientation>;

View File

@ -0,0 +1,2 @@
export type DataOrientation = "vertical" | "horizontal";
export type Direction = "top" | "middle" | "bottom";