Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions docs/component/breadcrumb-specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Breadcrumb コンポーネント仕様書

## 目次

1. [概要](#概要)
2. [インポート](#インポート)
3. [基本的な使用方法](#基本的な使用方法)
4. [Props](#props)
- [必須プロパティ](#必須プロパティ)
- [オプションプロパティ](#オプションプロパティ)
- [継承プロパティ](#継承プロパティ)
5. [コンポジション(子コンポーネント)](#コンポジション子コンポーネント)
6. [状態とスタイル](#状態とスタイル)
- [状態に応じたスタイル](#状態に応じたスタイル)
- [その他のスタイル仕様](#その他のスタイル仕様)
7. [使用例](#使用例)
- [基本的な使用例](#基本的な使用例)
- [現在位置を明示する例](#現在位置を明示する例)
8. [アクセシビリティ](#アクセシビリティ)
9. [技術的な詳細](#技術的な詳細)
10. [注意事項](#注意事項)
11. [スタイルのカスタマイズ](#スタイルのカスタマイズ)
12. [更新履歴](#更新履歴)

---

## 概要

Breadcrumbコンポーネントは、現在のページがサイト階層のどこに位置するかを示し、上位階層へ戻るための経路を提示するナビゲーションコンポーネントである。`Breadcrumb.Item` を子要素として並べ、スラッシュ区切りのリストを生成する。

## インポート

```typescript
import { Breadcrumb } from '@zenkigen-inc/component-ui';
```

## 基本的な使用方法

```typescript
import { Breadcrumb } from '@zenkigen-inc/component-ui';

const items = [
{ key: 'home', label: 'ホーム', href: '/' },
{ key: 'project', label: '案件一覧', href: '/projects' },
{ key: 'detail', label: '詳細' },
];

<Breadcrumb>
{items.map((item) => (
<Breadcrumb.Item key={item.key}>
{item.href != null ? <a href={item.href}>{item.label}</a> : item.label}
</Breadcrumb.Item>
))}
</Breadcrumb>;
```

## Props

### 必須プロパティ

| プロパティ | 型 | 説明 |
| ---------- | ----------- | ----------------------------------------------------------------- |
| `children` | `ReactNode` | パンくずとして並べる要素。通常は複数の `Breadcrumb.Item` を渡す。 |

### オプションプロパティ

本コンポーネント固有のオプションプロパティは存在しない。

### 継承プロパティ

ネイティブ要素の属性を受け取らない(`Breadcrumb` は `nav` に固定の `aria-label="breadcrumb"` を付与する)。

## コンポジション(子コンポーネント)

`Breadcrumb.Item` はパンくずの各階層を表現する子コンポーネントである。

| コンポーネント | 必須/オプション | プロパティ | 型 | デフォルト値 | 説明 |
| ----------------- | --------------- | ---------- | ----------- | ------------ | ------------------------------------------------------------------------------------------------- |
| `Breadcrumb.Item` | 必須 | `children` | `ReactNode` | `undefined` | 1階層分の内容。リンクまたはテキストを渡す。最後の階層はリンク無しで現在位置を示すのが推奨である。 |

## 状態とスタイル

### 状態に応じたスタイル

- リンクが含まれる場合、リンクは `text-interactive02` で表示され、ホバー時に下線が付く。
- 非リンクのテキストは `text-text01` のまま表示される。

### その他のスタイル仕様

- 文字スタイル: `typography-label14regular`
- レイアウト: `flex` + `flex-wrap` で横方向に並べ、`gap-2` でアイテム間の間隔を確保する。
- セパレーター: `after:content-['/']` によるスラッシュを各アイテム末尾に表示し、最後のアイテムでは `last:after:content-none` により非表示とする。
- 折り返し: `whitespace-nowrap` で各アイテム内の折り返しを抑制しつつ、リスト全体は折り返し可能である。

## 使用例

### 基本的な使用例

```typescript
<Breadcrumb>
<Breadcrumb.Item>
<a href="/">ホーム</a>
</Breadcrumb.Item>
<Breadcrumb.Item>
<a href="/projects">案件一覧</a>
</Breadcrumb.Item>
<Breadcrumb.Item>詳細</Breadcrumb.Item>
</Breadcrumb>
```

### 現在位置を明示する例

```typescript
<Breadcrumb>
<Breadcrumb.Item>
<a href="/">ホーム</a>
</Breadcrumb.Item>
<Breadcrumb.Item>
<a href="/users">ユーザー</a>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span aria-current="page">プロフィール</span>
</Breadcrumb.Item>
</Breadcrumb>
```

## アクセシビリティ

- `nav` 要素に `aria-label="breadcrumb"` を付与し、ランドマークとして識別できる。
- リスト構造(`ul` / `li`)で階層を示す。
- 現在位置を示す最後の要素は、リンクではなくテキストで表示するか、`aria-current="page"` を付与することが推奨である。
- セパレーターはCSS擬似要素で描画するため、支援技術に読み上げられず、ナビゲーションの意味を妨げない。

## 技術的な詳細

- `Breadcrumb.Item` を `Breadcrumb.Item = BreadcrumbItem` として公開し、`Breadcrumb` の名前空間配下で利用できるようにしている。
- セパレーターは `after:content-['/']` と `last:after:content-none` を組み合わせて実装している。
- リンク色とホバー時の下線は Tailwind クラス `[&_a]:text-interactive02` および `[&_a]:hover:underline` により適用される。

## 注意事項

1. 子要素は `Breadcrumb.Item` を用いて構築することを推奨する(任意の要素も受け付けるが、スタイルの統一性が損なわれる)。
2. 現在地の階層はリンクにしないか、`aria-current="page"` を付与して支援技術に伝えること。
3. セパレーターは固定で `/` のみであり、カスタマイズ用のプロパティは存在しない。
4. コンポーネント外からクラス名やスタイルを直接渡すAPIは提供していない。スタイルを変更する場合はコンポーネント自体を拡張する。

## スタイルのカスタマイズ

このコンポーネントは Tailwind CSS のユーティリティクラスを使用しており、`@zenkigen-inc/component-config` で定義されたデザイントークンに依存している。カスタマイズする場合は、当該設定を参照すること。

## 更新履歴

| 日付 | 内容 | 担当者 |
| -------------------- | -------- | ------ |
| 2025-12-03 08:41 JST | 新規作成 | - |
20 changes: 18 additions & 2 deletions packages/component-ui/src/breadcrumb/Docs.mdx
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import { Canvas, Meta, ArgTypes, Story, Controls, Source } from '@storybook/addon-docs/blocks';
import { Canvas, Markdown, Meta, Story, Source } from '@storybook/addon-docs/blocks';
import BreadcrumbStories, { Base } from './breadcrumb.stories';
import spec from '../../../../docs/component/breadcrumb-specification.md?raw';

<Meta title="Breadcrumb" of={BreadcrumbStories} />

# Breadcrumb

<details>
<summary className="typography-label14regular text-text02">詳細仕様書</summary>
<div style={{ marginTop: '20px', padding: '12px 36px 36px 36px', backgroundColor: '#f9fafb', borderRadius: '4px' }}>
<p>※ 目次のアンカーリンクは使用できません。</p>
<Markdown>{spec}</Markdown>
</div>
</details>

<Canvas>
<Story of={Base} />
</Canvas>

### Code Example

- `Breadcrumb.Item` を子要素として並べる。
- 最終階層はリンクではなくテキスト、もしくは `aria-current="page"` を付与した要素で現在位置を示す。

<Source of={Base} />

## 概要

ユーザーが現在のページまでの階層を理解し、親の階層へ戻るのをナビゲートするコンポーネントです
ユーザーが現在のページまでの階層を理解し、親の階層へ戻るのをナビゲートするコンポーネントである

## 設計の原則(Do)

Expand Down
9 changes: 7 additions & 2 deletions packages/component-ui/src/breadcrumb/breadcrumb-item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { PropsWithChildren } from 'react';
import type { ReactNode } from 'react';

export const BreadcrumbItem = ({ children }: PropsWithChildren) => {
type BreadcrumbItemProps = {
/** パンくずの1階層分の内容。リンクまたはテキストを渡す。 */
children: ReactNode;
};

export const BreadcrumbItem = ({ children }: BreadcrumbItemProps) => {
return (
<li className="flex gap-2 after:content-['/'] last:after:content-none [&_a]:text-interactive02 [&_a]:hover:underline">
{children}
Expand Down
9 changes: 7 additions & 2 deletions packages/component-ui/src/breadcrumb/breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type { PropsWithChildren } from 'react';
import type { ReactNode } from 'react';

import { BreadcrumbItem } from './breadcrumb-item';

export function Breadcrumb({ children }: PropsWithChildren) {
type BreadcrumbProps = {
/** パンくずとして表示する要素。通常は複数の Breadcrumb.Item を渡す。 */
children: ReactNode;
};

export function Breadcrumb({ children }: BreadcrumbProps) {
return (
<nav aria-label="breadcrumb">
<ul className="typography-label14regular flex flex-wrap gap-2 whitespace-nowrap text-text01">{children}</ul>
Expand Down