Files
ulitka-app/src/components/ExpansionPanel.vue
T
2026-10-05 10:52:49 +03:00

64 lines
1.5 KiB
Vue

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
interface Props {
title: string
/** localStorage key for the open/closed state; defaults to one derived from the title. */
storageKey?: string
}
const icons = ['mdi-chevron-right', 'mdi-chevron-down']
const { title, storageKey } = defineProps<Props>()
const key = computed(() => `expansion-panel:${storageKey ?? title}`)
function readStoredOpen(): boolean {
try {
return localStorage.getItem(key.value) !== 'closed'
} catch {
// ignore storage errors (e.g. disabled in private mode)
return true
}
}
const isOpen = ref(readStoredOpen())
watch(isOpen, (value) => {
try {
localStorage.setItem(key.value, value ? 'open' : 'closed')
} catch {
// ignore storage errors (e.g. disabled in private mode)
}
})
const icon = computed(() => {
return isOpen.value ? icons[1] : icons[0]
})
const open = () => {
isOpen.value = !isOpen.value
}
</script>
<template>
<div class="d-flex flex-column ga-1">
<div class="d-flex align-center ga-1">
<v-btn density="compact" size="small" :icon="icon" @click="open" />
<div @click="open" class="font-weight-bold text-capitalize text-title-medium cursor-pointer">{{ title }}</div>
</div>
<div v-if="isOpen" class="panel__body ml-6 mt-2">
<slot />
</div>
</div>
</template>
<style scoped lang="scss">
// Lets the body fill the panel when the panel itself is stretched by its parent
.panel__body {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
</style>