funkwhale/front/src/components/ui/Modal.vue

99 lines
2.9 KiB
Vue

<script setup lang="ts">
import { type ColorProps, type DefaultProps, color } from '~/composables/color';
import { watchEffect, ref, nextTick } from 'vue';
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
import Button from '~/components/ui/Button.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Layout from '~/components/ui/Layout.vue'
import Heading from '~/components/ui/Heading.vue'
const props = defineProps<{
title: string,
overPopover?: true,
destructive?: true,
cancel?: string
} & (ColorProps | DefaultProps)>()
const isOpen = defineModel<boolean>({ default:false })
const previouslyFocusedElement = ref();
// Handle focus and inertness of the elements behind the modal
watchEffect(() =>
isOpen.value
? ( previouslyFocusedElement.value = document.activeElement,
document.querySelector('#app')?.setAttribute('inert', 'true'))
: ( nextTick(()=> previouslyFocusedElement.value?.focus()),
document.querySelector('#app')?.removeAttribute('inert'))
)
onKeyboardShortcut('escape', () => isOpen.value = false)
// TODO:
// When overflowing content: Add inset shadow to indicate scrollability
</script>
<template>
<Teleport to="body">
<Transition mode="out-in">
<div v-if="isOpen"
@click.exact.stop="isOpen = false"
class="funkwhale overlay"
>
<div @click.stop
class="funkwhale modal"
:class="[
{ 'is-destructive': destructive,
'has-alert': !!$slots.alert,
'over-popover': overPopover,
}
]"
v-bind="{...$attrs, ...color(props)}"
>
<Layout flex gap-12 style="padding: 12px;">
<div style="width: 48px;">
<slot name="topleft" />
</div>
<Spacer h grow />
<Heading :h2="title"
section-heading
:class="{'destructive-header': destructive}"
/>
<Spacer h grow />
<Button icon="bi-x-lg" ghost @click="isOpen = false" align-self="baseline" />
</Layout>
<!-- Content -->
<div class="modal-content">
<Transition>
<div v-if="$slots.alert" class="alert-container">
<div>
<slot name="alert" />
</div>
</div>
</Transition>
<slot />
</div>
<!-- Actions slot -->
<Layout flex gap-12 style="flex-wrap: wrap;" v-if="$slots.actions || cancel" class="modal-actions">
<slot name="actions" />
<Button v-if="cancel" secondary autofocus :onClick="()=>{ isOpen = false }">
{{ cancel }}
</Button>
</Layout>
<Spacer size-46 v-else />
</div>
</div>
</Transition>
</Teleport>
</template>
<style lang="scss">
@import './modal.scss'
</style>