アプリレールAppRailExperimental

左側の細いプライマリナビゲーションです。

プレビュー

選択中

ホーム

最近の更新、タスク、概要を確認します。

状態とバリエーション

主要ナビゲーション

アプリ全体の移動先をアイコンボタンで縦に並べます。アイコンのみのため、ツールチップと aria-label を必ず合わせます。

選択中

ホーム

最近の更新、タスク、概要を確認します。

少数アクション

アクションが少ない場合も、設定などの補助操作は下端に寄せて役割を分けます。

選択中

ホーム

最近の更新、タスク、概要を確認します。

未読の数を載せる

器は中身を決めないので、項目に数を重ねられます。数字は飾りなので aria-hidden にし、件数は aria-label の文に入れます。

件数は aria-label にも入れています。数字が見えない人にも「未読 3 件」が届きます。

プロパティ

表は横にスクロールできます
プロパティ初期値説明
classNamestring-レール本体に追加するクラス名。
childrenReact.ReactNode-レール内に配置するナビゲーションや操作。

使い方

import { AppRail, TooltipButton } from "@gunjo/ui"
import {
  IconBell as Bell,
  IconHome as Home,
  IconSearch as Search,
  IconSettings as Settings,
  IconUserCircle as UserRound,
} from "@tabler/icons-react"
import type { ReactNode } from "react"
import { useState } from "react"

function RailAction(props: {
  label: string
  children: ReactNode
  active?: boolean
  onSelect: () => void
}) {
  const { label, children, active, onSelect } = props

  return (
    <TooltipButton
      type="button"
      variant="ghost"
      size="icon"
      tooltip={label}
      tooltipSide="right"
      tooltipOpenOnClick
      aria-label={label}
      aria-pressed={active}
      onClick={onSelect}
      className={[
        "h-10 w-10 text-muted hover:bg-background/10 hover:text-background",
        active ? "bg-background/20 text-background ring-1 ring-background/25" : "",
      ].join(" ")}
    >
      {children}
    </TooltipButton>
  )
}

export function AppRailExample() {
  const items = [
    {
      key: "home",
      label: "ホーム",
      icon: <Home className="h-5 w-5" />,
      title: "ホーム",
      description: "最近の更新、タスク、概要を確認します。",
    },
    {
      key: "search",
      label: "検索",
      icon: <Search className="h-5 w-5" />,
      title: "検索",
      description: "ワークスペース全体からドキュメントや操作を探します。",
    },
    {
      key: "notifications",
      label: "通知",
      icon: <Bell className="h-5 w-5" />,
      title: "通知",
      description: "未読通知と重要な更新を確認します。",
    },
    {
      key: "account",
      label: "アカウント",
      icon: <UserRound className="h-5 w-5" />,
      title: "アカウント",
      description: "プロフィール、チーム、請求情報を管理します。",
    },
  ]
  const settingsItem = {
    key: "settings",
    label: "設定",
    icon: <Settings className="h-5 w-5" />,
    title: "設定",
    description: "表示、通知、権限などの環境設定を変更します。",
  }
  const allItems = [...items, settingsItem]
  const [activeKey, setActiveKey] = useState(allItems[0].key)
  const activeItem = allItems.find((item) => item.key === activeKey) ?? allItems[0]

  return (
    <div className="flex h-[340px] w-full max-w-3xl overflow-hidden rounded-md border bg-background">
      <AppRail>
        {items.map((item) => (
          <RailAction
            key={item.key}
            label={item.label}
            active={activeItem.key === item.key}
            onSelect={() => setActiveKey(item.key)}
          >
            {item.icon}
          </RailAction>
        ))}
        <div className="mt-auto">
          <RailAction
            label={settingsItem.label}
            active={activeItem.key === settingsItem.key}
            onSelect={() => setActiveKey(settingsItem.key)}
          >
            {settingsItem.icon}
          </RailAction>
        </div>
      </AppRail>
      <main className="flex min-w-0 flex-1 items-center justify-center bg-secondary/50 p-6">
        <div className="max-w-sm space-y-2 text-center">
          <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
            選択中
          </p>
          <h3 className="text-2xl font-semibold tracking-tight">{activeItem.title}</h3>
          <p className="text-sm leading-6 text-muted-foreground">
            {activeItem.description}
          </p>
        </div>
      </main>
    </div>
    )
}

設計の判断

  • 器と中身を分ける。AppRail が持つのは幅64px・地色・縦並びだけです。項目は TooltipButton を並べて作ります。項目の見た目や当たり判定を器の側で決め打ちしないので、通知の点を足すのも、下端に別の一群を置くのも呼ぶ側でできます。
  • アイコンだけの並びに、必ず名前を付ける。見本では項目ごとに aria-label と、右側に出るツールチップを付けています。この並びではアイコンは飾りではなく唯一のラベルなので、名前が無いと読み上げでも触っただけでも何の項目か分かりません。tooltipOpenOnClick を付けているのは、触る画面でも名前を出せるようにするためです。
  • いまいる場所を、色以外でも出す。選択中の項目には aria-pressed を付け、見た目は背景の明るさと細い輪郭の2つで示します。地色が暗いので、明るさの差だけに頼らない形にしています。幅で切り替える3段の考え方(狭い画面は下の並び、広い画面は横の並び)は資料に書いてあります。

いつ・なぜ使うか(UIXHERO)

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