Guides/SeriesNav
Default
Last Article
Long Series
Middle Article
Two Article Series
series-nav.tsx
import { Link } from '@/components/shared/link';
import { ArrowRight } from '@/components/svg';
interface SeriesNavProps {
seriesName: string;
articles: { slug: string; title: string }[];
currentSlug: string;
locale?: 'ja' | 'en';
}
/**
* Series navigation component for guide article pages.
* Shows links to all articles in the same series, with the
* current article displayed as non-linked text.
* Uses a TOC-like dashed border box layout.
*/
export function SeriesNav({ seriesName, articles, currentSlug, locale = 'ja' }: SeriesNavProps) {
if (articles.length === 0) return null;
const isEn = locale === 'en';
const guidesPrefix = isEn ? '/en/guides' : '/guides';
return (
<nav
aria-label={isEn ? 'Series navigation' : 'シリーズナビゲーション'}
className={
'mt-vgap-lg border-t border-dashed border-zd-white pt-vgap-sm lg:border lg:pt-vgap-md lg:px-hgap-md lg:pb-vgap-sm'
}
>
<h2 className={'font-futura font-bold text-base md:text-lg pb-vgap-sm'}>{seriesName}</h2>
<ol className="list-none p-0 m-0">
{articles.map((article, index) => {
const isCurrent = article.slug === currentSlug;
return (
<li key={article.slug} className={'py-vgap-xs font-futura text-sm md:text-base'}>
{isCurrent ? (
<span className="inline-flex items-center" aria-current="page">
<ArrowRight aria-hidden="true" className="w-[18px] mr-[5px] flex-shrink-0" />
{index + 1}. {article.title}
{isEn ? ' (this article)' : '(この記事)'}
</span>
) : (
<Link
to={`${guidesPrefix}/${article.slug}/`}
className="inline-flex items-center zd-invert-color-link"
>
<ArrowRight aria-hidden="true" className="w-[18px] mr-[5px] flex-shrink-0" />
{index + 1}. {article.title}
</Link>
)}
</li>
);
})}
</ol>
</nav>
);
}
series-nav.stories.tsx
import { SeriesNav } from './series-nav';
import type { ControlsMap } from '../../sub-packages/styleguide-v2/src/data/control-types';
export const meta = {
title: 'Guides/SeriesNav',
};
export const controls: ControlsMap = {
seriesName: { type: 'text', default: 'モジュラーシンセ入門シリーズ' },
};
const sampleArticles = [
{ slug: 'modular-synth-basics', title: 'モジュラーシンセ基礎知識' },
{ slug: 'first-modules', title: 'はじめてのモジュール選び' },
{ slug: 'patching-101', title: 'パッチングの基本' },
{ slug: 'power-and-cases', title: '電源とケースの選び方' },
{ slug: 'sound-design-tips', title: 'サウンドデザインのコツ' },
];
/**
* Default - viewing the first article in a 5-article series
*/
export const Default = {
args: { seriesName: 'モジュラーシンセ入門シリーズ' },
render: ({ seriesName }: { seriesName: string }) => (
<SeriesNav
seriesName={seriesName}
articles={sampleArticles}
currentSlug="modular-synth-basics"
/>
),
};
/**
* Viewing a middle article in the series
*/
export const MiddleArticle = () => (
<SeriesNav
seriesName="モジュラーシンセ入門シリーズ"
articles={sampleArticles}
currentSlug="patching-101"
/>
);
/**
* Viewing the last article in the series
*/
export const LastArticle = () => (
<SeriesNav
seriesName="モジュラーシンセ入門シリーズ"
articles={sampleArticles}
currentSlug="sound-design-tips"
/>
);
/**
* Short series with only 2 articles
*/
export const TwoArticleSeries = () => (
<SeriesNav
seriesName="VCO比較シリーズ"
articles={[
{ slug: 'analog-vco', title: 'アナログVCOの特徴' },
{ slug: 'digital-vco', title: 'デジタルVCOの特徴' },
]}
currentSlug="analog-vco"
/>
);
/**
* Long series with many articles
*/
export const LongSeries = () => (
<SeriesNav
seriesName="Mutable Instruments 全モジュールレビュー"
articles={[
{ slug: 'braids', title: 'Braids - マクロオシレーター' },
{ slug: 'clouds', title: 'Clouds - グラニュラープロセッサー' },
{ slug: 'rings', title: 'Rings - レゾネーター' },
{ slug: 'plaits', title: 'Plaits - マクロオシレーター2' },
{ slug: 'beads', title: 'Beads - グラニュラープロセッサー2' },
{ slug: 'stages', title: 'Stages - セグメントジェネレーター' },
{ slug: 'marbles', title: 'Marbles - ランダムサンプラー' },
{ slug: 'tides', title: 'Tides - ファンクションジェネレーター' },
]}
currentSlug="rings"
/>
);