-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(v-collapsible): new collapsible component
- Loading branch information
Showing
3 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<template> | ||
<div :class="{'navbar-collapse': navbar}"> | ||
<slot /> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
const props = { | ||
duration: { | ||
type: [Number, Object], | ||
default: 300, | ||
}, | ||
transition: { | ||
type: String, | ||
default: 'ease-in-out', | ||
}, | ||
show: Boolean, | ||
navbar: Boolean, | ||
}; | ||
export default { | ||
name: 'VCollapse', | ||
props, | ||
emits: ['finish'], | ||
data() { | ||
return { | ||
collapsing: false, | ||
heightWatcher: null, | ||
visible: this.show, | ||
el: null, | ||
}; | ||
}, | ||
computed: { | ||
toggleTime() { | ||
return ( | ||
(this.visible ? this.duration.show : this.duration.hide) || | ||
this.duration | ||
); | ||
}, | ||
}, | ||
watch: { | ||
show(val) { | ||
this.visible = val; | ||
}, | ||
visible(val) { | ||
if (this.toggleTime) { | ||
this.collapseController(val); | ||
} else { | ||
this.reset(); | ||
} | ||
}, | ||
}, | ||
mounted() { | ||
this.$el.style.display = this.visible ? '' : 'none'; | ||
}, | ||
beforeUnmount() { | ||
clearTimeout(this.heightWatcher); | ||
}, | ||
methods: { | ||
collapseController(val) { | ||
if (this.collapsing === false) { | ||
val ? this.toggle(true) : this.toggle(false); | ||
this.setFinishTimer(this.toggleTime); | ||
} else { | ||
this.setTransition(); | ||
this.turn(); | ||
const height = Number(this.collapsing.slice(0, -2)); | ||
const current = this.$el.offsetHeight; | ||
const time = (val ? height - current : current) / height; | ||
this.setFinishTimer(this.toggleTime * time); | ||
} | ||
}, | ||
turn() { | ||
if (this.visible) { | ||
this.$el.style.height = this.collapsing; | ||
} else { | ||
this.$el.style.height = 0; | ||
} | ||
}, | ||
toggle(val) { | ||
this.$el.style.display = ''; | ||
this.collapsing = this.$el.scrollHeight + 'px'; | ||
this.$el.style.height = val ? 0 : this.$el.scrollHeight + 'px'; | ||
this.$el.style.overflow = 'hidden'; | ||
this.setTransition(); | ||
const self = this; | ||
setTimeout(() => { | ||
self.$el.style.height = val ? this.collapsing : 0; | ||
}, 0); | ||
}, | ||
setTransition() { | ||
this.$el.style.transition = `all ${this.toggleTime}ms ${this.transition}`; | ||
}, | ||
setFinishTimer(duration) { | ||
clearTimeout(this.heightWatcher); | ||
this.heightWatcher = setTimeout(() => this.reset(), duration); | ||
}, | ||
reset() { | ||
this.collapsing = false; | ||
this.$el.style.display = this.visible ? '' : 'none'; | ||
this.$el.style.height = ''; | ||
this.$el.style.overflow = ''; | ||
this.$el.style.transition = ''; | ||
this.$emit('finish', this.visible); | ||
}, | ||
}, | ||
}; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import {ref} from 'vue'; | ||
import MyCollapsible from './VCollapsible.vue'; | ||
|
||
export default { | ||
title: 'Components/Collapsible', | ||
component: MyCollapsible, | ||
args: { | ||
modelValue: false, | ||
title: 'Title', | ||
headerClass: 'font-bold', | ||
activeClass: '', | ||
inactiveClass: '', | ||
wrapperClass: 'mb-5', | ||
activatorClass: '', | ||
panelClass: 'px-4 pb-4', | ||
}, | ||
}; | ||
|
||
const Template = (args) => ({ | ||
// Components used in your story `template` are defined in the `components` object | ||
components: {MyCollapsible}, | ||
// The story's `args` need to be mapped into the template through the `setup()` method | ||
setup() { | ||
const isOpen = ref(false); | ||
|
||
return {args, isOpen}; | ||
}, | ||
// And then the `args` are bound to your component with `v-bind="args"` | ||
template: `<MyCollapsible v-model="isOpen" v-bind='args'> | ||
Anim eiusmod ea nostrud do incididunt consequat duis adipisicing reprehenderit nisi. Minim mollit eiusmod incididunt fugiat ipsum velit ut consequat est consectetur adipisicing. Nulla duis anim velit magna aute nisi elit nulla deserunt. Fugiat consequat ut magna eiusmod sit incididunt qui. Incididunt velit fugiat sunt sint amet magna est laborum excepteur. Aute aliqua nisi est nulla voluptate enim qui amet labore et consectetur. Est pariatur qui amet cupidatat magna est adipisicing quis ea ea. | ||
</MyCollapsible> | ||
`, | ||
}); | ||
|
||
export const Default = Template.bind({}); | ||
Default.args = {}; | ||
|
||
export const AutoOpen = Template.bind({}); | ||
AutoOpen.args = { | ||
modelValue: true, | ||
}; | ||
|
||
export const CustomClasses = Template.bind({}); | ||
CustomClasses.args = { | ||
headerClass: '', | ||
activeClass: 'font-bold text-red-500 bg-red-200 rounded-t-lg', | ||
inactiveClass: 'text-red-500 bg-red-50', | ||
wrapperClass: 'rounded-lg', | ||
activatorClass: 'hover:bg-red-200 hover:text-red-500', | ||
panelClass: 'border p-4 rounded-b-lg', | ||
}; | ||
|
||
export const Group = (args) => ({ | ||
// Components used in your story `template` are defined in the `components` object | ||
components: {MyCollapsible}, | ||
// The story's `args` need to be mapped into the template through the `setup()` method | ||
setup() { | ||
return {args}; | ||
}, | ||
// And then the `args` are bound to your component with `v-bind="args"` | ||
template: `<MyCollapsible v-for="i in 5" :key="i" v-bind='args'> | ||
Anim eiusmod ea nostrud do incididunt consequat duis adipisicing reprehenderit nisi. Minim mollit eiusmod incididunt fugiat ipsum velit ut consequat est consectetur adipisicing. Nulla duis anim velit magna aute nisi elit nulla deserunt. Fugiat consequat ut magna eiusmod sit incididunt qui. Incididunt velit fugiat sunt sint amet magna est laborum excepteur. Aute aliqua nisi est nulla voluptate enim qui amet labore et consectetur. Est pariatur qui amet cupidatat magna est adipisicing quis ea ea. | ||
</MyCollapsible>`, | ||
}); | ||
|
||
// export const Collapsible = (args) => ({ | ||
// components: {MyCollapsible}, | ||
// setup() { | ||
// return {args}; | ||
// }, | ||
// template: `<div class="container mx-auto"><MyCollapsible v-bind="args" /></div>`, | ||
// }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<template> | ||
<div> | ||
<div | ||
class=" | ||
py-3 | ||
w-full | ||
flex | ||
justify-between | ||
items-center | ||
px-4 | ||
gap-x-4 | ||
transition | ||
duration-300 | ||
group | ||
cursor-pointer | ||
font-bold | ||
" | ||
:class="[activatorClass, isOpen ? activeClass : inactiveClass]" | ||
@click="toggle" | ||
> | ||
<slot name="header"> | ||
{{ title }} | ||
</slot> | ||
|
||
<slot name="icon"> | ||
<ChevronDownIcon | ||
class="w-5 h-5" | ||
:class="[isOpen ? 'transform rotate-180' : '']" | ||
/> | ||
</slot> | ||
</div> | ||
<v-collapse :show="isOpen"> | ||
<div :class="panelClass"> | ||
<slot /> | ||
</div> | ||
</v-collapse> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import {ref, watch, toRefs} from 'vue'; | ||
import {ChevronDownIcon} from '@heroicons/vue/outline'; | ||
import VCollapse from './VCollapse.vue'; | ||
const props = defineProps({ | ||
modelValue: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
defaultOpen: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
title: { | ||
type: String, | ||
default: '', | ||
}, | ||
headerClass: { | ||
type: String, | ||
default: 'font-bold', | ||
}, | ||
activeClass: { | ||
type: String, | ||
default: '', | ||
}, | ||
wrapperClass: { | ||
type: String, | ||
default: 'mb-5', | ||
}, | ||
inactiveClass: { | ||
type: String, | ||
default: '', | ||
}, | ||
activatorClass: { | ||
type: String, | ||
default: '', | ||
}, | ||
panelClass: { | ||
type: String, | ||
default: 'px-4 pb-4', | ||
}, | ||
}); | ||
const { | ||
modelValue, | ||
defaultOpen, | ||
title, | ||
headerClass, | ||
activeClass, | ||
wrapperClass, | ||
inactiveClass, | ||
activatorClass, | ||
panelClass, | ||
} = toRefs(props); | ||
const emit = defineEmits(['update:modelValue']); | ||
const panel = ref(null); | ||
const isOpen = ref(modelValue.value); | ||
const toggle = () => (isOpen.value = !isOpen.value); | ||
watch( | ||
modelValue, | ||
(value) => { | ||
isOpen.value = value; | ||
}, | ||
{immediate: true}, | ||
); | ||
watch(isOpen, (value) => { | ||
emit('update:modelValue', value); | ||
}); | ||
</script> | ||
|
||
<style scoped></style> |