Sidebar
Sidebarは、Webサイトやアプリケーションの画面左側に表示されるコンポーネントです。
ヘッダーメニュー・ドロップダウンメニューなど、ナビゲーションに必要な要素をまとめて表示できます。
sidebar/standard is coming soon.
<script lang="ts">
import Sidebar from '$lib/components/ui/modules/Sidebar.svelte';
const headerMenuSections = [
{
item: 'メニュー',
shortCutText: '⇧⌘E',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
];
const headerMenu = {
image: '/images/sample.png',
title: 'メニュー',
subTitle: 'サブタイトル',
menus: headerMenuSections,
};
const menuItems = [
{
id: 'menu1',
category: 'カテゴリー',
entries: [
{
id: 'menu1-entries',
text: 'メニュー1',
},
{
id: 'menu2-entries',
text: 'メニュー2',
},
{
id: 'menu3-entries',
text: 'メニュー3',
},
],
},
{
id: 'menu2',
category: 'カテゴリー',
entries: [
{
id: 'menu2-entries1',
text: 'メニュー1',
options: [
{
id: 'menu2-entries1-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries1-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries1-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries2',
text: 'メニュー2',
options: [
{
id: 'menu2-entries2-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries2-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries2-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries3',
text: 'メニュー3',
options: [
{
id: 'menu2-entries3-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries3-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries3-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries4',
text: 'メニュー4',
options: [
{
id: 'menu2-entries4-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries4-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries4-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries5',
text: 'メニュー5',
options: [
{
id: 'menu2-entries5-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries5-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries5-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries6',
text: 'メニュー6',
options: [
{
id: 'menu2-entries6-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries6-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries6-options3',
label: 'サブメニュー3',
},
],
},
],
},
{
id: 'menu3',
category: 'カテゴリー',
entries: [
{
id: 'menu3-entries1',
text: 'メニュー1',
},
{
id: 'menu3-entries2',
text: 'メニュー2',
},
{
id: 'menu3-entries3',
text: 'メニュー3',
},
{
id: 'menu3-entries4',
text: 'メニュー4',
},
{
id: 'menu3-entries5',
text: 'メニュー5',
},
{
id: 'menu3-entries6',
text: 'メニュー6',
},
],
},
];
</script>
<Sidebar class="w-64" items={menuItems} {headerMenu}></Sidebar>
プロパティ
Sidebarは、以下のプロパティをサポートしています。
| 名前 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
headerMenu |
HeaderMenuProps |
ヘッダー部分に表示するメニュー情報(画像・タイトル・サブタイトルなど)です。 | |
items |
MenuItems[] |
サイドバー内に表示するメニュー群です。 | |
startContent |
Snippet |
メニュー項目の左側に表示するカスタム要素(アイコンなど)を指定できます。 |
インストールの手順
以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新します。
modules/Sidebar.svelte
<!--
@component
## 概要
- ヘッダーにドロップダウン付きの情報を表示し、カテゴリごとに整理されたメニューリストを描画するサイドバーコンポーネントです。
- 各メニューは1階層のサブメニュー(options)を持つことができ、押下時に任意のコールバックを発火させられます。
## 機能
- ヘッダーに画像・タイトル・サブタイトルを表示
- ヘッダーメニューのドロップダウン表示
- メニューカテゴリごとの区切り表示(Separator付き)
- メニューの階層化
- 選択状態のハイライト
- startContentによるアイコン表示
- 入力フィールドによる検索(※ロジックは未実装)
## Props
- class: 追加のクラスを指定できます
- headerMenu: ヘッダーに表示する画像・タイトル・メニュー情報を渡します
- items: メニューの設定(カテゴリや項目)を渡します
- startContent: 各メニュー項目の先頭に表示する要素を指定できます(例: アイコン)
## Usage
```svelte
<Sidebar headerMenu={headerMenu} items={menuItems}>
{#snippet startContent()}
<Icon />
{/snippet}
</Sidebar>
```
-->
<script module lang="ts">
import type { Snippet } from 'svelte';
import type { ClassValue } from 'svelte/elements';
/** メニュー内でセパレーターを挿入する位置のインデックス */
const SEPARATOR_POSITION_INDEX = 2;
export interface HeaderMenuProps {
/** headerMenuに表示させる画像 */
image: string;
/** headerMenuに表示させるタイトル */
title: string;
/** headerMenuに表示させるサブタイトル */
subTitle: string;
/** DropdownMenuで表示する値 */
menus: HeaderMenusProps[];
}
export interface HeaderMenusProps {
/** メニューセクションのラベル */
item?: string;
/** ショートカットテキスト */
shortCutText?: string;
}
export interface MenuItems {
id: string;
/** カテゴリー */
category: string;
/** サブメニューの設定 */
entries?: MenuEntry[];
}
export interface MenuEntry {
id: string;
/** サブメニューの設定 */
options?: MenuOptionProps[];
text?: string;
}
export interface MenuOptionProps {
id: string;
/** メニュー内オプションのラベル */
label: string;
/** 押下時に発火させるコールバック関数 */
onClick?: (items: MenuOptionProps) => void;
}
export interface SidebarProps {
/** クラス */
class?: ClassValue;
/** ヘッダーの情報 */
headerMenu: HeaderMenuProps;
/** サイドバーの情報 */
items: MenuItems[];
/** itemの先頭にiconを表示させる */
startContent?: Snippet;
}
</script>
<script lang="ts">
import Input from '$lib/components/ui/atoms/Input.svelte';
import Separator from '$lib/components/ui/atoms/Separator.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
import { ChevronRight, ChevronsUpDown, Circle, UserRound } from '@lucide/svelte';
import { slide } from 'svelte/transition';
let { class: className, headerMenu, items, startContent }: SidebarProps = $props();
let isMenuOpenMap = $state<Record<string, boolean>>({});
let current = $state('');
/**
* メニュー押下時の処理
*/
function onClick(item: MenuEntry) {
current = item.id;
}
/**
* サブメニュー押下時の処理
*/
function onClickOption(callback: ((option: MenuOptionProps) => void) | undefined, option: MenuOptionProps) {
current = option.id;
callback?.(option);
}
/**
* 開閉フラグの切り替え処理
*/
function toggleOpen(id: string) {
isMenuOpenMap = {
...isMenuOpenMap,
[id]: !isMenuOpenMap[id],
};
}
</script>
<div class={[className, 'flex flex-col h-full min-h-screen pb-2 bg-base-surface-subarea-default']}>
<!-- Header -->
<div class="p-2">
<Popover offset={{ x: 8 }} placement="right" hideArrow>
<div class="w-64 px-2 py-1">
<div class="flex items-center gap-2 py-2">
{#if headerMenu.image}
<img class="size-8 rounded-md" src={headerMenu.image} alt={`${headerMenu.title}のアイコン`} />
{:else}
<UserRound class="rounded-md" size="2rem" />
{/if}
<div class="text-left">
<div class="font-bold text-base-foreground-default text-sm">{headerMenu.title}</div>
<div class="text-base-foreground-subarea-default text-xs">{headerMenu.subTitle}</div>
</div>
</div>
<Separator class="my-1" />
<div class="flex flex-col">
{#each headerMenu.menus as menu, i}
<div class="flex items-center justify-between w-full px-2 py-1.5 rounded-md text-left text-base-foreground-subarea-default text-sm cursor-pointer hover:bg-base-container-subarea-accent/90">
<div class="flex items-center gap-2 text-base-foreground-default text-sm/[150%]">
<Circle size="1rem" />
{menu.item}
</div>
{#if menu.shortCutText}
<span class="text-base-foreground-subarea-default text-xs">{menu.shortCutText}</span>
{/if}
</div>
{#if i === SEPARATOR_POSITION_INDEX}
<Separator class="my-1" />
{/if}
{/each}
</div>
</div>
{#snippet triggerContent(toggle)}
<button class="flex items-center justify-between gap-2 p-2 rounded-md cursor-pointer hover:bg-base-container-subarea-accent/90" onclick={toggle}>
<img class="size-8 rounded-md" src={headerMenu.image} alt={`${headerMenu.title}のアイコン`} />
<div class="w-40 text-left">
<div class="font-semibold text-base-foreground-default text-sm/[100%] mb-1">{headerMenu.title}</div>
<div class="font-normal text-base-foreground-subarea-default text-xs/[100%]">{headerMenu.subTitle}</div>
</div>
<ChevronsUpDown class="text-base-foreground-subarea-default" size="1rem" />
</button>
{/snippet}
</Popover>
<div class="py-2">
<Input inputClass="!min-h-auto !h-7.25" placeholder="検索" />
</div>
</div>
<!-- Menu -->
<div class="flex flex-col overflow-y-scroll">
{#each items as item, index}
{#if index !== 0}
<Separator class="my-2" />
{/if}
<div class="px-2">
{#if item.category}
<div class="h-8 p-2 text-base-foreground-subarea-default text-xs/[100%]">
{item.category}
</div>
{/if}
{#each item.entries || [] as entry}
<button class={['flex items-center justify-between w-full p-2 rounded-md text-left text-base-foreground-subarea-default text-sm/[100%] cursor-pointer hover:bg-base-container-subarea-accent/90', current === entry.id && 'bg-primary/10 rounded-md hover:bg-primary/20']} onclick={!entry.options ? () => onClick(entry) : () => toggleOpen(entry.id)}>
{#if startContent}
<div class="contents pointer-events-auto">{@render startContent()}</div>
{/if}
{entry.text}
{#if entry.options}
<ChevronRight class={['text-base-foreground-subarea-default transition duration-250 my-auto', isMenuOpenMap[entry.id] && 'rotate-90']} size="1rem" />
{/if}
</button>
{#if entry.options && isMenuOpenMap[entry.id]}
<div class="flex w-full py-0.5 pl-4 overflow-hidden translate-3d" transition:slide={{ duration: 250 }}>
<div class="w-0.25 min-h-full bg-base-stroke-default mr-2"></div>
<div class="flex flex-col w-full">
{#each entry.options as option}
<button class={['flex items-center justify-between w-full h-8 px-2 py-1.5 rounded-md text-left text-base-foreground-subarea-default text-sm/[100%] transition-colors cursor-pointer hover:bg-base-container-subarea-accent/90', current === option.id && 'bg-primary/10 rounded-md hover:bg-primary/20']} onclick={() => onClickOption(option.onClick, option)}>
{option.label}
</button>
{/each}
</div>
</div>
{/if}
{/each}
</div>
{/each}
</div>
</div>
依存コンポーネント
Sidebarを使うときは、以下のコンポーネントもダウンロードが必要です。
使い方
sidebar/standard is coming soon.
<script lang="ts">
import Sidebar from '$lib/components/ui/modules/Sidebar.svelte';
const headerMenuSections = [
{
item: 'メニュー',
shortCutText: '⇧⌘E',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
];
const headerMenu = {
image: '/images/sample.png',
title: 'メニュー',
subTitle: 'サブタイトル',
menus: headerMenuSections,
};
const menuItems = [
{
id: 'menu1',
category: 'カテゴリー',
entries: [
{
id: 'menu1-entries',
text: 'メニュー1',
},
{
id: 'menu2-entries',
text: 'メニュー2',
},
{
id: 'menu3-entries',
text: 'メニュー3',
},
],
},
{
id: 'menu2',
category: 'カテゴリー',
entries: [
{
id: 'menu2-entries1',
text: 'メニュー1',
options: [
{
id: 'menu2-entries1-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries1-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries1-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries2',
text: 'メニュー2',
options: [
{
id: 'menu2-entries2-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries2-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries2-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries3',
text: 'メニュー3',
options: [
{
id: 'menu2-entries3-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries3-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries3-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries4',
text: 'メニュー4',
options: [
{
id: 'menu2-entries4-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries4-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries4-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries5',
text: 'メニュー5',
options: [
{
id: 'menu2-entries5-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries5-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries5-options3',
label: 'サブメニュー3',
},
],
},
{
id: 'menu2-entries6',
text: 'メニュー6',
options: [
{
id: 'menu2-entries6-options1',
label: 'サブメニュー1',
},
{
id: 'menu2-entries6-options2',
label: 'サブメニュー2',
},
{
id: 'menu2-entries6-options3',
label: 'サブメニュー3',
},
],
},
],
},
{
id: 'menu3',
category: 'カテゴリー',
entries: [
{
id: 'menu3-entries1',
text: 'メニュー1',
},
{
id: 'menu3-entries2',
text: 'メニュー2',
},
{
id: 'menu3-entries3',
text: 'メニュー3',
},
{
id: 'menu3-entries4',
text: 'メニュー4',
},
{
id: 'menu3-entries5',
text: 'メニュー5',
},
{
id: 'menu3-entries6',
text: 'メニュー6',
},
],
},
];
</script>
<Sidebar class="w-64" items={menuItems} {headerMenu}></Sidebar>
Sidebar Dropdown
SidebarDropdownは、Webサイトやアプリケーションのサイドバー内に表示されるメニューコンポーネントです。
ヘッダー・フッター・ドロップダウン形式のメニューを持ち、ナビゲーションやアカウント切り替えなどの主要な要素を表示します。
sidebar/dropdown is coming soon.
<script lang="ts">
import SidebarDropdown from '$lib/components/ui/modules/SidebarDropdown.svelte';
const headerMenuSections = [
{
item: 'メニュー',
shortCutText: '⇧⌘E',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
];
const headerMenu = {
image: '/images/sample.png',
title: 'メニュー',
subTitle: 'サブタイトル',
menus: headerMenuSections,
};
const footerMenuSections = [
{
item: 'メニュー',
shortCutText: '⇧⌘E',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
];
const footerMenu = {
image: '/images/sample.png',
title: 'メニュー',
subTitle: 'サブタイトル',
menus: footerMenuSections,
};
const items = [
{
label: 'ラベル',
items: [{ label: 'メニュー', shortCutText: '⌘K' }, { label: 'メニュー' }],
},
{
items: [{ label: 'メニュー' }, { label: 'メニュー' }],
},
];
const menus = [
{ label: 'メニュー1', items },
{ label: 'メニュー2', items },
{ label: 'メニュー3', items },
{ label: 'メニュー4', items },
{ label: 'メニュー5', items },
];
</script>
<SidebarDropdown class="w-64" {menus} {headerMenu} {footerMenu}></SidebarDropdown>
プロパティ
SidebarDropdownは、以下のプロパティをサポートしています。
| 名前 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
headerMenu |
HeaderMenuProps |
ヘッダー部分に表示するメニュー情報(画像・タイトル・サブタイトルなど)です。 | |
footerMenu |
FooterMenuProps |
フッター部分に表示するメニュー情報(画像・タイトル・サブタイトルなど)です。 | |
menus |
SidebarMenu[] |
サイドバー内に表示するドロップダウン形式のメニュー群です。 | |
startContent |
Snippet |
メニュー項目の左側に表示するカスタム要素(アイコンなど)を指定できます。 |
インストールの手順
以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新します。
modules/SidebarDropdown.svelte
<!--
@component
## 概要
- サイドバーの中にヘッダーメニュー・フッターメニュー・ドロップダウンメニューを持つ UI コンポーネントです。
- 各セクションに画像、タイトル、サブタイトルを設定でき、ドロップダウンメニューはショートカットキーやセパレーターなど柔軟に構成可能です。
## 機能
- ヘッダー・フッターにメニューを表示し、クリックでドロップダウンを展開
- サブメニュー(階層構造)に対応
- 現在選択中のメニューの表示・切り替え
- ショートカットキー表示、項目ごとのセパレーター表示
- メニュー表示位置の自動調整(ウィンドウサイズに追従)
## Props
- class: 追加のクラス名を付与できます
- headerMenu: ヘッダー部分に表示するメニュー情報
- footerMenu: フッター部分に表示するメニュー情報
- menus: サイドバーに表示されるドロップダウン形式のメニュー群
- startContent: メニュー項目左側に任意の要素を描画できます
## Usage
```svelte
<SidebarDropdown menus={menus} {headerMenu} {footerMenu}>
{#snippet startContent()}
<Icon />
{/snippet}
</SidebarDropdown>
```
-->
<script module lang="ts">
import type { Snippet } from 'svelte';
import type { ClassValue } from 'svelte/elements';
/** メニュー内でセパレーターを挿入する位置のインデックス */
export const SEPARATOR_POSITION_INDEX = 2;
export interface DropdownItem {
label: string;
shortCutText?: string;
subMenus?: DropdownSection[];
}
export interface DropdownSection {
label?: string;
items: DropdownItem[];
}
export interface HeaderMenuProps {
/** headerMenuに表示させる画像 */
image: string;
/** headerMenuに表示させるタイトル */
title: string;
/** headerMenuに表示させるサブタイトル */
subTitle: string;
/** DropdownMenuで表示する値 */
menus: HeaderMenusProps[];
}
export interface HeaderMenusProps {
/** メニューセクションのラベル */
item?: string;
/** ショートカットテキスト */
shortCutText?: string;
}
export interface FooterMenuProps {
/** footerMenuに表示させる画像 */
image: string;
/** footerMenuに表示させるタイトル */
title: string;
/** footerMenuに表示させるサブタイトル */
subTitle: string;
/** DropdownMenuで表示する値 */
menus: FooterMenusProps[];
}
export interface FooterMenusProps {
/** メニューセクションのラベル */
item?: string;
/** ショートカットテキスト */
shortCutText?: string;
}
export interface SidebarMenu {
label: string;
items: DropdownSection[];
}
export interface SidebarDropdownProps {
/** クラス */
class?: ClassValue;
/** ヘッダーの情報 */
headerMenu: HeaderMenuProps;
/** フッターの情報 */
footerMenu: FooterMenuProps;
/** メニューの情報 */
menus: SidebarMenu[];
/** itemの先頭にiconを表示させる */
startContent?: Snippet;
}
</script>
<script lang="ts">
import Separator from '$lib/components/ui/atoms/Separator.svelte';
import Popover from '$lib/components/ui/modules/Popover.svelte';
import { ChevronsUpDown, Circle, Ellipsis, UserRound } from '@lucide/svelte';
let { class: className, headerMenu, footerMenu, menus, startContent }: SidebarDropdownProps = $props();
</script>
<div class={[className, 'flex flex-col justify-between h-full min-h-screen bg-base-surface-subarea-default']}>
<div>
<!-- Header -->
<div class="p-2">
<Popover offset={{ x: 8 }} placement="right" hideArrow>
<div class="w-64 px-2 py-1">
<div class="flex items-center gap-2 py-2">
{#if headerMenu.image}
<img class="size-8 rounded-md" src={headerMenu.image} alt={`${headerMenu.title}のアイコン`} />
{:else}
<UserRound class="rounded-md" size="2rem" />
{/if}
<div class="text-left">
<div class="font-bold text-base-foreground-default text-sm">{headerMenu.title}</div>
<div class="text-base-foreground-subarea-default text-xs">{headerMenu.subTitle}</div>
</div>
</div>
<Separator class="my-1" />
<div class="flex flex-col">
{#each headerMenu.menus as menu, i}
<div class="flex items-center justify-between w-full px-2 py-1.5 rounded-md text-left text-base-foreground-subarea-default text-sm cursor-pointer hover:bg-base-container-subarea-accent/90">
<div class="flex items-center gap-2 text-base-foreground-default text-sm/[150%]">
<Circle size="1rem" />
{menu.item}
</div>
{#if menu.shortCutText}
<span class="text-base-foreground-subarea-default text-xs">{menu.shortCutText}</span>
{/if}
</div>
{#if i === SEPARATOR_POSITION_INDEX}
<Separator class="my-1" />
{/if}
{/each}
</div>
</div>
{#snippet triggerContent(toggle)}
<button class="flex items-center justify-between gap-2 p-2 rounded-md cursor-pointer hover:bg-base-container-subarea-accent/90" onclick={toggle}>
<img class="size-8 rounded-md" src={headerMenu.image} alt={`${headerMenu.title}のアイコン`} />
<div class="w-40 text-left">
<div class="font-semibold text-base-foreground-default text-sm/[100%] mb-1">{headerMenu.title}</div>
<div class="font-normal text-base-foreground-subarea-default text-xs/[100%]">{headerMenu.subTitle}</div>
</div>
<ChevronsUpDown class="text-base-foreground-subarea-default" size="1rem" />
</button>
{/snippet}
</Popover>
</div>
<!-- Dropdown -->
<div class="p-2">
<div class="flex flex-col overflow-y-scroll">
{#each menus as menu, index}
<Popover offset={{ x: 8 }} placement="right" hideArrow>
<div class="w-60 px-2 py-1">
<div class="flex flex-col">
{#each menu.items as group, groupIndex (group)}
{#if group.label}
<div class="px-2 py-1.5 font-semibold">
{group.label}
</div>
{/if}
{#each group.items as item}
<div class="flex items-center justify-between w-full px-2 py-1.5 rounded-md text-left text-base-foreground-subarea-default text-sm cursor-pointer hover:bg-base-container-subarea-accent/90">
<div class="text-base-foreground-default text-sm/[150%]">
{item.label}
</div>
{#if item.shortCutText}
<span class="text-base-foreground-subarea-default text-xs">{item.shortCutText}</span>
{/if}
</div>
{/each}
{#if groupIndex !== menu.items.length - 1}
<Separator class="my-1" />
{/if}
{/each}
</div>
</div>
{#snippet triggerContent(toggle)}
<button class="flex items-center justify-between w-full p-2 rounded-md text-left text-base-foreground-subarea-default text-sm/[100%] transition-colors cursor-pointer hover:bg-base-container-subarea-accent/90" onclick={toggle}>
{#if startContent}
<div class="contents pointer-events-auto">{@render startContent()}</div>
{/if}
{menu.label}
<Ellipsis class="text-base-foreground-subarea-default" size="1rem" />
</button>
{/snippet}
</Popover>
{/each}
</div>
</div>
</div>
<!-- Footer -->
<div class="p-2">
<Popover align="end" offset={{ x: 8 }} placement="right" hideArrow>
<div class="w-64 px-2 py-1">
<div class="flex items-center gap-2 py-2">
{#if footerMenu.image}
<img class="size-8 rounded-md" src={footerMenu.image} alt={`${footerMenu.title}のアイコン`} />
{:else}
<UserRound class="rounded-md" size="2rem" />
{/if}
<div class="text-left">
<div class="font-bold text-base-foreground-default text-sm">{footerMenu.title}</div>
<div class="text-base-foreground-subarea-default text-xs">{footerMenu.subTitle}</div>
</div>
</div>
<Separator class="my-1" />
<div class="flex flex-col">
{#each footerMenu.menus as menu, i}
<div class="flex items-center justify-between w-full px-2 py-1.5 rounded-md text-left text-base-foreground-subarea-default text-sm cursor-pointer hover:bg-base-container-subarea-accent/90">
<div class="flex items-center gap-2 text-base-foreground-default text-sm/[150%]">
<Circle size="1rem" />
{menu.item}
</div>
{#if menu.shortCutText}
<span class="text-base-foreground-subarea-default text-xs">{menu.shortCutText}</span>
{/if}
</div>
{#if i === SEPARATOR_POSITION_INDEX}
<Separator class="my-1" />
{/if}
{/each}
</div>
</div>
{#snippet triggerContent(toggle)}
<button class="flex items-center justify-between gap-2 p-2 rounded-md cursor-pointer hover:bg-base-container-subarea-accent/90" onclick={toggle}>
<img class="size-8 rounded-md" src={footerMenu.image} alt={`${footerMenu.title}のアイコン`} />
<div class="w-40 text-left">
<div class="font-semibold text-base-foreground-default text-sm/[100%] mb-1">{footerMenu.title}</div>
<div class="font-normal text-base-foreground-subarea-default text-xs/[100%]">{footerMenu.subTitle}</div>
</div>
<ChevronsUpDown class="text-base-foreground-subarea-default" size="1rem" />
</button>
{/snippet}
</Popover>
</div>
</div>
依存コンポーネント
SidebarDropdownを使うときは、以下のコンポーネントもダウンロードが必要です。
使い方
sidebar/dropdown is coming soon.
<script lang="ts">
import SidebarDropdown from '$lib/components/ui/modules/SidebarDropdown.svelte';
const headerMenuSections = [
{
item: 'メニュー',
shortCutText: '⇧⌘E',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
];
const headerMenu = {
image: '/images/sample.png',
title: 'メニュー',
subTitle: 'サブタイトル',
menus: headerMenuSections,
};
const footerMenuSections = [
{
item: 'メニュー',
shortCutText: '⇧⌘E',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
{
item: 'メニュー',
},
];
const footerMenu = {
image: '/images/sample.png',
title: 'メニュー',
subTitle: 'サブタイトル',
menus: footerMenuSections,
};
const items = [
{
label: 'ラベル',
items: [{ label: 'メニュー', shortCutText: '⌘K' }, { label: 'メニュー' }],
},
{
items: [{ label: 'メニュー' }, { label: 'メニュー' }],
},
];
const menus = [
{ label: 'メニュー1', items },
{ label: 'メニュー2', items },
{ label: 'メニュー3', items },
{ label: 'メニュー4', items },
{ label: 'メニュー5', items },
];
</script>
<SidebarDropdown class="w-64" {menus} {headerMenu} {footerMenu}></SidebarDropdown>