サイドバーExperimental

アプリやドキュメントの左側に主要ナビゲーションをまとめ、必要に応じてアイコン幅へ折りたためるサイドナビゲーションです。

プレビュー

メインコンテンツ

状態とバリエーション

折りたたみ初期表示

狭い画面や補助ナビでは、アイコンだけで始めて必要に応じて展開できます。

メインコンテンツ

トグルを置く高さを変える

トグルは境界線の上に浮かせてあり、本文やフッターの幅を取りません。既定はフッターの上端ですが、フッターを置かない画面では header か center に寄せます。

メインコンテンツ

開閉をアプリ側で持つ

collapsed と onCollapsedChange を渡すと、開閉の持ち主がアプリになります。保存した値から復元したい、サイドバーの外にも開閉の入口を置きたい、というときはこちらです。

いまの状態: 開いている
メインコンテンツ

プロパティ

表は横にスクロールできます
プロパティ初期値説明
Sidebaraside-レールそのもの。フレックス/グリッド親の高さいっぱいに自分で伸びます(240px ⇄ 折りたたみ 60px)。ブロック親に置く場合だけ高さを明示してください。
SidebarProvider.defaultCollapsedbooleanfalse非制御時の初期折りたたみ状態。
SidebarProvider.collapsedboolean-折りたたみ状態を外部 state で制御します。
SidebarProvider.onCollapsedChange(collapsed: boolean) => void-折りたたみ状態が変わった時に呼ばれます。
useSidebar(){ collapsed, setCollapsed, toggleCollapsed }-子孫コンポーネントからサイドバー状態を読み書きします。プロバイダ外で呼ぶと例外になります。
useSidebarCollapsed()boolean | null-最も近い SidebarProvider の折りたたみ状態。プロバイダが無ければ null を返し、例外は投げません。サイドバーの内外どちらでも成立する部品向け。
SidebarTogglebutton-サイドバー境界線上に配置する折りたたみトグル。フッターや本文のレイアウト幅を消費しません。
SidebarToggle.expandLabelReactNode"Expand sidebar"折りたたみ時に表示するツールチップと aria-label。
SidebarToggle.collapseLabelReactNode"Collapse sidebar"展開時に表示するツールチップと aria-label。
SidebarToggle.placement"center" | "header" | "footer""footer"トグルを置く境界線位置。既定ではフッター上端と右境界線の交点に置きます。

使い方

import * as React from "react"
import {
  Avatar,
  AvatarFallback,
  Sidebar,
  SidebarBody,
  SidebarFooter,
  SidebarHeader,
  SidebarItem,
  SidebarProvider,
  SidebarToggle,
  useSidebar,
} from "@gunjo/ui"
import {
  IconChartBar as BarChart3,
  IconHome as Home,
  IconLayoutKanban as FolderKanban,
  IconSettings as Settings,
} from "@tabler/icons-react"

const navItems = [
  { id: "home", label: "ホーム", icon: Home },
  { id: "projects", label: "プロジェクト", icon: FolderKanban },
  { id: "reports", label: "レポート", icon: BarChart3 },
  { id: "settings", label: "設定", icon: Settings },
]

function SidebarContent() {
  const { collapsed } = useSidebar()
  const [activeId, setActiveId] = React.useState("projects")

  return (
    <Sidebar className="min-h-[360px]">
      <SidebarHeader>
        <div className="grid h-7 w-7 shrink-0 place-items-center rounded-md bg-primary text-xs font-semibold text-primary-foreground">G</div>
        {!collapsed ? <span className="truncate text-sm font-semibold">Gunjo UI</span> : null}
      </SidebarHeader>
      <SidebarBody>
        {/* SidebarItem reads the collapse from the provider: the row goes
            icon-only and its label moves into a tooltip on its own. */}
        {navItems.map((item) => {
          const Icon = item.icon
          return (
            <SidebarItem
              key={item.id}
              id={item.id}
              icon={<Icon className="h-4 w-4 shrink-0" />}
              label={item.label}
              isActive={activeId === item.id}
              onClick={() => setActiveId(item.id)}
              reserveChevronSpace={false}
            />
          )
        })}
      </SidebarBody>
      <SidebarFooter>
        <Avatar className="h-7 w-7 shrink-0"><AvatarFallback>UI</AvatarFallback></Avatar>
        {!collapsed ? <span className="min-w-0 flex-1 truncate text-sm">デザインチーム</span> : null}
      </SidebarFooter>
      <SidebarToggle
        expandLabel="サイドバーを展開"
        collapseLabel="サイドバーを折りたたむ"
      />
    </Sidebar>
  )
}

export function SidebarLayout() {
  return (
    <div className="flex overflow-hidden rounded-md border bg-background">
      <SidebarProvider>
        <SidebarContent />
      </SidebarProvider>
      <main className="flex min-w-0 flex-1 items-center justify-center bg-muted/30 p-6 text-sm text-muted-foreground">
        メインコンテンツ
      </main>
    </div>
  )
}

設計の判断

  • 畳んだ幅を60pxに固定した。資料は「畳んだときはアイコンだけを出し、ホバーで補足を出す。アイコンの無い項目に畳みは使わない」を挙げています。GUNJO は240pxと60pxの2つだけを持ち、その間の幅を作れないようにしました。60pxはアイコン1つと左右の余白でちょうど埋まる幅なので、「ラベルが半分だけ見える」中途半端な状態が作れません。畳んだときの補足は SidebarItem が吹き出しで出します。
  • 畳みの状態は文脈で配るが、文脈が無くても壊れない。SidebarProvider が状態を持ち、SidebarHeaderSidebarFooterSidebarItem が同じ状態を読んで自分で詰めます。ただし useSidebarCollapsed はプロバイダが無いとき例外を投げずに空を返します。SidebarItem はサイドバーの外(設定画面の一覧など)でも使う部品なので、そのために毎回プロバイダで包ませるのを避けました(#692)。
  • 現在地の印は項目の側が持つ。資料が求める aria-current は、isActive を渡した SidebarItem が付けます。開いている親の行は、子の塗りと二重にならないように別の見た目(isCurrentAncestor)にしてあります。資料が挙げる「モバイルではサイドバーを Sheet に置き換える」は部品に入っていないので、いまは画面の側で出し分けます。

使用コンポーネント

いつ・なぜ使うか(UIXHERO)

「いつ・なぜ使うか」の判断は、姉妹サイト UIXHERO の記事で解説しています。