feat: add Storybook configuration and mocks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Temuulen Ankhbayar 2026-03-07 15:22:18 +09:00
parent 17ec53e08f
commit 74ee154669
5 changed files with 133 additions and 0 deletions

View File

@ -0,0 +1,48 @@
import type { StorybookConfig } from "@storybook/react-vite";
import tailwindcss from "@tailwindcss/vite";
import path from "path";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(ts|tsx)"],
addons: ["@storybook/addon-essentials"],
framework: {
name: "@storybook/react-vite",
options: {},
},
staticDirs: ["../public"],
viteFinal: async config => {
config.plugins = config.plugins || [];
config.plugins.push(tailwindcss());
// Ensure JSX runtime is available (auto-imports React)
config.esbuild = {
...config.esbuild,
jsx: "automatic",
};
config.resolve = config.resolve || {};
config.resolve.alias = {
...config.resolve.alias,
"@": path.resolve(__dirname, "../src"),
"next/link": path.resolve(__dirname, "mocks/next-link.tsx"),
"next/image": path.resolve(__dirname, "mocks/next-image.tsx"),
"next/navigation": path.resolve(__dirname, "mocks/next-navigation.tsx"),
};
// Disable PostCSS — @tailwindcss/vite handles CSS directly
config.css = config.css || {};
config.css.postcss = { plugins: [] };
// Polyfill process.env for Next.js code that uses it
config.define = {
...config.define,
"process.env": JSON.stringify({
NODE_ENV: "development",
}),
};
return config;
},
};
export default config;

View File

@ -0,0 +1,24 @@
import React from "react";
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
src: string;
alt: string;
width?: number;
height?: number;
fill?: boolean;
priority?: boolean;
}
const Image = ({ src, alt, width, height, fill, priority: _priority, ...props }: ImageProps) => (
// eslint-disable-next-line @next/next/no-img-element
<img
src={src}
alt={alt}
width={width}
height={height}
style={fill ? { objectFit: "cover", width: "100%", height: "100%" } : undefined}
{...props}
/>
);
export default Image;

View File

@ -0,0 +1,13 @@
import React from "react";
const Link = React.forwardRef<
HTMLAnchorElement,
React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }
>(({ href, children, ...props }, ref) => (
<a ref={ref} href={href} {...props}>
{children}
</a>
));
Link.displayName = "Link";
export default Link;

View File

@ -0,0 +1,30 @@
export function useRouter() {
return {
push: (url: string) => {
window.location.hash = url;
},
replace: (url: string) => {
window.location.hash = url;
},
back: () => {
window.history.back();
},
forward: () => {
window.history.forward();
},
refresh: () => {},
prefetch: () => Promise.resolve(),
};
}
export function usePathname() {
return "/";
}
export function useSearchParams() {
return new URLSearchParams();
}
export function useParams() {
return {};
}

View File

@ -0,0 +1,18 @@
import type { Preview } from "@storybook/react";
import "../src/app/globals.css";
const preview: Preview = {
parameters: {
layout: "centered",
backgrounds: {
default: "light",
values: [
{ name: "light", value: "#f9f9f9" },
{ name: "white", value: "#ffffff" },
{ name: "dark", value: "#1a1a2e" },
],
},
},
};
export default preview;