サイドバー項目SidebarItemExperimental
サイドバー内で使う選択行です。アイコン、ラベル、件数、階層、開閉、削除操作を同じ行幅で揃えて表示します。
プレビュー
ライブラリ
フォルダ
状態とバリエーション
フラットな項目
階層を持たないナビゲーションでは、先頭の開閉スペースを消して左端を揃えます。
行アクション付き
削除などの行アクションは右端の予約枠に置き、件数の位置を揃えます。
ライブラリ
フォルダ
プロパティ
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| id | string | - | 項目を識別する ID。 |
| label | string | - | 行に表示するラベル。 |
| icon | ReactNode | - | ラベルの前に表示するアイコン。 |
| count | number | - | 右端に表示する件数。 |
| isActive | boolean | - | 現在選択中の項目かどうか。 |
| collapsed | boolean | - | アイコンのみの行にします。既定では最も近い SidebarProvider の折りたたみ状態に追従するため、通常は指定不要です。畳んだ行はラベルをツールチップで出します。 |
| level | number | 0 | 階層の深さ。子要素のインデントに使います。 |
| hasChildren | boolean | false | 開閉できる子要素を持つことを示します。 |
| isExpanded | boolean | false | 子要素が開いている状態を示します。 |
| reserveChevronSpace | boolean | true | 階層表示のための開閉スペースを予約します。 |
| onClick | () => void | - | 行を選択した時に呼ばれます。 |
| onToggleExpand | (event: MouseEvent) => void | - | 開閉アイコンまたは子を持つ行を操作した時に呼ばれます。 |
| onDelete | (event: MouseEvent) => void | - | 右端の削除ボタンを押した時に呼ばれます。 |
| deleteLabel | ReactNode | "Delete" | 削除ボタンのツールチップと aria-label。 |
| dragOverId / dragAction | string | null | - | ドラッグ中の並び替え・ネスト位置を行上に表示します。 |
使い方
import * as React from "react"
import { SidebarItem, TooltipButton } from "@gunjo/ui"
import { IconChevronRight as ChevronRight, IconFileText as FileText, IconFolder as Folder, IconGridDots as Grid, IconPlus as Plus, IconStar as Star, IconTrash as Trash2 } from "@tabler/icons-react"
const libraryItems = [
{ id: "all", label: "すべての素材", icon: Grid, count: 128 },
{ id: "favorite", label: "お気に入り", icon: Star, count: 24 },
{ id: "uncategorized", label: "未分類", icon: Folder, count: 8 },
{ id: "trash", label: "ごみ箱", icon: Trash2, count: 3 },
]
const folderItems = [
{ id: "campaign", label: "キャンペーン", icon: Folder, count: 18, level: 0, hasChildren: true },
{ id: "spring", label: "春の公開素材", icon: FileText, count: 6, level: 1, hasChildren: false },
{ id: "product", label: "商品写真", icon: Folder, count: 42, level: 0, hasChildren: false },
]
export function MediaLibrarySidebarContent() {
const [activeId, setActiveId] = React.useState("favorite")
const [foldersExpanded, setFoldersExpanded] = React.useState(true)
const [campaignExpanded, setCampaignExpanded] = React.useState(true)
return (
<div className="w-full max-w-sm rounded-md border bg-muted/30 p-3">
<section className="space-y-1">
<p className="px-2 text-xs font-bold uppercase tracking-wide text-muted-foreground">ライブラリ</p>
{libraryItems.map((item) => {
const Icon = item.icon
return (
<SidebarItem
key={item.id}
id={item.id}
icon={<Icon size={18} />}
label={item.label}
count={item.count}
isActive={activeId === item.id}
onClick={() => setActiveId(item.id)}
reserveChevronSpace={false}
/>
)
})}
</section>
<section className="mt-4 space-y-1">
<div className="flex items-center justify-between pl-2">
<p className="text-xs font-bold uppercase tracking-wide text-muted-foreground">フォルダ</p>
<div className="grid w-14 grid-cols-[1.5rem_1.5rem] items-center justify-end">
<TooltipButton type="button" variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground" tooltip="フォルダを追加" aria-label="フォルダを追加">
<Plus size={14} />
</TooltipButton>
<TooltipButton
type="button"
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground"
tooltip={foldersExpanded ? "フォルダを閉じる" : "フォルダを開く"}
aria-label={foldersExpanded ? "フォルダを閉じる" : "フォルダを開く"}
onClick={() => setFoldersExpanded((open) => !open)}
>
<ChevronRight size={14} className={foldersExpanded ? "rotate-90 transition-transform" : "transition-transform"} />
</TooltipButton>
</div>
</div>
{foldersExpanded ? (
<div className="space-y-1">
{folderItems.map((item) => {
if (item.id === "spring" && !campaignExpanded) return null
const Icon = item.icon
return (
<SidebarItem
key={item.id}
id={item.id}
icon={<Icon size={18} />}
label={item.label}
count={item.count}
level={item.level ?? 0}
hasChildren={item.hasChildren}
isExpanded={campaignExpanded}
isActive={activeId === item.id}
onClick={() => setActiveId(item.id)}
onToggleExpand={(event) => {
event.stopPropagation()
setCampaignExpanded((open) => !open)
}}
/>
)
})}
</div>
) : null}
</section>
</div>
)
}