64 lines
2.1 KiB
Vue
64 lines
2.1 KiB
Vue
<!-- SPEC: PJ2026-010405 云端控制台. -->
|
||
<!-- Implementation reference: draft-2026-07-13-p0-cloud-console. -->
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref, toRef } from "vue";
|
||
import { nextOverlayId, useOverlaySurface } from "@/composables/useOverlaySurface";
|
||
|
||
const props = withDefaults(defineProps<{
|
||
open: boolean;
|
||
title: string;
|
||
description?: string;
|
||
wide?: boolean;
|
||
closeOnBackdrop?: boolean;
|
||
busy?: boolean;
|
||
initialFocus?: string;
|
||
}>(), {
|
||
description: "",
|
||
wide: false,
|
||
closeOnBackdrop: true,
|
||
busy: false,
|
||
initialFocus: ""
|
||
});
|
||
|
||
const emit = defineEmits<{ close: [] }>();
|
||
const surface = ref<HTMLElement | null>(null);
|
||
const titleId = nextOverlayId("console-dialog-title");
|
||
const descriptionId = nextOverlayId("console-dialog-description");
|
||
const describedBy = computed(() => props.description ? descriptionId : undefined);
|
||
|
||
useOverlaySurface(toRef(props, "open"), surface, close, { initialFocusSelector: props.initialFocus || undefined });
|
||
|
||
function close(): void {
|
||
if (!props.busy) emit("close");
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<Teleport to="body">
|
||
<div v-if="open" class="console-overlay-backdrop" role="presentation" @mousedown.self="closeOnBackdrop ? close() : undefined">
|
||
<section
|
||
ref="surface"
|
||
class="console-overlay-surface"
|
||
:data-wide="wide ? 'true' : 'false'"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
:aria-labelledby="titleId"
|
||
:aria-describedby="describedBy"
|
||
:aria-busy="busy ? 'true' : 'false'"
|
||
tabindex="-1"
|
||
>
|
||
<header class="console-overlay-header">
|
||
<div class="console-overlay-title-stack">
|
||
<h2 :id="titleId">{{ title }}</h2>
|
||
<p v-if="description" :id="descriptionId">{{ description }}</p>
|
||
</div>
|
||
<button type="button" class="console-overlay-close" :disabled="busy" aria-label="关闭" @click="close">×</button>
|
||
</header>
|
||
<div class="console-overlay-body"><slot /></div>
|
||
<footer v-if="$slots.footer" class="console-overlay-footer"><slot name="footer" /></footer>
|
||
</section>
|
||
</div>
|
||
</Teleport>
|
||
</template>
|