aboutsummaryrefslogtreecommitdiff
path: root/src/components/SCollapsible.vue
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/SCollapsible.vue')
-rw-r--r--src/components/SCollapsible.vue61
1 files changed, 61 insertions, 0 deletions
diff --git a/src/components/SCollapsible.vue b/src/components/SCollapsible.vue
new file mode 100644
index 0000000..39b4283
--- /dev/null
+++ b/src/components/SCollapsible.vue
@@ -0,0 +1,61 @@
+<template>
+<div :style="styles">
+ <div class="hover:cursor-pointer" @click="onClick">
+ <slot name="summary" :open="open">(Summary)</slot>
+ </div>
+ <transition @enter="onEnter" @after-enter="onAfterEnter" @leave="onLeave" @before-leave="onBeforeLeave">
+ <div v-show="open" :style="contentStyles" class="max-h-0" ref="content">
+ <slot name="content">(Content)</slot>
+ </div>
+ </transition>
+</div>
+</template>
+
+<script setup lang="ts">
+import {ref, computed, watch} from 'vue';
+
+// Props + events
+const props = defineProps({
+ modelValue: {type: Boolean, default: false}, // For using v-model on the component
+});
+const emit = defineEmits(['update:modelValue', 'open']);
+
+// For open status
+const open = ref(false);
+watch(() => props.modelValue, (newVal) => {open.value = newVal})
+function onClick(){
+ open.value = !open.value;
+ emit('update:modelValue', open.value);
+ if (open.value){
+ emit('open');
+ }
+}
+
+// Styles
+const styles = computed(() => ({
+ overflow: open.value ? 'visible' : 'hidden',
+}));
+const contentStyles = computed(() => ({
+ overflow: 'hidden',
+ opacity: open.value ? '1' : '0',
+ transitionProperty: 'max-height, opacity',
+ transitionDuration: '300ms',
+ transitionTimingFunction: 'ease-in-out',
+}));
+
+// Open/close transitions
+function onEnter(el: HTMLDivElement){
+ el.style.maxHeight = el.scrollHeight + 'px';
+}
+function onAfterEnter(el: HTMLDivElement){
+ el.style.maxHeight = 'none';
+ // Allows the content to grow after the transition ends, as the scrollHeight sometimes is too short
+}
+function onBeforeLeave(el: HTMLDivElement){
+ el.style.maxHeight = el.scrollHeight + 'px';
+ el.offsetWidth; // Triggers reflow
+}
+function onLeave(el: HTMLDivElement){
+ el.style.maxHeight = '0';
+}
+</script>