* feat: add i18n * refactor/feat: workflow attributes type * refactor: workflow => gray stat card * refactor: select menu with search => separator * feat: workflow => workflow step properties * fix: workflow type * fix: dialog properties component => model data * fix: form flow => prevent toggle expansion with keyboard * refactor: workflow step data & change status * fix: form flow properties btn * refactor: side menu => hide sub index * feat: workflow => avatar & status on table * refactor: workflow => drawer id and dialog id * feat: workflow => card * fix: agencies => format address
157 lines
3.8 KiB
Vue
157 lines
3.8 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;
|
|
hideSubIndex?: boolean;
|
|
tab?: string;
|
|
useBtn?: boolean;
|
|
};
|
|
|
|
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 row items-center justify-between no-wrap"
|
|
:class="{
|
|
'side-menu__active': activeMenu === v.anchor,
|
|
'side-menu__sub': v.sub || false,
|
|
}"
|
|
@click="handleClick(v)"
|
|
>
|
|
<span
|
|
class="row no-wrap items-center"
|
|
:class="{ 'app-text-muted': v.sub && activeMenu !== v.anchor }"
|
|
>
|
|
<div v-if="v.sub" class="circle-2 q-mr-md"></div>
|
|
<div
|
|
v-if="v.sub && !v.hideSubIndex"
|
|
class="surface-tab circle flex justify-center q-mr-md"
|
|
>
|
|
{{ menu.filter((v) => v.sub === true).indexOf(v) + 1 }}
|
|
</div>
|
|
{{ v.name }}
|
|
</span>
|
|
<slot v-if="v.useBtn" :name="`btn-${v.anchor}`"></slot>
|
|
</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;
|
|
}
|
|
}
|
|
}
|
|
|
|
.circle {
|
|
width: 15px;
|
|
height: 15px;
|
|
font-size: 12px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.circle-2 {
|
|
background: var(--surface-tab);
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: 50%;
|
|
}
|
|
</style>
|