jws-frontend/src/components/SideMenu.vue
2024-08-19 17:19:33 +07:00

128 lines
3 KiB
Vue

<script lang="ts" setup>
import { onUnmounted } from 'vue';
import { ref } from 'vue';
import { onMounted } from 'vue';
type Menu = {
element?: HTMLElement;
anchor: string;
name: string;
sub?: boolean;
tab?: string;
};
const props = defineProps<{
menu: Menu[];
scrollElement?: Element | HTMLElement | string;
foreground?: string;
background?: string;
active?: {
foreground?: string;
background?: string;
};
}>();
const currentScrollElement = ref<Element | HTMLElement>();
function handleClick(menu: Menu) {
const element = document.getElementById(menu.anchor);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}
const activeMenu = ref(props.menu.at(0)?.anchor || '');
function onScroll() {
let current = activeMenu.value;
if (!currentScrollElement.value) return;
const container = currentScrollElement.value;
props.menu.forEach((v) => {
if (!v.element) v.element = document.getElementById(v.anchor) || undefined;
if (!v.element) return;
const containerRect = container.getBoundingClientRect();
const elementRect = v.element.getBoundingClientRect();
if (containerRect.y + containerRect.height / 5 >= elementRect.y) {
current = v.anchor;
}
});
activeMenu.value = current;
}
onMounted(() => {
if (typeof props.scrollElement === 'string' || !props.scrollElement) {
const element = document.querySelector(props.scrollElement || 'body');
if (element) currentScrollElement.value = element;
} else {
currentScrollElement.value = props.scrollElement;
}
currentScrollElement.value?.addEventListener('scroll', onScroll);
});
onUnmounted(() => {
currentScrollElement.value?.removeEventListener('scroll', onScroll);
});
</script>
<template>
<div class="side-menu__container">
<template v-for="v in menu" :key="v.anchor">
<span
:style="{
'--side-menu__fg': activeMenu !== v.anchor ? foreground : undefined,
'--side-menu__bg': activeMenu !== v.anchor ? background : undefined,
'--side-menu__fg-active': active?.foreground,
'--side-menu__bg-active': active?.background,
}"
class="side-menu__item"
:class="{
'side-menu__active': activeMenu === v.anchor,
'side-menu__sub': v.sub || false,
}"
@click="handleClick(v)"
>
{{ v.name }}
</span>
</template>
</div>
</template>
<style scoped>
.side-menu__container {
display: flex;
flex-direction: column;
gap: var(--size-1);
max-width: 100%;
& > .side-menu__item {
transition: 0.3s background ease-in-out;
color: var(--side-menu__fg, inherit);
background: var(--side-menu__bg, inherit);
padding: var(--size-2) var(--size-4);
border-radius: var(--radius-2);
cursor: pointer;
&.side-menu__sub {
margin-left: 1rem;
}
&.side-menu__active {
--side-menu__fg: var(--side-menu__fg-active, inherit);
--side-menu__bg: var(--side-menu__bg-active, inherit);
font-weight: 600;
}
}
}
</style>