Remove unused Modal, Collapsible components and events page
This commit is contained in:
parent
e5e4dca8e4
commit
44c3397d85
|
|
@ -1,313 +0,0 @@
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'CollapseTransition',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: 'collapse',
|
|
||||||
},
|
|
||||||
dimension: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: 'height',
|
|
||||||
validator: (value) => {
|
|
||||||
return ['height', 'width'].includes(value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
duration: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 300,
|
|
||||||
},
|
|
||||||
easing: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: 'ease-in-out',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['before-appear', 'appear', 'after-appear', 'appear-cancelled', 'before-enter', 'enter', 'after-enter', 'enter-cancelled', 'before-leave', 'leave', 'after-leave', 'leave-cancelled'],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
cachedStyles: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
transition() {
|
|
||||||
const transitions = []
|
|
||||||
|
|
||||||
Object.keys(this.cachedStyles).forEach((key) => {
|
|
||||||
transitions.push(
|
|
||||||
`${this.convertToCssProperty(key)} ${this.duration}ms ${this.easing}`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return transitions.join(', ')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
dimension() {
|
|
||||||
this.clearCachedDimensions()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
beforeAppear(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('before-appear', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
appear(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('appear', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
afterAppear(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('after-appear', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
appearCancelled(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('appear-cancelled', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeEnter(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('before-enter', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
enter(el, done) {
|
|
||||||
// Because width and height may be 'auto',
|
|
||||||
// first detect and cache the dimensions
|
|
||||||
this.detectAndCacheDimensions(el)
|
|
||||||
|
|
||||||
// The order of applying styles is important:
|
|
||||||
// - 1. Set styles for state before transition
|
|
||||||
// - 2. Force repaint
|
|
||||||
// - 3. Add transition style
|
|
||||||
// - 4. Set styles for state after transition
|
|
||||||
// If the order is not right and you open any 2nd level submenu
|
|
||||||
// for the first time, the transition will not work.
|
|
||||||
this.setClosedDimensions(el)
|
|
||||||
this.hideOverflow(el)
|
|
||||||
this.forceRepaint(el)
|
|
||||||
this.setTransition(el)
|
|
||||||
this.setOpenedDimensions(el)
|
|
||||||
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('enter', el, done)
|
|
||||||
|
|
||||||
// Call done() when the transition ends
|
|
||||||
// to trigger the @after-enter event.
|
|
||||||
setTimeout(done, this.duration)
|
|
||||||
},
|
|
||||||
|
|
||||||
afterEnter(el) {
|
|
||||||
// Clean up inline styles
|
|
||||||
this.unsetOverflow(el)
|
|
||||||
this.unsetTransition(el)
|
|
||||||
this.unsetDimensions(el)
|
|
||||||
this.clearCachedDimensions()
|
|
||||||
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('after-enter', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
enterCancelled(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('enter-cancelled', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeLeave(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('before-leave', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
leave(el, done) {
|
|
||||||
// For some reason, @leave triggered when starting
|
|
||||||
// from open state on page load. So for safety,
|
|
||||||
// check if the dimensions have been cached.
|
|
||||||
this.detectAndCacheDimensions(el)
|
|
||||||
|
|
||||||
// The order of applying styles is less important
|
|
||||||
// than in the enter phase, as long as we repaint
|
|
||||||
// before setting the closed dimensions.
|
|
||||||
// But it is probably best to use the same
|
|
||||||
// order as the enter phase.
|
|
||||||
this.setOpenedDimensions(el)
|
|
||||||
this.hideOverflow(el)
|
|
||||||
this.forceRepaint(el)
|
|
||||||
this.setTransition(el)
|
|
||||||
this.setClosedDimensions(el)
|
|
||||||
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('leave', el, done)
|
|
||||||
|
|
||||||
// Call done() when the transition ends
|
|
||||||
// to trigger the @after-leave event.
|
|
||||||
// This will also cause v-show
|
|
||||||
// to reapply 'display: none'.
|
|
||||||
setTimeout(done, this.duration)
|
|
||||||
},
|
|
||||||
|
|
||||||
afterLeave(el) {
|
|
||||||
// Clean up inline styles
|
|
||||||
this.unsetOverflow(el)
|
|
||||||
this.unsetTransition(el)
|
|
||||||
this.unsetDimensions(el)
|
|
||||||
this.clearCachedDimensions()
|
|
||||||
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('after-leave', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
leaveCancelled(el) {
|
|
||||||
// Emit the event to the parent
|
|
||||||
this.$emit('leave-cancelled', el)
|
|
||||||
},
|
|
||||||
|
|
||||||
detectAndCacheDimensions(el) {
|
|
||||||
// Cache actual dimensions
|
|
||||||
// only once to void invalid values when
|
|
||||||
// triggering during a transition
|
|
||||||
if (this.cachedStyles)
|
|
||||||
return
|
|
||||||
|
|
||||||
const visibility = el.style.visibility
|
|
||||||
const display = el.style.display
|
|
||||||
|
|
||||||
// Trick to get the width and
|
|
||||||
// height of a hidden element
|
|
||||||
el.style.visibility = 'hidden'
|
|
||||||
el.style.display = ''
|
|
||||||
|
|
||||||
this.cachedStyles = this.detectRelevantDimensions(el)
|
|
||||||
|
|
||||||
// Restore any original styling
|
|
||||||
el.style.visibility = visibility
|
|
||||||
el.style.display = display
|
|
||||||
},
|
|
||||||
|
|
||||||
clearCachedDimensions() {
|
|
||||||
this.cachedStyles = null
|
|
||||||
},
|
|
||||||
|
|
||||||
detectRelevantDimensions(el) {
|
|
||||||
// These properties will be transitioned
|
|
||||||
if (this.dimension === 'height') {
|
|
||||||
return {
|
|
||||||
height: `${el.offsetHeight}px`,
|
|
||||||
paddingTop:
|
|
||||||
el.style.paddingTop || this.getCssValue(el, 'padding-top'),
|
|
||||||
paddingBottom:
|
|
||||||
el.style.paddingBottom || this.getCssValue(el, 'padding-bottom'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dimension === 'width') {
|
|
||||||
return {
|
|
||||||
width: `${el.offsetWidth}px`,
|
|
||||||
paddingLeft:
|
|
||||||
el.style.paddingLeft || this.getCssValue(el, 'padding-left'),
|
|
||||||
paddingRight:
|
|
||||||
el.style.paddingRight || this.getCssValue(el, 'padding-right'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
|
|
||||||
setTransition(el) {
|
|
||||||
el.style.transition = this.transition
|
|
||||||
},
|
|
||||||
|
|
||||||
unsetTransition(el) {
|
|
||||||
el.style.transition = ''
|
|
||||||
},
|
|
||||||
|
|
||||||
hideOverflow(el) {
|
|
||||||
el.style.overflow = 'hidden'
|
|
||||||
},
|
|
||||||
|
|
||||||
unsetOverflow(el) {
|
|
||||||
el.style.overflow = ''
|
|
||||||
},
|
|
||||||
|
|
||||||
setClosedDimensions(el) {
|
|
||||||
Object.keys(this.cachedStyles).forEach((key) => {
|
|
||||||
el.style[key] = '0'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
setOpenedDimensions(el) {
|
|
||||||
Object.keys(this.cachedStyles).forEach((key) => {
|
|
||||||
el.style[key] = this.cachedStyles[key]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
unsetDimensions(el) {
|
|
||||||
Object.keys(this.cachedStyles).forEach((key) => {
|
|
||||||
el.style[key] = ''
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
forceRepaint(el) {
|
|
||||||
// Force repaint to make sure the animation is triggered correctly.
|
|
||||||
// Thanks: https://markus.oberlehner.net/blog/transition-to-height-auto-with-vue/
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
getComputedStyle(el)[this.dimension]
|
|
||||||
},
|
|
||||||
|
|
||||||
getCssValue(el, style) {
|
|
||||||
return getComputedStyle(el, null).getPropertyValue(style)
|
|
||||||
},
|
|
||||||
|
|
||||||
convertToCssProperty(style) {
|
|
||||||
// Example: convert 'paddingTop' to 'padding-top'
|
|
||||||
// Thanks: https://gist.github.com/tan-yuki/3450323
|
|
||||||
const upperChars = style.match(/([A-Z])/g)
|
|
||||||
|
|
||||||
if (!upperChars)
|
|
||||||
return style
|
|
||||||
|
|
||||||
for (let i = 0, n = upperChars.length; i < n; i++) {
|
|
||||||
style = style.replace(
|
|
||||||
new RegExp(upperChars[i]),
|
|
||||||
`-${upperChars[i].toLowerCase()}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style.slice(0, 1) === '-')
|
|
||||||
style = style.slice(1)
|
|
||||||
|
|
||||||
return style
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<transition
|
|
||||||
:name="name"
|
|
||||||
@before-appear="beforeAppear"
|
|
||||||
@appear="appear"
|
|
||||||
@after-appear="afterAppear"
|
|
||||||
@appear-cancelled="appearCancelled"
|
|
||||||
@before-enter="beforeEnter"
|
|
||||||
@enter="enter"
|
|
||||||
@after-enter="afterEnter"
|
|
||||||
@enter-cancelled="enterCancelled"
|
|
||||||
@before-leave="beforeLeave"
|
|
||||||
@leave="leave"
|
|
||||||
@after-leave="afterLeave"
|
|
||||||
@leave-cancelled="leaveCancelled"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import type { Story } from '@storybook/vue3'
|
|
||||||
import Collapsible from './Collapsible.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Components/Collapsible',
|
|
||||||
component: Collapsible,
|
|
||||||
args: {
|
|
||||||
modelValue: false,
|
|
||||||
title: 'Item',
|
|
||||||
content: 'lorem ipsum dolor sit amet',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const Template: Story = (args, { argTypes }) => ({
|
|
||||||
components: { Collapsible },
|
|
||||||
setup() {
|
|
||||||
return { args, argTypes }
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<Collapsible v-bind="args"/>
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Default = Template.bind({})
|
|
||||||
Default.args = {}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
|
||||||
import { ref, toRefs, watch } from 'vue'
|
|
||||||
import CollapseTransition from './CollapseTransition.vue'
|
|
||||||
import Modal from '../Modal/Modal.vue';
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
modelValue?: boolean
|
|
||||||
title: string
|
|
||||||
content?: string
|
|
||||||
classes?: {
|
|
||||||
wrapper?: string
|
|
||||||
button?: string
|
|
||||||
title?: string
|
|
||||||
panel?: string
|
|
||||||
}
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
modelValue: false,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const emit = defineEmits([
|
|
||||||
'update:modelValue',
|
|
||||||
'change',
|
|
||||||
'toggle',
|
|
||||||
'open',
|
|
||||||
'close',
|
|
||||||
])
|
|
||||||
|
|
||||||
const { modelValue } = toRefs(props)
|
|
||||||
const isOpen = ref(modelValue.value)
|
|
||||||
|
|
||||||
watch(modelValue, (val) => {
|
|
||||||
isOpen.value = val
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(isOpen, (val) => {
|
|
||||||
emit('update:modelValue', val)
|
|
||||||
emit('change', val)
|
|
||||||
|
|
||||||
if (val)
|
|
||||||
emit('open')
|
|
||||||
else
|
|
||||||
emit('close')
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggle = () => {
|
|
||||||
emit('toggle')
|
|
||||||
isOpen.value = !isOpen.value
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Disclosure v-slot="{ open }" as="div">
|
|
||||||
<DisclosureButton
|
|
||||||
class="
|
|
||||||
flex
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
w-full
|
|
||||||
text-left
|
|
||||||
rounded-lg
|
|
||||||
focus:outline-none
|
|
||||||
focus-visible:ring
|
|
||||||
focus-visible:ring-blue-50
|
|
||||||
focus-visible:ring-opacity-75
|
|
||||||
"
|
|
||||||
:class="classes?.button"
|
|
||||||
type="button"
|
|
||||||
@click="toggle"
|
|
||||||
>
|
|
||||||
<div class="inline-flex w-full">
|
|
||||||
<Icon
|
|
||||||
name="heroicons:chevron-down"
|
|
||||||
:class="isOpen ? 'transform rotate-180' : ''"
|
|
||||||
class="w-5 h-5 text-black"
|
|
||||||
/>
|
|
||||||
<slot name="title"></slot>
|
|
||||||
</div>
|
|
||||||
</DisclosureButton>
|
|
||||||
<CollapseTransition>
|
|
||||||
<div v-show="isOpen">
|
|
||||||
<DisclosurePanel static class="pb-2 text-15" :class="classes?.panel">
|
|
||||||
<slot name="content"></slot>
|
|
||||||
</DisclosurePanel>
|
|
||||||
</div>
|
|
||||||
</CollapseTransition>
|
|
||||||
</Disclosure>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import type { Story } from '@storybook/vue3'
|
|
||||||
import CollapsibleGroup from './CollapsibleGroup.vue'
|
|
||||||
|
|
||||||
const genItems = (length = 5): any[] =>
|
|
||||||
Array.from({ length }, (_, v) => ({
|
|
||||||
title: `Item ${v + 1}`,
|
|
||||||
content: `lorem ipsum ${v + 1}`,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const items = genItems(5)
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Components/CollapsibleGroup',
|
|
||||||
component: CollapsibleGroup,
|
|
||||||
args: {
|
|
||||||
modelValue: false,
|
|
||||||
accordion: false,
|
|
||||||
items,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const Template: Story = (args, { argTypes }) => ({
|
|
||||||
components: { CollapsibleGroup },
|
|
||||||
setup() {
|
|
||||||
return { args, argTypes }
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<CollapsibleGroup v-bind="args"/>
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Default = Template.bind({})
|
|
||||||
Default.args = {}
|
|
||||||
|
|
||||||
export const Accordion = Template.bind({})
|
|
||||||
Accordion.args = {
|
|
||||||
accordion: true,
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
|
||||||
import { ref, toRefs, watch } from 'vue'
|
|
||||||
import Collapsible from './Collapsible.vue'
|
|
||||||
|
|
||||||
interface CollapsibleItem {
|
|
||||||
title: string
|
|
||||||
content: string
|
|
||||||
isOpen?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props
|
|
||||||
= defineProps<{
|
|
||||||
items?: CollapsibleItem[]
|
|
||||||
classes?: {
|
|
||||||
wrapper?: string
|
|
||||||
button?: string
|
|
||||||
title?: string
|
|
||||||
panel?: string
|
|
||||||
}
|
|
||||||
accordion?: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { items } = toRefs(props)
|
|
||||||
|
|
||||||
const children = ref(props.items)
|
|
||||||
|
|
||||||
watch(items, (val) => {
|
|
||||||
children.value = val
|
|
||||||
})
|
|
||||||
|
|
||||||
const onToggle = (item: CollapsibleItem) => {
|
|
||||||
if (props.accordion) {
|
|
||||||
children.value.forEach((child) => {
|
|
||||||
child.isOpen = false
|
|
||||||
})
|
|
||||||
item.isOpen = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="w-full p-2" :class="classes?.wrapper">
|
|
||||||
<slot>
|
|
||||||
<Collapsible
|
|
||||||
v-for="(item, idx) in children"
|
|
||||||
:key="idx"
|
|
||||||
v-bind="item"
|
|
||||||
v-model="item.isOpen"
|
|
||||||
@toggle="onToggle(item)"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="fixed inset-0 bg-black/50 z-15 transition duration-300" />
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogPanel,
|
|
||||||
TransitionChild,
|
|
||||||
TransitionRoot,
|
|
||||||
} from '@headlessui/vue'
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
modelValue?: boolean
|
|
||||||
persistent?: boolean
|
|
||||||
fullscreen?: boolean
|
|
||||||
maxHeight?: string
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
modelValue: false,
|
|
||||||
persistent: false,
|
|
||||||
fullscreen: false,
|
|
||||||
maxHeight: '85vh',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:modelValue', value: boolean): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { modelValue } = toRefs(props)
|
|
||||||
|
|
||||||
const isOpen = ref(modelValue.value)
|
|
||||||
|
|
||||||
watch(modelValue, (value) => {
|
|
||||||
isOpen.value = value
|
|
||||||
})
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
isOpen.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function openModal() {
|
|
||||||
isOpen.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function onModalClose() {
|
|
||||||
if (!props.persistent)
|
|
||||||
closeModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(isOpen, (value) => {
|
|
||||||
emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const api = {
|
|
||||||
isOpen,
|
|
||||||
open: openModal,
|
|
||||||
close: closeModal,
|
|
||||||
}
|
|
||||||
|
|
||||||
provide('modal', api)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
|
|
||||||
<slot name="activator" :open="openModal" :on="{ click: openModal }" />
|
|
||||||
|
|
||||||
<TransitionRoot appear :show="isOpen" as="template">
|
|
||||||
<Dialog as="div" class="relative z-20" @close="onModalClose">
|
|
||||||
<TransitionChild
|
|
||||||
as="template"
|
|
||||||
enter="duration-300 ease-out"
|
|
||||||
enter-from="opacity-0"
|
|
||||||
enter-to="opacity-100"
|
|
||||||
leave="duration-200 ease-in"
|
|
||||||
leave-from="opacity-100"
|
|
||||||
leave-to="opacity-0"
|
|
||||||
>
|
|
||||||
<div class="fixed inset-0 bg-black bg-opacity-25" />
|
|
||||||
</TransitionChild>
|
|
||||||
|
|
||||||
<div class="fixed inset-0 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
class="flex min-h-full items-center justify-center text-center"
|
|
||||||
:class="{
|
|
||||||
'p-4': !fullscreen,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<TransitionChild
|
|
||||||
as="template"
|
|
||||||
enter="duration-300 ease-out"
|
|
||||||
enter-from="opacity-0 scale-95"
|
|
||||||
enter-to="opacity-100 scale-100"
|
|
||||||
leave="duration-200 ease-in"
|
|
||||||
leave-from="opacity-100 scale-100"
|
|
||||||
leave-to="opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<DialogPanel
|
|
||||||
class="w-full transform overflow-hidden bg-white text-left align-middle shadow-xl transition-all py-4"
|
|
||||||
:class="{
|
|
||||||
'h-screen': fullscreen,
|
|
||||||
'max-w-[min(85vw,1200px)] rounded-lg': !fullscreen,
|
|
||||||
}"
|
|
||||||
:style="!fullscreen ? { maxHeight } : {}"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</DialogPanel>
|
|
||||||
</TransitionChild>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</TransitionRoot>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { DialogDescription } from '@headlessui/vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DialogDescription class="px-4 py-3 text-sm text-gray-800">
|
|
||||||
<slot />
|
|
||||||
</DialogDescription>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
// import { ref } from 'vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="px-4 py-3">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { DialogTitle } from '@headlessui/vue'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
dismissable?: boolean
|
|
||||||
titleClass?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
defineProps<Props>()
|
|
||||||
|
|
||||||
const api = inject('modal')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DialogTitle
|
|
||||||
as="div"
|
|
||||||
class="flex gap-2 justify-between items-center px-4 pt-3"
|
|
||||||
>
|
|
||||||
<h3
|
|
||||||
class="text-lg font-medium leading-6 text-gray-900"
|
|
||||||
:class="titleClass"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</h3>
|
|
||||||
<slot v-if="dismissable" name="dismissable">
|
|
||||||
<button
|
|
||||||
class="text-2xl text-gray-500 appearance-none px-2 -mr-2"
|
|
||||||
@click="api.close"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</slot>
|
|
||||||
</DialogTitle>
|
|
||||||
</template>
|
|
||||||
111
pages/events.vue
111
pages/events.vue
|
|
@ -1,111 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="bg-zinc-100 rounded-lg m-5 grid grid-cols-2 gap-10 divide-x divide-solid divide-black py-4 mb-10">
|
|
||||||
|
|
||||||
<div class="px-5">
|
|
||||||
<p class="text-lg">performances</p>
|
|
||||||
|
|
||||||
<div v-for="(item, index) in events">
|
|
||||||
<Collapsible title='placeholder' :modelValue='index <= 10' class="leading-tight py-2 ml-3 text-sm">
|
|
||||||
<template v-slot:title>
|
|
||||||
<div class="gap-1 w-[95%] px-2">
|
|
||||||
<div>
|
|
||||||
{{ item.formatted_date }}: {{item.venue.city}}, {{item.venue.state}}
|
|
||||||
<div class="ml-4 text-[#7F7F7F]">
|
|
||||||
{{ item.venue.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-slot:content>
|
|
||||||
<div v-for="performance in item.program">
|
|
||||||
<div class="italic text-sm ml-16 pt-1">{{performance.work}}</div>
|
|
||||||
<div v-if="performance.ensemble" class="ml-20">
|
|
||||||
{{ performance.ensemble }}
|
|
||||||
</div>
|
|
||||||
<div v-for="performer in performance.performers" class="ml-20">
|
|
||||||
{{ performer.name }}<span v-if="performer.instrument_tags?.length"> - </span>
|
|
||||||
<span v-for="(instrument, index) in performer.instrument_tags">
|
|
||||||
<span v-if="index !== 0">, </span>
|
|
||||||
{{ instrument }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Collapsible>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="px-5">
|
|
||||||
<p class="text-lg">lectures</p>
|
|
||||||
|
|
||||||
<div v-for="yearGroup in lecturesByYear" :key="yearGroup.year">
|
|
||||||
<p class="text-sm font-semibold mt-4 text-[#7F7F7F]">{{ yearGroup.year }}</p>
|
|
||||||
<div class="leading-tight py-2 ml-3 text-sm" v-for="item in yearGroup.talks">
|
|
||||||
<div class="gap-1">
|
|
||||||
<div>
|
|
||||||
{{item.location}}
|
|
||||||
<div v-for="talk in item.talks" class="ml-4 text-[#7F7F7F]">
|
|
||||||
{{ talk.title }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const { data: events } = await useFetch('/api/events', {
|
|
||||||
transform: (events) => {
|
|
||||||
for (const event of events) {
|
|
||||||
let date = new Date(event.start_date)
|
|
||||||
event.formatted_date = ("0" + date.getDate()).slice(-2) + "." + ("0" + (date.getMonth() + 1)).slice(-2) + "." + date.getFullYear()
|
|
||||||
}
|
|
||||||
return events.sort((a,b) => new Date(b.start_date) - new Date(a.start_date))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data: talksData } = await useFetch('/api/talks', {
|
|
||||||
transform: (events) => {
|
|
||||||
for (const event of events) {
|
|
||||||
let date = new Date(event.date)
|
|
||||||
event.date = date
|
|
||||||
event.formatted_date = ("0" + (date.getMonth() + 1)).slice(-2) + "." + date.getFullYear()
|
|
||||||
if(typeof event.title === 'string' || event.title instanceof String) {event.talks = [{'title': event.title}]
|
|
||||||
} else {
|
|
||||||
let talks = []
|
|
||||||
for(const talk of event.title){
|
|
||||||
talks.push({"title": talk})
|
|
||||||
}
|
|
||||||
event.talks = talks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return events.sort((a,b) => new Date(b.date) - new Date(a.date))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const lecturesByYear = computed(() => {
|
|
||||||
if (!talksData.value) return []
|
|
||||||
|
|
||||||
const byYear = {}
|
|
||||||
for (const talk of talksData.value) {
|
|
||||||
const year = talk.date ? talk.date.getFullYear() : 'Unknown'
|
|
||||||
if (!byYear[year]) byYear[year] = []
|
|
||||||
byYear[year].push(talk)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(byYear)
|
|
||||||
.sort((a, b) => b - a)
|
|
||||||
.map(year => {
|
|
||||||
const talks = byYear[year].sort((a, b) => new Date(b.date) - new Date(a.date))
|
|
||||||
return { year, talks }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({
|
|
||||||
titleTemplate: 'Michael Winter - Events - Performances and Lectures'
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
Loading…
Reference in a new issue