101 lines
2.1 KiB
Vue
101 lines
2.1 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<Transition name="sheet">
|
|
<div v-if="open" class="sheet-backdrop" @click.self="$emit('close')" @keydown.escape="$emit('close')">
|
|
<div class="sheet" role="dialog" aria-modal="true" :aria-label="label" ref="sheetEl" tabindex="-1">
|
|
<div class="sheet__handle" aria-hidden="true" />
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch, nextTick } from 'vue'
|
|
|
|
defineProps<{
|
|
open: boolean
|
|
label: string
|
|
}>()
|
|
|
|
defineEmits<{
|
|
close: []
|
|
}>()
|
|
|
|
const sheetEl = ref<HTMLElement | null>(null)
|
|
|
|
watch(
|
|
() => sheetEl.value,
|
|
async (el) => {
|
|
if (el) {
|
|
await nextTick()
|
|
const firstInput = el.querySelector<HTMLElement>('input, textarea, button[type="submit"]')
|
|
if (firstInput) {
|
|
firstInput.focus()
|
|
} else {
|
|
el.focus()
|
|
}
|
|
}
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.sheet-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: var(--color-glass-overlay);
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: center;
|
|
z-index: 100;
|
|
}
|
|
|
|
.sheet {
|
|
background: linear-gradient(135deg, var(--color-glass-strong) 0%, var(--color-glass-subtle) 100%);
|
|
border: 1px solid var(--color-glass-border);
|
|
border-bottom: none;
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border-radius: 20px 20px 0 0;
|
|
padding: var(--spacing-lg) var(--spacing-xl) var(--spacing-2xl);
|
|
width: 100%;
|
|
max-width: var(--content-max-width);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-lg);
|
|
outline: none;
|
|
}
|
|
|
|
.sheet__handle {
|
|
width: 36px;
|
|
height: 4px;
|
|
background: var(--color-glass-border-hover);
|
|
border-radius: 2px;
|
|
align-self: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Transition */
|
|
.sheet-enter-active,
|
|
.sheet-leave-active {
|
|
transition: opacity 0.25s ease;
|
|
}
|
|
|
|
.sheet-enter-active .sheet,
|
|
.sheet-leave-active .sheet {
|
|
transition: transform 0.25s ease;
|
|
}
|
|
|
|
.sheet-enter-from,
|
|
.sheet-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.sheet-enter-from .sheet,
|
|
.sheet-leave-to .sheet {
|
|
transform: translateY(100%);
|
|
}
|
|
</style>
|