feat: InspiraUI
This commit is contained in:
76
src/components/InspiraUI/dock/Dock.vue
Normal file
76
src/components/InspiraUI/dock/Dock.vue
Normal 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>
|
61
src/components/InspiraUI/dock/DockIcon.vue
Normal file
61
src/components/InspiraUI/dock/DockIcon.vue
Normal 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>
|
14
src/components/InspiraUI/dock/DockSeparator.vue
Normal file
14
src/components/InspiraUI/dock/DockSeparator.vue
Normal 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>
|
5
src/components/InspiraUI/dock/index.ts
Normal file
5
src/components/InspiraUI/dock/index.ts
Normal 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";
|
11
src/components/InspiraUI/dock/injectionKeys.ts
Normal file
11
src/components/InspiraUI/dock/injectionKeys.ts
Normal 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>;
|
2
src/components/InspiraUI/dock/types.ts
Normal file
2
src/components/InspiraUI/dock/types.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export type DataOrientation = "vertical" | "horizontal";
|
||||
export type Direction = "top" | "middle" | "bottom";
|
Reference in New Issue
Block a user