Dialog
Dialogは、ユーザーに対して明示的な情報を提示したり、オプションを選択させるためのコンポーネントです。
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import Dialog from '$lib/components/ui/modals/Dialog.svelte';
let isOpen = $state(false);
function openDialog(e) {
e.preventDefault();
e.stopPropagation();
isOpen = !isOpen;
}
function onClick(result) {
if (result) {
isOpen = !isOpen;
}
else {
isOpen = !isOpen;
}
}
</script>
<Button variant="primary" onclick={openDialog}>Open Dialog</Button>
<Dialog class="max-w-lg w-[80vw] m-auto" bind:open={isOpen} onClick={(result) => onClick(result)}>
<div class="flex flex-col gap-2">
<Label class="!text-lg !font-semibold max-md:mx-auto">ラベル</Label>
<p class="text-base-foreground-muted text-sm max-md:text-center">ここに補足文が入ります。</p>
</div>
</Dialog>
プロパティ
Dialogは、以下のプロパティをサポートしています。
| 名前 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
open |
boolean |
false |
Dialog を表示するかどうか。 |
dismissible |
boolean |
false |
Dialog 以外を押しても閉じるかどうか。 |
positiveText |
string |
決定 |
primaryボタンの文言を渡します。 |
negativeText |
string |
キャンセル |
secondaryボタンの文言を渡します。 |
enableClose |
boolean |
false |
閉じるボタンを表示できます。 |
onClick |
(result: boolean) => void |
ボタンをクリックされた際に呼び出される関数です。 |
インストールの手順
以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新します。
modals/Dialog.svelte
<!--
@component
## 概要
- ユーザーに対して情報を提示したり、オプションを選択させるためのコンポーネントです
## 機能
- グループ化して表示することができます
- 任意のコンテンツを配置できます
## Usage
```svelte
<Dialog bind:open={isOpen}>
{@render children()}
</Dialog>
```
-->
<script module lang="ts">
import type { Snippet } from 'svelte';
import type { ClassValue } from 'svelte/elements';
import { cva, type VariantProps } from 'class-variance-authority';
export const dialogVariants = cva('relative flex flex-col gap-6 p-6 bg-base-container-default border border-base-stroke-default rounded-lg shadow-lg',
);
export type DialogVariants = VariantProps<typeof dialogVariants>;
export interface DialogProps extends DialogVariants {
/** Dialogが開いているかどうか */
open: boolean;
/** Dialog以外押して閉じれるかどうか */
dismissible?: boolean;
/** primaryボタンの文言 */
positiveText?: string;
/** secondaryボタンの文言 */
negativeText?: string;
/** 閉じれるようにするかどうか */
enableClose?: boolean;
/** ボタンが押されたときのハンドラ */
onClick: (result: boolean) => void;
/** クラス */
class?: ClassValue;
children: Snippet<[]>;
}
</script>
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import { X } from '@lucide/svelte';
import { fade, scale } from 'svelte/transition';
let { class: className, open = $bindable(false), dismissible = false, positiveText = '決定', negativeText = 'キャンセル', enableClose = false, onClick, children }: DialogProps = $props();
let backgroundElement = $state<HTMLElement>();
let dialogVariantsClass = $derived(dialogVariants({ class: className }));
$effect(() => {
if (open) {
document.body.classList.add('overflow-hidden');
document.addEventListener('click', onClickOutside);
document.addEventListener('keydown', onKeyDown);
}
return () => {
document.body.classList.remove('overflow-hidden');
document.removeEventListener('click', onClickOutside);
document.removeEventListener('keydown', onKeyDown);
};
});
// dialog外をクリックしたときにdialogを閉じる
function onClickOutside(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
if (!backgroundElement) return;
if (!(e.target instanceof HTMLElement)) return;
if (backgroundElement.contains(e.target) && dismissible) {
open = false;
}
}
// escキーでdrawerを閉じる
function onKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape' && dismissible) {
open = false;
}
}
</script>
{#if open}
<div class="fixed inset-0 z-40 bg-base-container-default/50" bind:this={backgroundElement} transition:fade={{ duration: 150 }}></div>
<div class="fixed inset-0 z-50 size-fit m-auto">
<div class={dialogVariantsClass} transition:scale={{ start: 0.9, duration: 150 }}>
{@render children()}
<div class="flex justify-end gap-2 max-md:flex-col-reverse">
<Button class="max-md:w-full" variant="secondary" onclick={() => onClick(false)}>{negativeText}</Button>
<Button class="max-md:w-full" variant="primary" onclick={() => onClick(true)}>{positiveText}</Button>
</div>
{#if enableClose}
<Button class="absolute top-2 right-2 p-2" tone="ghost" variant="secondary" size="small" isSquare onclick={() => onClick(false)}>
<X size="1rem" />
</Button>
{/if}
</div>
</div>
{/if}
依存コンポーネント
Dialogを使うときは、以下のコンポーネントもダウンロードが必要です。
使い方
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import Dialog from '$lib/components/ui/modals/Dialog.svelte';
let isOpen = $state(false);
function openDialog(e) {
e.preventDefault();
e.stopPropagation();
isOpen = !isOpen;
}
function onClick(result) {
if (result) {
isOpen = !isOpen;
}
else {
isOpen = !isOpen;
}
}
</script>
<Button variant="primary" onclick={openDialog}>Open Dialog</Button>
<Dialog class="max-w-lg w-[80vw] m-auto" bind:open={isOpen} onClick={(result) => onClick(result)}>
<div class="flex flex-col gap-2">
<Label class="!text-lg !font-semibold max-md:mx-auto">ラベル</Label>
<p class="text-base-foreground-muted text-sm max-md:text-center">ここに補足文が入ります。</p>
</div>
</Dialog>
サンプル
Default
特に操作が行われていない、デフォルトの状態です。
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import Dialog from '$lib/components/ui/modals/Dialog.svelte';
let isOpen = $state(false);
function openDialog(e) {
e.preventDefault();
e.stopPropagation();
isOpen = !isOpen;
}
function onClick(result) {
if (result) {
isOpen = !isOpen;
}
else {
isOpen = !isOpen;
}
}
</script>
<Button variant="primary" onclick={openDialog}>Open Dialog</Button>
<Dialog class="max-w-lg w-[80vw] m-auto" bind:open={isOpen} onClick={(result) => onClick(result)}>
<div class="flex flex-col gap-2">
<Label class="!text-lg !font-semibold max-md:mx-auto">ラベル</Label>
<p class="text-base-foreground-muted text-sm max-md:text-center">ここに補足文が入ります。</p>
</div>
</Dialog>
EnableClose
閉じるボタンを表示することができます。
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import Dialog from '$lib/components/ui/modals/Dialog.svelte';
let isOpen = $state(false);
function openDialog(e) {
e.preventDefault();
e.stopPropagation();
isOpen = !isOpen;
}
function onClick(result) {
if (result) {
isOpen = !isOpen;
}
else {
isOpen = !isOpen;
}
}
</script>
<Button variant="primary" onclick={openDialog}>Open Dialog</Button>
<Dialog class="max-w-lg w-[80vw] m-auto" enableClose dismissible bind:open={isOpen} onClick={(result) => onClick(result)}>
<div class="flex flex-col gap-2">
<Label class="!text-lg !font-semibold max-md:m-auto">ラベル</Label>
<p class="text-base-foreground-muted text-sm max-md:text-center">ここに補足文が入ります。</p>
</div>
</Dialog>
With Input,Select
InputやSelectなどの要素と組み合わせることもできます。
<script lang="ts">
import Button from '$lib/components/ui/atoms/Button.svelte';
import Input from '$lib/components/ui/atoms/Input.svelte';
import Label from '$lib/components/ui/atoms/Label.svelte';
import Select from '$lib/components/ui/atoms/Select.svelte';
import Dialog from '$lib/components/ui/modals/Dialog.svelte';
let selectValue = $state('');
let inputValue = $state('');
let isOpen = $state(false);
let options = [
{ label: '選択肢1', value: 'item1' },
{ label: '選択肢2', value: 'item2' },
{ label: '選択肢3', value: 'item3' },
];
function openDialog(e) {
e.preventDefault();
e.stopPropagation();
isOpen = !isOpen;
}
function onClick(result) {
if (result) {
isOpen = !isOpen;
}
else {
isOpen = !isOpen;
}
}
</script>
<Button variant="primary" onclick={openDialog}>Open Dialog</Button>
<Dialog class="max-w-lg w-[80vw] m-auto" bind:open={isOpen} onClick={(result) => onClick(result)}>
<div class="flex flex-col gap-2">
<Label class="!text-lg !font-semibold max-md:mx-auto">ラベル</Label>
<p class="text-base-foreground-muted text-sm max-md:text-center">ここに補足文が入ります。</p>
</div>
<div>
<Label class="mb-1.5">ラベル</Label>
<Input type="text" placeholder="プレースホルダー" bind:value={inputValue} />
</div>
<div>
<Label class="mb-1.5">ラベル</Label>
<Select options={options} placeholder="選択してください" bind:value={selectValue} />
</div>
</Dialog>