Popover
Popoverは、ユーザーのアクションに応じて一時的にコンテンツを表示するコンポーネントです。
アイコンやボタンなどの要素をトリガーとして使用し、追加情報や操作オプションを提供します。
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
</script>
<Popover>
<div class="p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="large" onclick={toggle}>Popover を開く</Button>
{/snippet}
</Popover>
プロパティ
Popoverは、以下のプロパティをサポートしています。
| 名前 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
placement |
string |
top |
表示する位置を指定します。top, bottom, left, right のいずれかを指定できます。 |
align |
string |
start |
吹き出しの位置を指定できます。start, center, end のいずれかを指定できます。 |
show |
boolean |
false |
表示するかどうかを制御します。 |
hideArrow |
boolean |
false |
矢印を非表示にするかどうかを指定します。 |
offset |
{x: number; y: number} |
{x: 0, y: 0} |
表示位置を任意の方向にずらすための値です。 |
duration |
number |
380 |
表示・非表示の速さを指定できます。 |
noFollow |
boolean |
false |
追従させるかどうかを制御できます。 |
インストールの手順
以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新します。
modules/Popover.svelte
<!--
@component
## 概要
- ユーザーのアクションに応じて一時的にコンテンツを表示するコンポーネントです
## 機能
- 指定した方向にポップオーバーを表示できる
- 指定した位置に吹き出しを移動できる
- 表示したポップオーバーを追従させるさせないを指定できる
## Props
- show: ポップオーバーを表示するかどうか
- placement: ポップオーバーの表示方向
- align: 吹き出しの表示位置
- offset: ポップオーバー表示位置のオフセット
- noFollow: ポップオーバーを追従させるかどうか
- hideArrow: 吹き出しの矢印を非表示にするかどうか
- trigger: ポップオーバーを表示させるためのコンテンツ
## Usage
```svelte
<Popover placement="top">
<div class="w-45">
<div>ここにコンテンツが入ります</div>
<div>コンテンツ説明が入ります</div>
</div>
{#snippet trigger(toggle)}
<Button size="large" onclick={toggle}>表示</Button>
{/snippet}
</Popover>
```
-->
<script module lang="ts">
import type { ClassValue } from 'svelte/elements';
import { cva, type VariantProps } from 'class-variance-authority';
import { type Snippet, tick, untrack } from 'svelte';
export const popoverVariants = cva('relative z-10 bg-base-container-default border border-base-stroke-default rounded-lg shadow-lg', {
variants: {
/** どの位置にpopoverを表示するか */
placement: {
top: '',
bottom: '',
left: '',
right: '',
},
/** どの位置に吹き出しを表示するか */
align: {
start: '',
center: '',
end: '',
},
},
compoundVariants: [
{
placement: 'top',
align: 'start',
class: 'origin-bottom-left',
},
{
placement: 'top',
align: 'center',
class: 'origin-[bottom_center]',
},
{
placement: 'top',
align: 'end',
class: 'origin-bottom-right',
},
{
placement: 'bottom',
align: 'start',
class: 'origin-top-left',
},
{
placement: 'bottom',
align: 'center',
class: 'origin-[top_center]',
},
{
placement: 'bottom',
align: 'end',
class: 'origin-top-right',
},
{
placement: 'left',
align: 'start',
class: 'origin-[right_top]',
},
{
placement: 'left',
align: 'center',
class: 'origin-[right_center]',
},
{
placement: 'left',
align: 'end',
class: 'origin-[right_bottom]',
},
{
placement: 'right',
align: 'start',
class: 'origin-[left_top]',
},
{
placement: 'right',
align: 'center',
class: 'origin-[left_center]',
},
{
placement: 'right',
align: 'end',
class: 'origin-[left_bottom]',
},
],
defaultVariants: {
placement: 'top',
align: 'start',
},
});
export const arrowContainerVariants = cva('absolute z-10 size-3', {
variants: {
placement: {
top: '',
bottom: '',
left: '',
right: '',
},
align: {
start: '',
center: '',
end: '',
},
},
compoundVariants: [
{
placement: 'top',
align: 'start',
class: 'left-6 -bottom-1.5',
},
{
placement: 'top',
align: 'center',
class: 'left-1/2 -bottom-1.5 -translate-x-1/2',
},
{
placement: 'top',
align: 'end',
class: 'right-6 -bottom-1.5',
},
{
placement: 'bottom',
align: 'start',
class: 'left-6 -top-1.5',
},
{
placement: 'bottom',
align: 'center',
class: 'left-1/2 -top-1.5 -translate-x-1/2',
},
{
placement: 'bottom',
align: 'end',
class: 'right-6 -top-1.5',
},
{
placement: 'left',
align: 'start',
class: 'top-6 -right-1.5',
},
{
placement: 'left',
align: 'center',
class: 'top-1/2 -right-1.5 -translate-y-1/2',
},
{
placement: 'left',
align: 'end',
class: 'bottom-6 -right-1.5',
},
{
placement: 'right',
align: 'start',
class: 'top-6 -left-1.5',
},
{
placement: 'right',
align: 'center',
class: 'top-1/2 -left-1.5 -translate-y-1/2',
},
{
placement: 'right',
align: 'end',
class: 'bottom-6 -left-1.5',
},
],
defaultVariants: {
placement: 'top',
align: 'start',
},
});
export const arrowVariants = cva('size-3 bg-base-container-default border-base-stroke-default origin-center', {
variants: {
placement: {
top: 'border-b border-r rotate-45',
bottom: 'border-r border-t -rotate-45',
left: 'border-r border-t rotate-45',
right: 'border-l border-t -rotate-45',
},
align: {
start: '',
center: '',
end: '',
},
},
defaultVariants: {
placement: 'top',
align: 'start',
},
});
export type PopoverVariants = VariantProps<typeof popoverVariants>;
export interface PopoverProps extends PopoverVariants {
/** popoverを表示するか */
show?: boolean;
/** popoverの表示位置のオフセット距離 */
offset?: { x?: number; y?: number };
/** popover表示/非表示の速さ */
duration?: number;
/** popoverを追従させるかどうか */
noFollow?: boolean;
/** 吹き出しの矢印を非表示にするかどうか */
hideArrow?: boolean;
/** popover の中身 */
children: Snippet<[]>;
/**
* popover を開くための snippet。引数に toggle 関数を受け取ります。
*/
triggerContent: Snippet<[() => void]>;
class?: ClassValue;
}
/** 吹き出しの大きさ */
const ARROW_PX = 12;
/** ポップオーバーの端ギリギリに矢印が来ないようにするマージン */
const EDGE_PAD = 12;
/** 6rem 相当の距離(6 * 4px = 24px と仮定) */
const BASE_ARROW_POSITION = 24;
</script>
<script lang="ts">
import { scale } from 'svelte/transition';
let { show = $bindable(false), placement = 'top', align = 'start', offset = { x: 0, y: 0 }, duration = 300, noFollow = false, hideArrow = false, children, triggerContent, class: className }: PopoverProps = $props();
/** popoverとtrigger要素のベースのオフセット距離 */
const BASE_OFFSET = $derived(hideArrow === false ? 9 : 0);
/** viewportサイズ */
let vw = $state(0);
let vh = $state(0);
/** popover 座標 */
let top = $state(0);
let left = $state(0);
/** popover 見切れ解消補正値 */
let popoverAdjustmentTop = $state(0);
let popoverAdjustmentLeft = $state(0);
let displayPlacement = $derived(placement);
let triggerElement = $state<HTMLElement>();
let popoverElement = $state<HTMLElement>();
let popoverWidth = $state(0);
let popoverHeight = $state(0);
let triggerWidth = $state(0);
let triggerHeight = $state(0);
let popoverVariantsClass = $derived(popoverVariants({ placement: displayPlacement, align }));
let arrowContainerVariantsClass = $derived(arrowContainerVariants({ placement: displayPlacement, align }));
let arrowVariantsClass = $derived(arrowVariants({ placement: displayPlacement, align }));
let arrowAdjustmentX = $derived.by(() => {
// 上下のときだけ横方向を調整
if (displayPlacement !== 'top' && displayPlacement !== 'bottom') return 0;
if (!popoverWidth) return 0;
const base_left =
align === 'start'
? BASE_ARROW_POSITION
: align === 'center'
? popoverWidth / 2 - ARROW_PX / 2
: popoverWidth - BASE_ARROW_POSITION - ARROW_PX;
const lo = EDGE_PAD;
const hi = popoverWidth - EDGE_PAD - ARROW_PX;
const ideal_left = base_left - popoverAdjustmentLeft;
const clamped_left = clamp(ideal_left, lo, hi);
return Math.round(clamped_left - base_left);
});
let arrowAdjustmentY = $derived.by(() => {
// 左右のときだけ縦方向を調整
if (displayPlacement !== 'left' && displayPlacement !== 'right') return 0;
if (!popoverHeight) return 0;
const base_top =
align === 'start'
? BASE_ARROW_POSITION
: align === 'center'
? popoverHeight / 2 - ARROW_PX / 2
: popoverHeight - BASE_ARROW_POSITION - ARROW_PX;
const lo = EDGE_PAD;
const hi = popoverHeight - EDGE_PAD - ARROW_PX;
const ideal_top = base_top - popoverAdjustmentTop;
const clamped_top = clamp(ideal_top, lo, hi);
return Math.round(clamped_top - base_top);
});
// 座標更新
$effect(() => {
updatePopoverPosition();
});
// pinch でのリサイズ
$effect(() => {
window.visualViewport?.addEventListener('resize', updatePopoverPosition);
return () => {
window.visualViewport?.removeEventListener('resize', updatePopoverPosition);
};
});
$effect(() => {
if (noFollow) return;
let raf_id: number | null = null;
const update = () => {
updatePopoverPosition();
raf_id = requestAnimationFrame(update);
};
update();
return () => {
if (raf_id !== null) {
cancelAnimationFrame(raf_id);
raf_id = null;
}
};
});
/** popover を非表示にした時の初期化 */
$effect(() => {
if (!show) {
displayPlacement = placement;
top = 0;
left = 0;
popoverAdjustmentLeft = 0;
popoverAdjustmentTop = 0;
}
});
function clamp(v: number, lo: number, hi: number) {
return Math.min(Math.max(v, lo), hi);
}
function toggleShow() {
show = !show;
}
/** ポップオーバーの座標を更新する */
async function updatePopoverPosition() {
if (!show || !triggerElement || !popoverElement) return;
const trigger_rect = triggerElement.getBoundingClientRect();
// 本来の位置に表示する
updatePopoverPositionByRect(placement, trigger_rect);
await tick();
requestAnimationFrame(() => {
if (!popoverElement) return;
// 反転のチェック
adjustDisplayPlacement(popoverElement.getBoundingClientRect());
if (displayPlacement !== placement) {
updatePopoverPositionByRect(displayPlacement, trigger_rect);
}
});
// 別軸のずらしのチェック
adjustPopoverPosition(trigger_rect);
}
/** rect に応じてポップオーバーの座標を更新する関数 */
function updatePopoverPositionByRect(placement: PopoverProps['placement'], rect: DOMRect) {
if (!triggerElement || !popoverElement) return;
// placement に基づく座標計算
switch (placement) {
case 'top':
top = rect.top - popoverHeight - BASE_OFFSET + (offset.y ?? 0);
break;
case 'bottom':
top = rect.bottom + BASE_OFFSET + (offset.y ?? 0);
break;
case 'left':
left = rect.left - popoverWidth - BASE_OFFSET + (offset.x ?? 0);
break;
case 'right':
left = rect.right + BASE_OFFSET + (offset.x ?? 0);
break;
}
// align に基づく座標計算
switch (align) {
case 'start':
if (placement === 'top' || placement === 'bottom') {
left = rect.left + (offset.x ?? 0);
}
else {
top = rect.top + (offset.y ?? 0);
}
break;
case 'center':
if (placement === 'top' || placement === 'bottom') {
left = rect.left + rect.width / 2 - popoverWidth / 2 + (offset.x ?? 0);
}
else {
top = rect.top + rect.height / 2 - popoverHeight / 2 + (offset.y ?? 0);
}
break;
case 'end':
if (placement === 'top' || placement === 'bottom') {
left = rect.right - popoverWidth + (offset.x ?? 0);
}
else {
top = rect.bottom - popoverHeight + (offset.y ?? 0);
}
break;
}
untrack(() => {
// Safari 対応
if (Math.abs((window.visualViewport?.width ?? 0) - window.innerWidth) < 1) {
top += window.visualViewport?.offsetTop ?? 0;
left += window.visualViewport?.offsetLeft ?? 0;
}
});
}
/**
* 必要であれば displayPlacement を調整する
* @param rect PopoverElement の DOMRect
*/
function adjustDisplayPlacement(rect: DOMRect) {
let display_placement = placement;
switch (placement) {
case 'top':
// 上にはみ出ている
if (rect.top < 0) {
const bottom_space = vh - (rect.bottom + triggerHeight + BASE_OFFSET);
// 下の方が領域が広い場合
if (Math.abs(rect.top) < bottom_space) {
display_placement = 'bottom';
}
}
break;
case 'bottom':
if (rect.bottom > vh) {
const top_space = rect.top - triggerHeight - BASE_OFFSET;
if (Math.abs(rect.bottom - vh) < top_space) {
display_placement = 'top';
}
}
break;
case 'left':
if (rect.left < 0) {
const right_space = vw - (rect.right + triggerWidth + BASE_OFFSET);
if (Math.abs(rect.left) < right_space) {
display_placement = 'right';
}
}
break;
case 'right':
if (rect.right > vw) {
const left_space = rect.left - triggerWidth - BASE_OFFSET;
if (Math.abs(rect.right - vw) < left_space) {
display_placement = 'left';
}
}
break;
}
displayPlacement = display_placement;
}
/** popoverの別軸での見切れを補正する値を求める関数 */
function adjustPopoverPosition(rect: DOMRect) {
// trigger が画面内に見えているかどうか
const is_trigger_visible = rect.bottom > 0 && rect.top < vh && rect.right > 0 && rect.left < vw;
// trigger が画面内でかつ popover がplacementの別軸でみきれている場合に位置を補正する
if (is_trigger_visible) {
if (displayPlacement === 'top' || displayPlacement === 'bottom') {
const overflow_left = -left;
const overflow_right = left + popoverWidth - vw;
popoverAdjustmentLeft = overflow_left > 0 ? overflow_left : overflow_right > 0 ? -overflow_right : 0;
popoverAdjustmentTop = 0;
}
else {
const overflow_top = -top;
const overflow_bottom = top + popoverHeight - vh;
popoverAdjustmentTop = overflow_top > 0 ? overflow_top : overflow_bottom > 0 ? -overflow_bottom : 0;
popoverAdjustmentLeft = 0;
}
}
}
function close(e) {
if (triggerElement?.contains(e.target)) return;
show = false;
}
</script>
<svelte:window onclickcapture={close} bind:innerWidth={vw} bind:innerHeight={vh} />
<div class={className}>
<div bind:this={triggerElement} bind:clientHeight={triggerHeight} bind:clientWidth={triggerWidth}>
{@render triggerContent(toggleShow)}
</div>
{#if show}
<div class="fixed top-0 left-0 z-50" style="transform: translate({left}px, {top}px);" bind:this={popoverElement} bind:clientHeight={popoverHeight} bind:clientWidth={popoverWidth}>
<div style="transform: translate({popoverAdjustmentLeft}px, {popoverAdjustmentTop}px);">
<div class={[popoverVariantsClass]} in:scale={{ duration, opacity: 0, start: 0.9 }} out:scale={{ duration, opacity: 0, start: 0.9 }}>
{@render children?.()}
</div>
<!-- 吹き出し -->
{#if hideArrow === false}
<div class={arrowContainerVariantsClass} style="transform: translate({arrowAdjustmentX}px, {arrowAdjustmentY}px);">
<div class={arrowVariantsClass} in:scale|global={{ duration, opacity: 0, start: 0.9 }} out:scale|global={{ duration, opacity: 0, start: 1 }}></div>
</div>
{/if}
</div>
</div>
{/if}
</div>
使い方
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
</script>
<Popover>
<div class="p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="large" onclick={toggle}>Popover を開く</Button>
{/snippet}
</Popover>
サンプル
Default
Defaultでの表示です。
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
</script>
<Popover>
<div class="p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="large" onclick={toggle}>Popover を開く</Button>
{/snippet}
</Popover>
Placement / Align
PlacementとAlignの組み合わせによる表示例です。
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
</script>
<div class="grid grid-cols-3 place-content-center place-items-center min-w-[450px] gap-12">
<Popover class="pl-25" placement="top">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>top/start</Button>
{/snippet}
</Popover>
<Popover placement="top" align="center">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>top/center</Button>
{/snippet}
</Popover>
<Popover class="pr-25" placement="top" align="end">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>top/end</Button>
{/snippet}
</Popover>
<div class="flex flex-col items-end gap-2">
<Popover placement="left">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>left/start</Button>
{/snippet}
</Popover>
<Popover placement="left" align="center">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>left/center</Button>
{/snippet}
</Popover>
<Popover placement="left" align="end">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>left/end</Button>
{/snippet}
</Popover>
</div>
<div class="w-40"></div>
<div class="flex flex-col gap-2">
<Popover placement="right">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>right/start</Button>
{/snippet}
</Popover>
<Popover placement="right" align="center">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>right/center</Button>
{/snippet}
</Popover>
<Popover placement="right" align="end">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>right/end</Button>
{/snippet}
</Popover>
</div>
<Popover class="pl-25" placement="bottom">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>bottom/start</Button>
{/snippet}
</Popover>
<Popover placement="bottom" align="center">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>bottom/center</Button>
{/snippet}
</Popover>
<Popover class="pr-25" placement="bottom" align="end">
<div class="w-58 p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="small" onclick={toggle}>bottom/end</Button>
{/snippet}
</Popover>
</div>
Hide Arrow
矢印を非表示にすることができます。
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
</script>
<Popover hideArrow>
<div class="p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="large" onclick={toggle}>Popover を開く</Button>
{/snippet}
</Popover>
No Follow
noFollow を指定すると、一度表示されたポップオーバーが対象要素に追従しなくなります。 そのため、スクロールしてもポップオーバーの表示位置は固定され、対象要素と一緒に移動しません。
スクロール追従が不要なケースや、パフォーマンスを重視したい場合に活用できます。
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
</script>
<Popover placement="top" noFollow>
<div class="p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="large" onclick={toggle}>Popover を開く</Button>
{/snippet}
</Popover>
Offset
Offsetを指定することで、表示位置を調整することができます。
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
</script>
<Popover offset={{ x: 50, y: -30 }}>
<div class="p-6">
<div class="font-bold text-base-foreground-default text-lg mb-2">ラベル</div>
<div class="text-base-foreground-muted">ここに補足文が入ります</div>
</div>
{#snippet triggerContent(toggle)}
<Button size="large" onclick={toggle}>Popover を開く</Button>
{/snippet}
</Popover>
With DropdownMenu
DropdownMenuと組み合わせた例です。
<script>
import Button from '$lib/components/ui/atoms/Button.svelte';
import DropdownMenu from '$lib/components/ui/modules/DropdownMenu.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
let subMenus = [
{
label: 'ラベル',
items: [
{ label: 'メニュー' },
{ label: 'メニュー', shortCutText: '⌘K' },
],
},
];
let menus = [
{
label: 'ラベル',
items: [
{ label: 'メニュー', shortCutText: '⌘K' },
{ label: 'メニュー' },
],
},
{
items: [
{ label: 'メニュー' },
{ label: 'メニュー', subMenus },
{ label: 'メニュー' },
],
},
];
</script>
<Popover placement="bottom" align="start" hideArrow>
<DropdownMenu class="min-w-72 border-0 !rounded-[0.4375rem]" {menus} />
{#snippet triggerContent(toggle)}
<Button size="large" onclick={toggle}>Popover を開く</Button>
{/snippet}
</Popover>