monkeeShark/packages/frontend/src/components/MkContainer.vue

241 lines
5.3 KiB
Vue
Raw Normal View History

<template>
<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.scrollable]: scrollable }]">
<header v-if="showHeader" ref="headerEl" :class="$style.header">
2023-01-14 23:30:29 +00:00
<div :class="$style.title">
<span :class="$style.titleIcon"><slot name="icon"></slot></span>
<slot name="header"></slot>
</div>
<div :class="$style.headerSub">
2023-05-19 04:58:09 +00:00
<slot name="func" :buttonStyleClass="$style.headerButton"></slot>
2023-01-14 23:30:29 +00:00
<button v-if="foldable" :class="$style.headerButton" class="_button" @click="() => showBody = !showBody">
<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
<template v-else><i class="ti ti-chevron-down"></i></template>
2020-08-13 14:02:43 +00:00
</button>
</div>
</header>
2022-12-30 04:37:14 +00:00
<Transition
2023-05-19 04:58:09 +00:00
:enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
:leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
:enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
2020-02-08 05:31:51 +00:00
@enter="enter"
2023-05-19 04:58:09 +00:00
@afterEnter="afterEnter"
2020-02-08 05:31:51 +00:00
@leave="leave"
2023-05-19 04:58:09 +00:00
@afterLeave="afterLeave"
2020-02-08 05:31:51 +00:00
>
<div v-show="showBody" ref="contentEl" :class="[$style.content, { [$style.omitted]: omitted }]">
2020-02-08 05:31:51 +00:00
<slot></slot>
2023-01-14 23:30:29 +00:00
<button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
2023-04-01 05:01:57 +00:00
<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
2021-04-16 00:41:56 +00:00
</button>
2020-02-08 05:31:51 +00:00
</div>
2022-12-30 04:37:14 +00:00
</Transition>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
2023-04-01 04:42:40 +00:00
import { defaultStore } from '@/store';
2023-04-01 05:01:57 +00:00
import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
showHeader?: boolean;
thin?: boolean;
naked?: boolean;
foldable?: boolean;
scrollable?: boolean;
expanded?: boolean;
maxHeight?: number | null;
}>(), {
expanded: true,
showHeader: true,
maxHeight: null,
});
const rootEl = shallowRef<HTMLElement>();
const contentEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();
const showBody = ref(props.expanded);
const ignoreOmit = ref(false);
const omitted = ref(false);
function enter(el) {
const elementHeight = el.getBoundingClientRect().height;
el.style.height = 0;
el.offsetHeight; // reflow
el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px';
}
function afterEnter(el) {
el.style.height = null;
}
function leave(el) {
const elementHeight = el.getBoundingClientRect().height;
el.style.height = elementHeight + 'px';
el.offsetHeight; // reflow
el.style.height = 0;
}
function afterLeave(el) {
el.style.height = null;
}
const calcOmit = () => {
if (omitted.value || ignoreOmit.value || props.maxHeight == null) return;
if (!contentEl.value) return;
const height = contentEl.value.offsetHeight;
omitted.value = height > props.maxHeight;
};
const omitObserver = new ResizeObserver((entries, observer) => {
calcOmit();
});
onMounted(() => {
watch(showBody, v => {
if (!rootEl.value) return;
const headerHeight = props.showHeader ? headerEl.value?.offsetHeight ?? 0 : 0;
rootEl.value.style.minHeight = `${headerHeight}px`;
if (v) {
rootEl.value.style.flexBasis = 'auto';
} else {
rootEl.value.style.flexBasis = `${headerHeight}px`;
}
}, {
immediate: true,
});
2021-04-16 00:41:56 +00:00
if (rootEl.value) rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px');
2021-04-16 00:41:56 +00:00
calcOmit();
2021-04-16 00:41:56 +00:00
if (contentEl.value) omitObserver.observe(contentEl.value);
});
onUnmounted(() => {
omitObserver.disconnect();
});
</script>
2023-01-14 23:30:29 +00:00
<style lang="scss" module>
.transition_toggle_enterActive,
.transition_toggle_leaveActive {
2022-12-30 06:40:29 +00:00
overflow-y: clip;
2020-02-08 05:31:51 +00:00
transition: opacity 0.5s, height 0.5s !important;
}
2023-01-14 23:30:29 +00:00
.transition_toggle_enterFrom,
.transition_toggle_leaveTo {
2020-02-08 05:31:51 +00:00
opacity: 0;
}
2023-01-14 23:30:29 +00:00
.root {
position: relative;
2022-07-13 12:41:06 +00:00
overflow: clip;
contain: content;
&.naked {
background: transparent !important;
box-shadow: none !important;
}
&.scrollable {
display: flex;
flex-direction: column;
2021-04-16 00:41:56 +00:00
> .content {
overflow: auto;
}
}
2023-01-14 23:30:29 +00:00
&.thin {
> .header {
> .title {
padding: 8px 10px;
font-size: 0.9em;
}
}
2023-01-14 23:30:29 +00:00
}
}
2023-01-14 23:30:29 +00:00
.header {
position: sticky;
top: var(--stickyTop, 0px);
left: 0;
color: var(--panelHeaderFg);
background: var(--panelHeaderBg);
border-bottom: solid 0.5px var(--panelHeaderDivider);
2023-01-14 23:30:29 +00:00
z-index: 2;
line-height: 1.4em;
}
2020-08-13 14:02:43 +00:00
2023-01-14 23:30:29 +00:00
.title {
margin: 0;
padding: 12px 16px;
2023-01-14 23:30:29 +00:00
&:empty {
display: none;
}
}
2021-07-19 02:36:35 +00:00
2023-01-14 23:30:29 +00:00
.titleIcon {
margin-right: 6px;
}
2021-04-16 00:41:56 +00:00
2023-01-14 23:30:29 +00:00
.headerSub {
position: absolute;
z-index: 2;
top: 0;
right: 0;
height: 100%;
}
2023-01-14 23:30:29 +00:00
.headerButton {
width: 42px;
height: 100%;
}
2021-04-16 00:41:56 +00:00
2023-01-14 23:30:29 +00:00
.content {
--stickyTop: 0px;
2023-01-14 23:30:29 +00:00
&.omitted {
position: relative;
max-height: var(--maxHeight);
overflow: hidden;
2023-01-14 23:30:29 +00:00
> .fade {
display: block;
position: absolute;
z-index: 10;
bottom: 0;
left: 0;
width: 100%;
height: 64px;
background: linear-gradient(0deg, var(--panel), var(--X15));
2023-01-14 23:30:29 +00:00
> .fadeLabel {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
2023-01-14 23:30:29 +00:00
&:hover {
> .fadeLabel {
background: var(--panelHighlight);
}
}
}
}
}
2020-07-12 02:34:45 +00:00
2023-01-14 23:30:29 +00:00
@container (max-width: 380px) {
.title {
padding: 8px 10px;
font-size: 0.9em;
2020-07-12 02:34:45 +00:00
}
}
</style>