Next.jsにStorybookを入れて、Story上でページ遷移できるようにしたい

Storybookの導入

npx storybook@latest init

Reactと変わらない ……がTypeScript入れているので、その辺りの記述の違いは見ておいた方がいいかも

import type { StorybookConfig } from "@storybook/nextjs";
const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-onboarding",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/nextjs",
    options: {},
  },
  docs: {
    autodocs: "tag",
  },
  core: {
    // <https://storybook.js.org/docs/configure/telemetry>
    disableTelemetry: true, // telemetryの無効化追加
  },
};
export default config;

        

一応おさらいとしてサーバーの起動の仕方は

npm run storybook

コマンドを打ち、http://localhost:6006/ にアクセス (Storybookは自動でブラウザ起動してくれるっぽいね)

React.jsで前、MemoryRouter使って、Storybook上でページ遷移できるようにしたし、Next.jsでもあったら便利かぁ~ と思って、調べていた

(っと軽はずみにはじめたら、後悔した……)

全然記事無くて、びびった~。 結局GitHub見に行って、かなり深い階層にあった~…… めちゅくちゃ彷徨った……. しかも、version7.0でも使えそうなのに、公式ドキュメント version8.0専用ページにあるし、どういうこっちゃ~?orz

まぁ何はともあれ、Next.jsでStorybook上でページ遷移をしたい場合、ここ参考にするといいと思う https://github.com/storybookjs/storybook/tree/next/code/frameworks/nextjs https://storybook.js.org/docs/8.0/get-started/nextjs

実際のコード(preview.tsx

import type { Preview } from "@storybook/react";

import { action, HandlerFunction } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';

type storybookPath = { 
  [key:string] : { title:string, story: string | undefined }
};

/** 
 * ページの設定
 * 実際の「pathname」をキーに、ストーリーに変換するための変数
 */
const storybookPathes : storybookPath = { 
  '/Page': { title:'Example/Page', story:'Logged Out' },
};

/** 設定された内容を見て、ログをコンソールに出しつつ、実際にページ遷移する */
function switchLocal(pathname:string) {
  console.log(pathname);
  if(storybookPathes[pathname] != undefined) {
    // // 用心深くいくならつけたらいいけど、入ることないはず
    // if(storybookPathes[pathname].title == undefined) {
    //   console.log('error:param is wrong : ' + pathname);
    //   linkTo('Configure your project')();
    // }
    if(storybookPathes[pathname].story != undefined) {
      console.log(`to ${storybookPathes[pathname].title} ${storybookPathes[pathname].story}`);
      linkTo(storybookPathes[pathname].title, storybookPathes[pathname].story)();
    }  else {
      console.log(`to ${storybookPathes[pathname].title}`);
      linkTo(storybookPathes[pathname].title)();
    }
  } else {
    console.log("This page has not yet been set up");
    linkTo('Configure your project')();
  }
}

type nextNavigationMethod = "push" | "replace" | "forward" | "back" | "prefetch" | "refresh"

function overridesMethod(
  methodName:nextNavigationMethod, 
  args:any[]
):Promise<boolean> {
  action(`nextRouter.${methodName}`)(...args);
  if(args.length > 0 && typeof args[0] === "string") {
    switchLocal(args[0]);
  }
  return Promise.resolve(true);
}

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    nextjs: {
      appDirectory: true, // App Router であることを設定する
      navigation: { // next/navigation に対する設定
        // それぞれのメソッドにフックする
        push(...args) { return overridesMethod("push", args); },
        replace(...args) { return overridesMethod("replace", args); },
        forward(...args) { return overridesMethod("forward", args); },
        back(...args) { return overridesMethod("back", args); },
        prefetch(...args) { return overridesMethod("prefetch", args); },
        refresh(...args) { return overridesMethod("refresh", args); },
      },
    },
  },
};

export default preview;