Created
March 15, 2025 00:54
-
-
Save kobitoDevelopment/032e9bc2de2dbac354c4d53f06e9d936 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
<template> | |
<MyDialog ref="dialogRef"> | |
<template #open>ダイアログを開く</template> | |
<template #inner> | |
<p>ダイアログの中身</p> | |
<button @click="closeDialogFromParent">閉じる</button> | |
</template> | |
</MyDialog> | |
</template> | |
<script setup lang="ts"> | |
/* 「ダイアログを閉じる」というアクションを、閉じるボタン以外からも行えるように */ | |
import MyDialog from "../components/MyDialog.vue"; | |
const dialogRef = ref<InstanceType<typeof MyDialog> | null>(null); | |
const closeDialogFromParent = () => { | |
dialogRef.value?.closeDialog(); | |
}; | |
</script> |
This file contains hidden or 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
<template> | |
<!-- ダイアログを開くボタン --> | |
<button class="open" aria-expanded="false" @click="openDialog"> | |
<slot name="open"> | |
<span class="text">ダイアログを開く</span> | |
</slot> | |
</button> | |
<!-- ダイアログ --> | |
<dialog ref="dialog" class="my-dialog"> | |
<button ref="close" class="close" aria-label="ダイアログを閉じる" @click="closeDialog"> | |
<slot name="close">X</slot> | |
</button> | |
<div tabindex="0" class="inner" role="presentation"> | |
<slot name="inner" /> | |
</div> | |
<div tabindex="0" @focus="handleEndFocus"></div> | |
</dialog> | |
</template> | |
<script lang="ts" setup> | |
const dialog = ref<HTMLDialogElement>(); | |
/* dialogを開く関数 */ | |
const openDialog = async () => { | |
await nextTick(); | |
if (!dialog.value) { | |
throw new Error("dialog要素はnull"); | |
} | |
dialog.value.addEventListener("keydown", (e) => { | |
if (dialog.value?.open && e.key === "Escape") { | |
e.stopPropagation(); | |
closeDialog(); | |
} | |
}); | |
dialog.value.showModal(); | |
/* html要素とbody要素の両方にoverflowを記述 */ | |
document.body.style.overflow = "hidden"; | |
document.documentElement.style.overflow = "hidden"; | |
}; | |
/* dialogを閉じる関数 */ | |
const closeDialog = () => { | |
if (!dialog.value) { | |
throw new Error("dialog要素はnull"); | |
} | |
dialog.value.close(); | |
/* html要素とbody要素の両方にoverflowを記述 */ | |
document.body.style.overflow = ""; | |
document.documentElement.style.overflow = ""; | |
}; | |
/* ダイアログ内のフォーカスを制御する */ | |
const close = ref<HTMLElement>(); | |
const handleEndFocus = () => { | |
close.value?.focus(); | |
}; | |
/* 「ダイアログを閉じる」というアクションを、閉じるボタン以外からも行えるように */ | |
defineExpose({ | |
closeDialog, | |
}); | |
/* ダイアログのbackdropをクリックしたら閉じる関数 */ | |
const handleBackdropClick = (e: MouseEvent) => { | |
if (e.target === dialog.value) { | |
closeDialog(); | |
} | |
}; | |
/* ダイアログのbackdropをクリックしたら閉じるイベントを登録 */ | |
onMounted(() => { | |
if (!dialog.value) { | |
throw new Error("dialog要素はnull"); | |
} | |
dialog.value.addEventListener("click", handleBackdropClick); | |
}); | |
onBeforeUnmount(() => { | |
document.body.style.overflow = ""; | |
document.documentElement.style.overflow = ""; | |
}); | |
</script> | |
<style scoped> | |
.open { | |
cursor: pointer; | |
} | |
.my-dialog { | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
max-height: unset; /* dialogのデフォルトのmax-heightをリセット */ | |
height: max-content; /* autoにすると、十分に画面縦幅がある場合でもダイアログに縦スクロールが生まれる場合がある */ | |
max-width: initial; /* dialogのデフォルトのmax-widthをリセット */ | |
width: 90%; | |
padding: 0; /* dialogのデフォルトのpaddingをリセット */ | |
margin: 0; /* dialogのデフォルトのmarginをリセット */ | |
background-color: rgb(0 0 0 / 0%); /* dialogにデフォルトで指定される白の背景色を透明にする */ | |
opacity: 0; | |
transform: translateX(-50%) translateY(-50%); | |
} | |
.my-dialog[open] { | |
animation: fade-in 0.3s forwards; | |
} | |
.my-dialog::backdrop { | |
background-color: rgb(0 0 0 / 80%); | |
cursor: pointer; | |
} | |
.my-dialog > .inner { | |
background-color: #fff; | |
height: max-content; | |
max-height: 90vh; /* 先祖要素にmax-contentを指定した場合、その子孫要素の単位に%を使うとwebkitで値が0になる場合があるためvhを使用 */ | |
overflow-y: auto; | |
width: 100%; | |
} | |
.my-dialog > .inner:focus-visible { | |
outline: none; | |
} | |
.my-dialog > .close { | |
aspect-ratio: 1; | |
cursor: pointer; | |
position: absolute; | |
right: 2%; | |
top: 2%; | |
width: 20px; | |
} | |
@keyframes fade-in { | |
from { | |
opacity: 0; | |
} | |
to { | |
opacity: 1; | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment