mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 07:21:17 +08:00
Add @storybook/nextjs-vite package
This commit is contained in:
parent
b94ead30bd
commit
2528064a2f
@ -11,6 +11,7 @@ export const frameworkToRenderer: Record<
|
||||
'html-vite': 'html',
|
||||
'html-webpack5': 'html',
|
||||
nextjs: 'react',
|
||||
'nextjs-vite': 'react',
|
||||
'preact-vite': 'preact',
|
||||
'preact-webpack5': 'preact',
|
||||
qwik: 'qwik',
|
||||
|
@ -44,6 +44,7 @@ export default {
|
||||
'@storybook/html-vite': '8.3.0-alpha.3',
|
||||
'@storybook/html-webpack5': '8.3.0-alpha.3',
|
||||
'@storybook/nextjs': '8.3.0-alpha.3',
|
||||
'@storybook/nextjs-vite': '8.3.0-alpha.3',
|
||||
'@storybook/preact-vite': '8.3.0-alpha.3',
|
||||
'@storybook/preact-webpack5': '8.3.0-alpha.3',
|
||||
'@storybook/react-vite': '8.3.0-alpha.3',
|
||||
|
@ -5,6 +5,7 @@ export type SupportedFrameworks =
|
||||
| 'html-vite'
|
||||
| 'html-webpack5'
|
||||
| 'nextjs'
|
||||
| 'nextjs-vite'
|
||||
| 'preact-vite'
|
||||
| 'preact-webpack5'
|
||||
| 'react-vite'
|
||||
|
23
code/frameworks/nextjs-vite/.eslintrc.json
Normal file
23
code/frameworks/nextjs-vite/.eslintrc.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"rules": {
|
||||
"global-require": "off",
|
||||
"no-param-reassign": "off",
|
||||
"import/no-dynamic-require": "off",
|
||||
"import/no-unresolved": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.stories.@(jsx|tsx)"],
|
||||
"rules": {
|
||||
"react/no-unknown-property": "off",
|
||||
"jsx-a11y/anchor-is-valid": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.compat.@(tsx|ts)"],
|
||||
"rules": {
|
||||
"local-rules/no-uncategorized-errors": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
10
code/frameworks/nextjs-vite/README.md
Normal file
10
code/frameworks/nextjs-vite/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Storybook for Next.js with Vite Builder
|
||||
|
||||
See [documentation](https://storybook.js.org/docs/get-started/frameworks/nextjs?renderer=react) for installation instructions, usage examples, APIs, and more.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This framework borrows heavily from these Storybook addons:
|
||||
|
||||
- [storybook-addon-next](https://github.com/RyanClementsHax/storybook-addon-next) by [RyanClementsHax](https://github.com/RyanClementsHax/)
|
||||
- [storybook-addon-next-router](https://github.com/lifeiscontent/storybook-addon-next-router) by [lifeiscontent](https://github.com/lifeiscontent)
|
143
code/frameworks/nextjs-vite/package.json
Normal file
143
code/frameworks/nextjs-vite/package.json
Normal file
@ -0,0 +1,143 @@
|
||||
{
|
||||
"name": "@storybook/nextjs-vite",
|
||||
"version": "8.3.0-alpha.3",
|
||||
"description": "Storybook for Next.js and Vite",
|
||||
"keywords": [
|
||||
"storybook",
|
||||
"nextjs",
|
||||
"vite"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/nextjs-vite",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybookjs/storybook.git",
|
||||
"directory": "code/frameworks/nextjs"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"node": "./dist/index.js",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./preset": {
|
||||
"types": "./dist/preset.d.ts",
|
||||
"require": "./dist/preset.js"
|
||||
},
|
||||
"./dist/preview.mjs": "./dist/preview.mjs",
|
||||
"./cache.mock": {
|
||||
"types": "./dist/export-mocks/cache/index.d.ts",
|
||||
"import": "./dist/export-mocks/cache/index.mjs",
|
||||
"require": "./dist/export-mocks/cache/index.js"
|
||||
},
|
||||
"./headers.mock": {
|
||||
"types": "./dist/export-mocks/headers/index.d.ts",
|
||||
"import": "./dist/export-mocks/headers/index.mjs",
|
||||
"require": "./dist/export-mocks/headers/index.js"
|
||||
},
|
||||
"./navigation.mock": {
|
||||
"types": "./dist/export-mocks/navigation/index.d.ts",
|
||||
"import": "./dist/export-mocks/navigation/index.mjs",
|
||||
"require": "./dist/export-mocks/navigation/index.js"
|
||||
},
|
||||
"./router.mock": {
|
||||
"types": "./dist/export-mocks/router/index.d.ts",
|
||||
"import": "./dist/export-mocks/router/index.mjs",
|
||||
"require": "./dist/export-mocks/router/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"dist/index.d.ts"
|
||||
],
|
||||
"cache.mock": [
|
||||
"dist/export-mocks/cache/index.d.ts"
|
||||
],
|
||||
"headers.mock": [
|
||||
"dist/export-mocks/headers/index.d.ts"
|
||||
],
|
||||
"router.mock": [
|
||||
"dist/export-mocks/router/index.d.ts"
|
||||
],
|
||||
"navigation.mock": [
|
||||
"dist/export-mocks/navigation/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"template/cli/**/*",
|
||||
"README.md",
|
||||
"*.js",
|
||||
"*.d.ts",
|
||||
"!src/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"check": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/check.ts",
|
||||
"prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/builder-vite": "workspace:*",
|
||||
"@storybook/react": "workspace:*",
|
||||
"@storybook/test": "workspace:*",
|
||||
"styled-jsx": "5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.0.0",
|
||||
"next": "^14.2.5",
|
||||
"typescript": "^5.3.2",
|
||||
"vite-plugin-storybook-nextjs": "^0.0.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^14.2.5",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
|
||||
"storybook": "workspace:^",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-storybook-nextjs": "^0.0.13"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sharp": "^0.33.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"bundler": {
|
||||
"entries": [
|
||||
"./src/index.ts",
|
||||
"./src/preset.ts",
|
||||
"./src/preview.tsx",
|
||||
"./src/export-mocks/cache/index.ts",
|
||||
"./src/export-mocks/headers/index.ts",
|
||||
"./src/export-mocks/router/index.ts",
|
||||
"./src/export-mocks/navigation/index.ts",
|
||||
"./src/images/decorator.tsx"
|
||||
],
|
||||
"externals": [
|
||||
"sb-original/image-context"
|
||||
],
|
||||
"platform": "node"
|
||||
},
|
||||
"gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16"
|
||||
}
|
1
code/frameworks/nextjs-vite/preset.js
Normal file
1
code/frameworks/nextjs-vite/preset.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/preset');
|
8
code/frameworks/nextjs-vite/project.json
Normal file
8
code/frameworks/nextjs-vite/project.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "nextjs-vite",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {}
|
||||
}
|
||||
}
|
4
code/frameworks/nextjs-vite/src/config/preview.ts
Normal file
4
code/frameworks/nextjs-vite/src/config/preview.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { setConfig } from 'next/config';
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
setConfig(process.env.__NEXT_RUNTIME_CONFIG);
|
23
code/frameworks/nextjs-vite/src/export-mocks/cache/index.ts
vendored
Normal file
23
code/frameworks/nextjs-vite/src/export-mocks/cache/index.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { fn } from '@storybook/test';
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
type Callback = (...args: any[]) => Promise<any>;
|
||||
|
||||
// mock utilities/overrides (as of Next v14.2.0)
|
||||
const revalidatePath = fn().mockName('next/cache::revalidatePath');
|
||||
const revalidateTag = fn().mockName('next/cache::revalidateTag');
|
||||
const unstable_cache = fn()
|
||||
.mockName('next/cache::unstable_cache')
|
||||
.mockImplementation((cb: Callback) => cb);
|
||||
const unstable_noStore = fn().mockName('next/cache::unstable_noStore');
|
||||
|
||||
const cacheExports = {
|
||||
unstable_cache,
|
||||
revalidateTag,
|
||||
revalidatePath,
|
||||
unstable_noStore,
|
||||
};
|
||||
|
||||
export default cacheExports;
|
||||
export { unstable_cache, revalidateTag, revalidatePath, unstable_noStore };
|
@ -0,0 +1,38 @@
|
||||
import { fn } from '@storybook/test';
|
||||
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
|
||||
// We need this import to be a singleton, and because it's used in multiple entrypoints
|
||||
// both in ESM and CJS, importing it via the package name instead of having a local import
|
||||
// is the only way to achieve it actually being a singleton
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore we must ignore types here as during compilation they are not generated yet
|
||||
import { headers } from '@storybook/nextjs/headers.mock';
|
||||
|
||||
class RequestCookiesMock extends RequestCookies {
|
||||
get = fn(super.get.bind(this)).mockName('next/headers::cookies().get');
|
||||
|
||||
getAll = fn(super.getAll.bind(this)).mockName('next/headers::cookies().getAll');
|
||||
|
||||
has = fn(super.has.bind(this)).mockName('next/headers::cookies().has');
|
||||
|
||||
set = fn(super.set.bind(this)).mockName('next/headers::cookies().set');
|
||||
|
||||
delete = fn(super.delete.bind(this)).mockName('next/headers::cookies().delete');
|
||||
}
|
||||
|
||||
let requestCookiesMock: RequestCookiesMock;
|
||||
|
||||
export const cookies = fn(() => {
|
||||
if (!requestCookiesMock) {
|
||||
requestCookiesMock = new RequestCookiesMock(headers());
|
||||
}
|
||||
return requestCookiesMock;
|
||||
}).mockName('next/headers::cookies()');
|
||||
|
||||
const originalRestore = cookies.mockRestore.bind(null);
|
||||
|
||||
// will be called automatically by the test loader
|
||||
cookies.mockRestore = () => {
|
||||
originalRestore();
|
||||
headers.mockRestore();
|
||||
requestCookiesMock = new RequestCookiesMock(headers());
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
import { fn } from '@storybook/test';
|
||||
|
||||
import { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers';
|
||||
|
||||
class HeadersAdapterMock extends HeadersAdapter {
|
||||
constructor() {
|
||||
super({});
|
||||
}
|
||||
|
||||
append = fn(super.append.bind(this)).mockName('next/headers::headers().append');
|
||||
|
||||
delete = fn(super.delete.bind(this)).mockName('next/headers::headers().delete');
|
||||
|
||||
get = fn(super.get.bind(this)).mockName('next/headers::headers().get');
|
||||
|
||||
has = fn(super.has.bind(this)).mockName('next/headers::headers().has');
|
||||
|
||||
set = fn(super.set.bind(this)).mockName('next/headers::headers().set');
|
||||
|
||||
forEach = fn(super.forEach.bind(this)).mockName('next/headers::headers().forEach');
|
||||
|
||||
entries = fn(super.entries.bind(this)).mockName('next/headers::headers().entries');
|
||||
|
||||
keys = fn(super.keys.bind(this)).mockName('next/headers::headers().keys');
|
||||
|
||||
values = fn(super.values.bind(this)).mockName('next/headers::headers().values');
|
||||
}
|
||||
|
||||
let headersAdapterMock: HeadersAdapterMock;
|
||||
|
||||
export const headers = () => {
|
||||
if (!headersAdapterMock) headersAdapterMock = new HeadersAdapterMock();
|
||||
return headersAdapterMock;
|
||||
};
|
||||
|
||||
// This fn is called by ./cookies to restore the headers in the right order
|
||||
headers.mockRestore = () => {
|
||||
headersAdapterMock = new HeadersAdapterMock();
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
import { fn } from '@storybook/test';
|
||||
import * as originalHeaders from 'next/dist/client/components/headers';
|
||||
|
||||
// re-exports of the actual module
|
||||
export * from 'next/dist/client/components/headers';
|
||||
|
||||
// mock utilities/overrides (as of Next v14.2.0)
|
||||
export { headers } from './headers';
|
||||
export { cookies } from './cookies';
|
||||
|
||||
// passthrough mocks - keep original implementation but allow for spying
|
||||
const draftMode = fn(originalHeaders.draftMode).mockName('draftMode');
|
||||
export { draftMode };
|
@ -0,0 +1,94 @@
|
||||
import type { Mock } from '@storybook/test';
|
||||
import { fn } from '@storybook/test';
|
||||
import * as actual from 'next/dist/client/components/navigation';
|
||||
import { NextjsRouterMocksNotAvailable } from 'storybook/internal/preview-errors';
|
||||
import { RedirectStatusCode } from 'next/dist/client/components/redirect-status-code';
|
||||
import { getRedirectError } from 'next/dist/client/components/redirect';
|
||||
|
||||
let navigationAPI: {
|
||||
push: Mock;
|
||||
replace: Mock;
|
||||
forward: Mock;
|
||||
back: Mock;
|
||||
prefetch: Mock;
|
||||
refresh: Mock;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a next/navigation router API mock. Used internally.
|
||||
* @ignore
|
||||
* @internal
|
||||
* */
|
||||
export const createNavigation = (overrides: any) => {
|
||||
const navigationActions = {
|
||||
push: fn().mockName('next/navigation::useRouter().push'),
|
||||
replace: fn().mockName('next/navigation::useRouter().replace'),
|
||||
forward: fn().mockName('next/navigation::useRouter().forward'),
|
||||
back: fn().mockName('next/navigation::useRouter().back'),
|
||||
prefetch: fn().mockName('next/navigation::useRouter().prefetch'),
|
||||
refresh: fn().mockName('next/navigation::useRouter().refresh'),
|
||||
};
|
||||
|
||||
if (overrides) {
|
||||
Object.keys(navigationActions).forEach((key) => {
|
||||
if (key in overrides) {
|
||||
(navigationActions as any)[key] = fn((...args: any[]) => {
|
||||
return (overrides as any)[key](...args);
|
||||
}).mockName(`useRouter().${key}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
navigationAPI = navigationActions;
|
||||
|
||||
return navigationAPI;
|
||||
};
|
||||
|
||||
export const getRouter = () => {
|
||||
if (!navigationAPI) {
|
||||
throw new NextjsRouterMocksNotAvailable({
|
||||
importType: 'next/navigation',
|
||||
});
|
||||
}
|
||||
|
||||
return navigationAPI;
|
||||
};
|
||||
|
||||
// re-exports of the actual module
|
||||
export * from 'next/dist/client/components/navigation';
|
||||
|
||||
// mock utilities/overrides (as of Next v14.2.0)
|
||||
export const redirect = fn(
|
||||
(url: string, type: actual.RedirectType = actual.RedirectType.push): never => {
|
||||
throw getRedirectError(url, type, RedirectStatusCode.SeeOther);
|
||||
}
|
||||
).mockName('next/navigation::redirect');
|
||||
|
||||
export const permanentRedirect = fn(
|
||||
(url: string, type: actual.RedirectType = actual.RedirectType.push): never => {
|
||||
throw getRedirectError(url, type, RedirectStatusCode.SeeOther);
|
||||
}
|
||||
).mockName('next/navigation::permanentRedirect');
|
||||
|
||||
// passthrough mocks - keep original implementation but allow for spying
|
||||
export const useSearchParams = fn(actual.useSearchParams).mockName(
|
||||
'next/navigation::useSearchParams'
|
||||
);
|
||||
export const usePathname = fn(actual.usePathname).mockName('next/navigation::usePathname');
|
||||
export const useSelectedLayoutSegment = fn(actual.useSelectedLayoutSegment).mockName(
|
||||
'next/navigation::useSelectedLayoutSegment'
|
||||
);
|
||||
export const useSelectedLayoutSegments = fn(actual.useSelectedLayoutSegments).mockName(
|
||||
'next/navigation::useSelectedLayoutSegments'
|
||||
);
|
||||
export const useRouter = fn(actual.useRouter).mockName('next/navigation::useRouter');
|
||||
export const useServerInsertedHTML = fn(actual.useServerInsertedHTML).mockName(
|
||||
'next/navigation::useServerInsertedHTML'
|
||||
);
|
||||
export const notFound = fn(actual.notFound).mockName('next/navigation::notFound');
|
||||
|
||||
// Params, not exported by Next.js, is manually declared to avoid inference issues.
|
||||
interface Params {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
export const useParams = fn<[], Params>(actual.useParams).mockName('next/navigation::useParams');
|
115
code/frameworks/nextjs-vite/src/export-mocks/router/index.ts
Normal file
115
code/frameworks/nextjs-vite/src/export-mocks/router/index.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import type { Mock } from '@storybook/test';
|
||||
import { fn } from '@storybook/test';
|
||||
import { NextjsRouterMocksNotAvailable } from 'storybook/internal/preview-errors';
|
||||
import type { NextRouter, SingletonRouter } from 'next/router';
|
||||
import singletonRouter, * as originalRouter from 'next/dist/client/router';
|
||||
|
||||
const defaultRouterState = {
|
||||
route: '/',
|
||||
asPath: '/',
|
||||
basePath: '/',
|
||||
pathname: '/',
|
||||
query: {},
|
||||
isFallback: false,
|
||||
isLocaleDomain: false,
|
||||
isReady: true,
|
||||
isPreview: false,
|
||||
};
|
||||
|
||||
let routerAPI: {
|
||||
push: Mock;
|
||||
replace: Mock;
|
||||
reload: Mock;
|
||||
back: Mock;
|
||||
forward: Mock;
|
||||
prefetch: Mock;
|
||||
beforePopState: Mock;
|
||||
events: {
|
||||
on: Mock;
|
||||
off: Mock;
|
||||
emit: Mock;
|
||||
};
|
||||
} & typeof defaultRouterState;
|
||||
|
||||
/**
|
||||
* Creates a next/router router API mock. Used internally.
|
||||
* @ignore
|
||||
* @internal
|
||||
* */
|
||||
export const createRouter = (overrides: Partial<NextRouter>) => {
|
||||
const routerActions: Partial<NextRouter> = {
|
||||
push: fn((..._args: any[]) => {
|
||||
return Promise.resolve(true);
|
||||
}).mockName('next/router::useRouter().push'),
|
||||
replace: fn((..._args: any[]) => {
|
||||
return Promise.resolve(true);
|
||||
}).mockName('next/router::useRouter().replace'),
|
||||
reload: fn((..._args: any[]) => {}).mockName('next/router::useRouter().reload'),
|
||||
back: fn((..._args: any[]) => {}).mockName('next/router::useRouter().back'),
|
||||
forward: fn(() => {}).mockName('next/router::useRouter().forward'),
|
||||
prefetch: fn((..._args: any[]) => {
|
||||
return Promise.resolve();
|
||||
}).mockName('next/router::useRouter().prefetch'),
|
||||
beforePopState: fn((..._args: any[]) => {}).mockName('next/router::useRouter().beforePopState'),
|
||||
};
|
||||
|
||||
const routerEvents: NextRouter['events'] = {
|
||||
on: fn((..._args: any[]) => {}).mockName('next/router::useRouter().events.on'),
|
||||
off: fn((..._args: any[]) => {}).mockName('next/router::useRouter().events.off'),
|
||||
emit: fn((..._args: any[]) => {}).mockName('next/router::useRouter().events.emit'),
|
||||
};
|
||||
|
||||
if (overrides) {
|
||||
Object.keys(routerActions).forEach((key) => {
|
||||
if (key in overrides) {
|
||||
(routerActions as any)[key] = fn((...args: any[]) => {
|
||||
return (overrides as any)[key](...args);
|
||||
}).mockName(`useRouter().${key}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (overrides?.events) {
|
||||
Object.keys(routerEvents).forEach((key) => {
|
||||
if (key in routerEvents) {
|
||||
(routerEvents as any)[key] = fn((...args: any[]) => {
|
||||
return (overrides.events as any)[key](...args);
|
||||
}).mockName(`useRouter().events.${key}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
routerAPI = {
|
||||
...defaultRouterState,
|
||||
...overrides,
|
||||
...routerActions,
|
||||
// @ts-expect-error TODO improve typings
|
||||
events: routerEvents,
|
||||
};
|
||||
|
||||
// overwrite the singleton router from next/router
|
||||
(singletonRouter as unknown as SingletonRouter).router = routerAPI as any;
|
||||
(singletonRouter as unknown as SingletonRouter).readyCallbacks.forEach((cb) => cb());
|
||||
(singletonRouter as unknown as SingletonRouter).readyCallbacks = [];
|
||||
|
||||
return routerAPI as unknown as NextRouter;
|
||||
};
|
||||
|
||||
export const getRouter = () => {
|
||||
if (!routerAPI) {
|
||||
throw new NextjsRouterMocksNotAvailable({
|
||||
importType: 'next/router',
|
||||
});
|
||||
}
|
||||
|
||||
return routerAPI;
|
||||
};
|
||||
|
||||
// re-exports of the actual module
|
||||
export * from 'next/dist/client/router';
|
||||
export default singletonRouter;
|
||||
|
||||
// mock utilities/overrides (as of Next v14.2.0)
|
||||
// passthrough mocks - keep original implementation but allow for spying
|
||||
export const useRouter = fn(originalRouter.useRouter).mockName('next/router::useRouter');
|
||||
export const withRouter = fn(originalRouter.withRouter).mockName('next/router::withRouter');
|
10
code/frameworks/nextjs-vite/src/head-manager/decorator.tsx
Normal file
10
code/frameworks/nextjs-vite/src/head-manager/decorator.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import HeadManagerProvider from './head-manager-provider';
|
||||
|
||||
export const HeadManagerDecorator = (Story: React.FC): React.ReactNode => {
|
||||
return (
|
||||
<HeadManagerProvider>
|
||||
<Story />
|
||||
</HeadManagerProvider>
|
||||
);
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { HeadManagerContext } from 'next/dist/shared/lib/head-manager-context.shared-runtime';
|
||||
import initHeadManager from 'next/dist/client/head-manager';
|
||||
|
||||
type HeadManagerValue = {
|
||||
updateHead?: ((state: JSX.Element[]) => void) | undefined;
|
||||
mountedInstances?: Set<unknown>;
|
||||
updateScripts?: ((state: any) => void) | undefined;
|
||||
scripts?: any;
|
||||
getIsSsr?: () => boolean;
|
||||
appDir?: boolean | undefined;
|
||||
nonce?: string | undefined;
|
||||
};
|
||||
|
||||
const HeadManagerProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const headManager: HeadManagerValue = useMemo(initHeadManager, []);
|
||||
headManager.getIsSsr = () => false;
|
||||
|
||||
return <HeadManagerContext.Provider value={headManager}>{children}</HeadManagerContext.Provider>;
|
||||
};
|
||||
|
||||
export default HeadManagerProvider;
|
19
code/frameworks/nextjs-vite/src/images/decorator.tsx
Normal file
19
code/frameworks/nextjs-vite/src/images/decorator.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Addon_StoryContext } from 'storybook/internal/types';
|
||||
|
||||
import { ImageContext } from 'sb-original/image-context';
|
||||
|
||||
export const ImageDecorator = (
|
||||
Story: React.FC,
|
||||
{ parameters }: Addon_StoryContext
|
||||
): React.ReactNode => {
|
||||
if (!parameters.nextjs?.image) {
|
||||
return <Story />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ImageContext.Provider value={parameters.nextjs.image}>
|
||||
<Story />
|
||||
</ImageContext.Provider>
|
||||
);
|
||||
};
|
2
code/frameworks/nextjs-vite/src/index.ts
Normal file
2
code/frameworks/nextjs-vite/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export * from './portable-stories';
|
132
code/frameworks/nextjs-vite/src/portable-stories.ts
Normal file
132
code/frameworks/nextjs-vite/src/portable-stories.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import {
|
||||
composeStory as originalComposeStory,
|
||||
composeStories as originalComposeStories,
|
||||
setProjectAnnotations as originalSetProjectAnnotations,
|
||||
composeConfigs,
|
||||
} from 'storybook/internal/preview-api';
|
||||
import type {
|
||||
Args,
|
||||
ProjectAnnotations,
|
||||
StoryAnnotationsOrFn,
|
||||
Store_CSFExports,
|
||||
StoriesWithPartialProps,
|
||||
NamedOrDefaultProjectAnnotations,
|
||||
ComposedStoryFn,
|
||||
} from 'storybook/internal/types';
|
||||
|
||||
// ! ATTENTION: This needs to be a relative import so it gets prebundled. This is to avoid ESM issues in Nextjs + Jest setups
|
||||
import { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS as reactAnnotations } from '../../../renderers/react/src/portable-stories';
|
||||
import * as rscAnnotations from '../../../renderers/react/src/entry-preview-rsc';
|
||||
import * as nextJsAnnotations from './preview';
|
||||
|
||||
import type { ReactRenderer, Meta } from '@storybook/react';
|
||||
|
||||
/** Function that sets the globalConfig of your storybook. The global config is the preview module of your .storybook folder.
|
||||
*
|
||||
* It should be run a single time, so that your global config (e.g. decorators) is applied to your stories when using `composeStories` or `composeStory`.
|
||||
*
|
||||
* Example:
|
||||
*```jsx
|
||||
* // setup.js (for jest)
|
||||
* import { setProjectAnnotations } from '@storybook/nextjs';
|
||||
* import projectAnnotations from './.storybook/preview';
|
||||
*
|
||||
* setProjectAnnotations(projectAnnotations);
|
||||
*```
|
||||
*
|
||||
* @param projectAnnotations - e.g. (import projectAnnotations from '../.storybook/preview')
|
||||
*/
|
||||
export function setProjectAnnotations(
|
||||
projectAnnotations:
|
||||
| NamedOrDefaultProjectAnnotations<ReactRenderer>
|
||||
| NamedOrDefaultProjectAnnotations<ReactRenderer>[]
|
||||
): ProjectAnnotations<ReactRenderer> {
|
||||
return originalSetProjectAnnotations<ReactRenderer>(projectAnnotations);
|
||||
}
|
||||
|
||||
// This will not be necessary once we have auto preset loading
|
||||
const defaultProjectAnnotations: ProjectAnnotations<ReactRenderer> = composeConfigs([
|
||||
reactAnnotations,
|
||||
rscAnnotations,
|
||||
nextJsAnnotations,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Function that will receive a story along with meta (e.g. a default export from a .stories file)
|
||||
* and optionally projectAnnotations e.g. (import * from '../.storybook/preview)
|
||||
* and will return a composed component that has all args/parameters/decorators/etc combined and applied to it.
|
||||
*
|
||||
*
|
||||
* It's very useful for reusing a story in scenarios outside of Storybook like unit testing.
|
||||
*
|
||||
* Example:
|
||||
*```jsx
|
||||
* import { render } from '@testing-library/react';
|
||||
* import { composeStory } from '@storybook/nextjs';
|
||||
* import Meta, { Primary as PrimaryStory } from './Button.stories';
|
||||
*
|
||||
* const Primary = composeStory(PrimaryStory, Meta);
|
||||
*
|
||||
* test('renders primary button with Hello World', () => {
|
||||
* const { getByText } = render(<Primary>Hello world</Primary>);
|
||||
* expect(getByText(/Hello world/i)).not.toBeNull();
|
||||
* });
|
||||
*```
|
||||
*
|
||||
* @param story
|
||||
* @param componentAnnotations - e.g. (import Meta from './Button.stories')
|
||||
* @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setProjectAnnotations` in your setup files.
|
||||
* @param [exportsName] - in case your story does not contain a name and you want it to have a name.
|
||||
*/
|
||||
export function composeStory<TArgs extends Args = Args>(
|
||||
story: StoryAnnotationsOrFn<ReactRenderer, TArgs>,
|
||||
componentAnnotations: Meta<TArgs | any>,
|
||||
projectAnnotations?: ProjectAnnotations<ReactRenderer>,
|
||||
exportsName?: string
|
||||
): ComposedStoryFn<ReactRenderer, Partial<TArgs>> {
|
||||
return originalComposeStory<ReactRenderer, TArgs>(
|
||||
story as StoryAnnotationsOrFn<ReactRenderer, Args>,
|
||||
componentAnnotations,
|
||||
projectAnnotations,
|
||||
defaultProjectAnnotations,
|
||||
exportsName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that will receive a stories import (e.g. `import * as stories from './Button.stories'`)
|
||||
* and optionally projectAnnotations (e.g. `import * from '../.storybook/preview`)
|
||||
* and will return an object containing all the stories passed, but now as a composed component that has all args/parameters/decorators/etc combined and applied to it.
|
||||
*
|
||||
*
|
||||
* It's very useful for reusing stories in scenarios outside of Storybook like unit testing.
|
||||
*
|
||||
* Example:
|
||||
*```jsx
|
||||
* import { render } from '@testing-library/react';
|
||||
* import { composeStories } from '@storybook/nextjs';
|
||||
* import * as stories from './Button.stories';
|
||||
*
|
||||
* const { Primary, Secondary } = composeStories(stories);
|
||||
*
|
||||
* test('renders primary button with Hello World', () => {
|
||||
* const { getByText } = render(<Primary>Hello world</Primary>);
|
||||
* expect(getByText(/Hello world/i)).not.toBeNull();
|
||||
* });
|
||||
*```
|
||||
*
|
||||
* @param csfExports - e.g. (import * as stories from './Button.stories')
|
||||
* @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setProjectAnnotations` in your setup files.
|
||||
*/
|
||||
export function composeStories<TModule extends Store_CSFExports<ReactRenderer, any>>(
|
||||
csfExports: TModule,
|
||||
projectAnnotations?: ProjectAnnotations<ReactRenderer>
|
||||
) {
|
||||
// @ts-expect-error (Converted from ts-ignore)
|
||||
const composedStories = originalComposeStories(csfExports, projectAnnotations, composeStory);
|
||||
|
||||
return composedStories as unknown as Omit<
|
||||
StoriesWithPartialProps<ReactRenderer, TModule>,
|
||||
keyof Store_CSFExports
|
||||
>;
|
||||
}
|
43
code/frameworks/nextjs-vite/src/preset.ts
Normal file
43
code/frameworks/nextjs-vite/src/preset.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// https://storybook.js.org/docs/react/addons/writing-presets
|
||||
import type { StorybookConfigVite } from '@storybook/builder-vite';
|
||||
import { dirname, join } from 'path';
|
||||
import type { PresetProperty } from 'storybook/internal/types';
|
||||
import vitePluginStorybookNextjs from 'vite-plugin-storybook-nextjs';
|
||||
import type { StorybookConfig } from './types';
|
||||
|
||||
export const core: PresetProperty<'core'> = async (config, options) => {
|
||||
const framework = await options.presets.apply('framework');
|
||||
|
||||
return {
|
||||
...config,
|
||||
builder: {
|
||||
name: dirname(
|
||||
require.resolve(join('@storybook/builder-vite', 'package.json'))
|
||||
) as '@storybook/builder-vite',
|
||||
options: {
|
||||
...(typeof framework === 'string' ? {} : framework.options.builder || {}),
|
||||
},
|
||||
},
|
||||
renderer: dirname(require.resolve(join('@storybook/react', 'package.json'))),
|
||||
};
|
||||
};
|
||||
|
||||
export const previewAnnotations: PresetProperty<'previewAnnotations'> = (entry = []) => {
|
||||
const nextDir = dirname(require.resolve('@storybook/nextjs-vite/package.json'));
|
||||
const result = [...entry, join(nextDir, 'dist/preview.mjs')];
|
||||
return result;
|
||||
};
|
||||
|
||||
export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, options) => {
|
||||
config.plugins = config.plugins || [];
|
||||
const framework = (await options.presets.apply(
|
||||
'framework',
|
||||
{},
|
||||
options
|
||||
)) as StorybookConfig['framework'];
|
||||
|
||||
const nextAppDir = typeof framework !== 'string' ? framework.options.nextAppDir : undefined;
|
||||
config.plugins.push(vitePluginStorybookNextjs({ dir: nextAppDir }));
|
||||
|
||||
return config;
|
||||
};
|
83
code/frameworks/nextjs-vite/src/preview.tsx
Normal file
83
code/frameworks/nextjs-vite/src/preview.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import type { Addon_DecoratorFunction, Addon_LoaderFunction } from 'storybook/internal/types';
|
||||
import './config/preview';
|
||||
import { ImageDecorator } from './images/decorator';
|
||||
import { RouterDecorator } from './routing/decorator';
|
||||
import { StyledJsxDecorator } from './styledJsx/decorator';
|
||||
import { HeadManagerDecorator } from './head-manager/decorator';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore we must ignore types here as during compilation they are not generated yet
|
||||
import { createRouter } from '@storybook/nextjs-vite/router.mock';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore we must ignore types here as during compilation they are not generated yet
|
||||
import { createNavigation } from '@storybook/nextjs-vite/navigation.mock';
|
||||
import { isNextRouterError } from 'next/dist/client/components/is-next-router-error';
|
||||
|
||||
function addNextHeadCount() {
|
||||
const meta = document.createElement('meta');
|
||||
meta.name = 'next-head-count';
|
||||
meta.content = '0';
|
||||
document.head.appendChild(meta);
|
||||
}
|
||||
|
||||
function isAsyncClientComponentError(error: unknown) {
|
||||
return (
|
||||
typeof error === 'string' &&
|
||||
(error.includes('A component was suspended by an uncached promise.') ||
|
||||
error.includes('async/await is not yet supported in Client Components'))
|
||||
);
|
||||
}
|
||||
addNextHeadCount();
|
||||
|
||||
// Copying Next patch of console.error:
|
||||
// https://github.com/vercel/next.js/blob/a74deb63e310df473583ab6f7c1783bc609ca236/packages/next/src/client/app-index.tsx#L15
|
||||
const origConsoleError = globalThis.console.error;
|
||||
globalThis.console.error = (...args: unknown[]) => {
|
||||
const error = args[0];
|
||||
if (isNextRouterError(error) || isAsyncClientComponentError(error)) {
|
||||
return;
|
||||
}
|
||||
origConsoleError.apply(globalThis.console, args);
|
||||
};
|
||||
|
||||
globalThis.addEventListener('error', (ev: WindowEventMap['error']): void => {
|
||||
if (isNextRouterError(ev.error) || isAsyncClientComponentError(ev.error)) {
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
export const decorators: Addon_DecoratorFunction<any>[] = [
|
||||
StyledJsxDecorator,
|
||||
ImageDecorator,
|
||||
RouterDecorator,
|
||||
HeadManagerDecorator,
|
||||
];
|
||||
|
||||
export const loaders: Addon_LoaderFunction = async ({ globals, parameters }) => {
|
||||
const { router, appDirectory } = parameters.nextjs ?? {};
|
||||
if (appDirectory) {
|
||||
createNavigation(router);
|
||||
} else {
|
||||
createRouter({
|
||||
locale: globals.locale,
|
||||
...router,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const parameters = {
|
||||
docs: {
|
||||
source: {
|
||||
excludeDecorators: true,
|
||||
},
|
||||
},
|
||||
react: {
|
||||
rootOptions: {
|
||||
onCaughtError(error: unknown) {
|
||||
if (isNextRouterError(error)) return;
|
||||
console.error(error);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
112
code/frameworks/nextjs-vite/src/routing/app-router-provider.tsx
Normal file
112
code/frameworks/nextjs-vite/src/routing/app-router-provider.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
LayoutRouterContext,
|
||||
AppRouterContext,
|
||||
GlobalLayoutRouterContext,
|
||||
} from 'next/dist/shared/lib/app-router-context.shared-runtime';
|
||||
import {
|
||||
PathnameContext,
|
||||
SearchParamsContext,
|
||||
PathParamsContext,
|
||||
} from 'next/dist/shared/lib/hooks-client-context.shared-runtime';
|
||||
import { type Params } from 'next/dist/shared/lib/router/utils/route-matcher';
|
||||
import { PAGE_SEGMENT_KEY } from 'next/dist/shared/lib/segment';
|
||||
import type { FlightRouterState } from 'next/dist/server/app-render/types';
|
||||
import type { RouteParams } from './types';
|
||||
// We need this import to be a singleton, and because it's used in multiple entrypoints
|
||||
// both in ESM and CJS, importing it via the package name instead of having a local import
|
||||
// is the only way to achieve it actually being a singleton
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore we must ignore types here as during compilation they are not generated yet
|
||||
import { getRouter } from '@storybook/nextjs-vite/navigation.mock';
|
||||
|
||||
type AppRouterProviderProps = {
|
||||
routeParams: RouteParams;
|
||||
};
|
||||
|
||||
// Since Next 14.2.x
|
||||
// https://github.com/vercel/next.js/pull/60708/files#diff-7b6239af735eba0c401e1a0db1a04dd4575c19a031934f02d128cf3ac813757bR106
|
||||
function getSelectedParams(currentTree: FlightRouterState, params: Params = {}): Params {
|
||||
const parallelRoutes = currentTree[1];
|
||||
|
||||
for (const parallelRoute of Object.values(parallelRoutes)) {
|
||||
const segment = parallelRoute[0];
|
||||
const isDynamicParameter = Array.isArray(segment);
|
||||
const segmentValue = isDynamicParameter ? segment[1] : segment;
|
||||
if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) continue;
|
||||
|
||||
// Ensure catchAll and optional catchall are turned into an array
|
||||
const isCatchAll = isDynamicParameter && (segment[2] === 'c' || segment[2] === 'oc');
|
||||
|
||||
if (isCatchAll) {
|
||||
params[segment[0]] = segment[1].split('/');
|
||||
} else if (isDynamicParameter) {
|
||||
params[segment[0]] = segment[1];
|
||||
}
|
||||
|
||||
params = getSelectedParams(parallelRoute, params);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
const getParallelRoutes = (segmentsList: Array<string>): FlightRouterState => {
|
||||
const segment = segmentsList.shift();
|
||||
|
||||
if (segment) {
|
||||
return [segment, { children: getParallelRoutes(segmentsList) }];
|
||||
}
|
||||
|
||||
return [] as any;
|
||||
};
|
||||
|
||||
export const AppRouterProvider: React.FC<React.PropsWithChildren<AppRouterProviderProps>> = ({
|
||||
children,
|
||||
routeParams,
|
||||
}) => {
|
||||
const { pathname, query, segments = [] } = routeParams;
|
||||
|
||||
const tree: FlightRouterState = [pathname, { children: getParallelRoutes([...segments]) }];
|
||||
const pathParams = useMemo(() => {
|
||||
return getSelectedParams(tree);
|
||||
}, [tree]);
|
||||
|
||||
// https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/app-router.tsx#L436
|
||||
return (
|
||||
<PathParamsContext.Provider value={pathParams}>
|
||||
<PathnameContext.Provider value={pathname}>
|
||||
<SearchParamsContext.Provider value={new URLSearchParams(query)}>
|
||||
<GlobalLayoutRouterContext.Provider
|
||||
value={{
|
||||
changeByServerResponse() {
|
||||
// NOOP
|
||||
},
|
||||
buildId: 'storybook',
|
||||
tree,
|
||||
focusAndScrollRef: {
|
||||
apply: false,
|
||||
hashFragment: null,
|
||||
segmentPaths: [tree],
|
||||
onlyHashChange: false,
|
||||
},
|
||||
nextUrl: pathname,
|
||||
}}
|
||||
>
|
||||
<AppRouterContext.Provider value={getRouter()}>
|
||||
<LayoutRouterContext.Provider
|
||||
value={{
|
||||
childNodes: new Map(),
|
||||
loading: null,
|
||||
tree,
|
||||
url: pathname,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LayoutRouterContext.Provider>
|
||||
</AppRouterContext.Provider>
|
||||
</GlobalLayoutRouterContext.Provider>
|
||||
</SearchParamsContext.Provider>
|
||||
</PathnameContext.Provider>
|
||||
</PathParamsContext.Provider>
|
||||
);
|
||||
};
|
48
code/frameworks/nextjs-vite/src/routing/decorator.tsx
Normal file
48
code/frameworks/nextjs-vite/src/routing/decorator.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import type { Addon_StoryContext } from 'storybook/internal/types';
|
||||
import { AppRouterProvider } from './app-router-provider';
|
||||
import { PageRouterProvider } from './page-router-provider';
|
||||
import type { RouteParams, NextAppDirectory } from './types';
|
||||
import { RedirectBoundary } from 'next/dist/client/components/redirect-boundary';
|
||||
|
||||
const defaultRouterParams: RouteParams = {
|
||||
pathname: '/',
|
||||
query: {},
|
||||
};
|
||||
|
||||
export const RouterDecorator = (
|
||||
Story: React.FC,
|
||||
{ parameters }: Addon_StoryContext
|
||||
): React.ReactNode => {
|
||||
const nextAppDirectory =
|
||||
(parameters.nextjs?.appDirectory as NextAppDirectory | undefined) ?? false;
|
||||
|
||||
if (nextAppDirectory) {
|
||||
if (!AppRouterProvider) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<AppRouterProvider
|
||||
routeParams={{
|
||||
...defaultRouterParams,
|
||||
...parameters.nextjs?.navigation,
|
||||
}}
|
||||
>
|
||||
{/*
|
||||
The next.js RedirectBoundary causes flashing UI when used client side.
|
||||
Possible use the implementation of the PR: https://github.com/vercel/next.js/pull/49439
|
||||
Or wait for next to solve this on their side.
|
||||
*/}
|
||||
<RedirectBoundary>
|
||||
<Story />
|
||||
</RedirectBoundary>
|
||||
</AppRouterProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageRouterProvider>
|
||||
<Story />
|
||||
</PageRouterProvider>
|
||||
);
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React from 'react';
|
||||
// We need this import to be a singleton, and because it's used in multiple entrypoints
|
||||
// both in ESM and CJS, importing it via the package name instead of having a local import
|
||||
// is the only way to achieve it actually being a singleton
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore we must ignore types here as during compilation they are not generated yet
|
||||
import { getRouter } from '@storybook/nextjs-vite/router.mock';
|
||||
|
||||
export const PageRouterProvider: React.FC<PropsWithChildren> = ({ children }) => (
|
||||
<RouterContext.Provider value={getRouter()}>{children}</RouterContext.Provider>
|
||||
);
|
7
code/frameworks/nextjs-vite/src/routing/types.tsx
Normal file
7
code/frameworks/nextjs-vite/src/routing/types.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
export type RouteParams = {
|
||||
pathname: string;
|
||||
query: Record<string, string>;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type NextAppDirectory = boolean;
|
8
code/frameworks/nextjs-vite/src/styledJsx/decorator.tsx
Normal file
8
code/frameworks/nextjs-vite/src/styledJsx/decorator.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { StyleRegistry } from 'styled-jsx';
|
||||
|
||||
export const StyledJsxDecorator = (Story: React.FC): React.ReactNode => (
|
||||
<StyleRegistry>
|
||||
<Story />
|
||||
</StyleRegistry>
|
||||
);
|
44
code/frameworks/nextjs-vite/src/types.ts
Normal file
44
code/frameworks/nextjs-vite/src/types.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type {
|
||||
CompatibleString,
|
||||
StorybookConfig as StorybookConfigBase,
|
||||
} from 'storybook/internal/types';
|
||||
import type { StorybookConfigVite, BuilderOptions } from '@storybook/builder-vite';
|
||||
|
||||
type FrameworkName = CompatibleString<'@storybook/nextjs-vite'>;
|
||||
type BuilderName = CompatibleString<'@storybook/builder-vite'>;
|
||||
|
||||
export type FrameworkOptions = {
|
||||
/**
|
||||
* The directory where the Next.js app is located.
|
||||
* @default process.cwd()
|
||||
*/
|
||||
nextAppDir?: string;
|
||||
builder?: BuilderOptions;
|
||||
};
|
||||
|
||||
type StorybookConfigFramework = {
|
||||
framework:
|
||||
| FrameworkName
|
||||
| {
|
||||
name: FrameworkName;
|
||||
options: FrameworkOptions;
|
||||
};
|
||||
core?: StorybookConfigBase['core'] & {
|
||||
builder?:
|
||||
| BuilderName
|
||||
| {
|
||||
name: BuilderName;
|
||||
options: BuilderOptions;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface for Storybook configuration in `main.ts` files.
|
||||
*/
|
||||
export type StorybookConfig = Omit<
|
||||
StorybookConfigBase,
|
||||
keyof StorybookConfigVite | keyof StorybookConfigFramework
|
||||
> &
|
||||
StorybookConfigVite &
|
||||
StorybookConfigFramework & {}
|
32
code/frameworks/nextjs-vite/src/typings.d.ts
vendored
Normal file
32
code/frameworks/nextjs-vite/src/typings.d.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
declare module 'sb-original/image-context' {
|
||||
import type { StaticImport } from 'next/dist/shared/lib/get-img-props';
|
||||
import type { Context } from 'next/dist/compiled/react';
|
||||
import type { ImageProps } from 'next/image';
|
||||
import type { ImageProps as LegacyImageProps } from 'next/legacy/image';
|
||||
|
||||
export const ImageContext: Context<
|
||||
Partial<
|
||||
Omit<ImageProps, 'src'> & {
|
||||
src: string | StaticImport;
|
||||
}
|
||||
> &
|
||||
Omit<LegacyImageProps, 'src'>
|
||||
>;
|
||||
}
|
||||
|
||||
declare module 'sb-original/default-loader' {
|
||||
import type { ImageLoaderProps } from 'next/image';
|
||||
|
||||
export const defaultLoader: (props: ImageLoaderProps) => string;
|
||||
}
|
||||
|
||||
declare module 'next/dist/compiled/react' {
|
||||
import * as React from 'react';
|
||||
export default React;
|
||||
export type Context<T> = React.Context<T>;
|
||||
export function createContext<T>(
|
||||
// If you thought this should be optional, see
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24509#issuecomment-382213106
|
||||
defaultValue: T
|
||||
): Context<T>;
|
||||
}
|
7
code/frameworks/nextjs-vite/template/cli/.eslintrc.json
Normal file
7
code/frameworks/nextjs-vite/template/cli/.eslintrc.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"import/extensions": "off",
|
||||
"react/no-unknown-property": "off"
|
||||
}
|
||||
}
|
54
code/frameworks/nextjs-vite/template/cli/js/Button.jsx
Normal file
54
code/frameworks/nextjs-vite/template/cli/js/Button.jsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './button.css';
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
|
||||
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
<style jsx>{`
|
||||
button {
|
||||
background-color: ${backgroundColor};
|
||||
}
|
||||
`}</style>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
Button.propTypes = {
|
||||
/**
|
||||
* Is this the principal call to action on the page?
|
||||
*/
|
||||
primary: PropTypes.bool,
|
||||
/**
|
||||
* What background color to use
|
||||
*/
|
||||
backgroundColor: PropTypes.string,
|
||||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||||
/**
|
||||
* Button contents
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Optional click handler
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
backgroundColor: null,
|
||||
primary: false,
|
||||
size: 'medium',
|
||||
onClick: undefined,
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
import { fn } from '@storybook/test';
|
||||
import { Button } from './Button';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
export default {
|
||||
title: 'Example/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
||||
args: { onClick: fn() },
|
||||
};
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
||||
export const Primary = {
|
||||
args: {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary = {
|
||||
args: {
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Large = {
|
||||
args: {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Small = {
|
||||
args: {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
446
code/frameworks/nextjs-vite/template/cli/js/Configure.mdx
Normal file
446
code/frameworks/nextjs-vite/template/cli/js/Configure.mdx
Normal file
@ -0,0 +1,446 @@
|
||||
import { Meta } from "@storybook/blocks";
|
||||
import Image from "next/image";
|
||||
|
||||
import Github from "./assets/github.svg";
|
||||
import Discord from "./assets/discord.svg";
|
||||
import Youtube from "./assets/youtube.svg";
|
||||
import Tutorials from "./assets/tutorials.svg";
|
||||
import Styling from "./assets/styling.png";
|
||||
import Context from "./assets/context.png";
|
||||
import Assets from "./assets/assets.png";
|
||||
import Docs from "./assets/docs.png";
|
||||
import Share from "./assets/share.png";
|
||||
import FigmaPlugin from "./assets/figma-plugin.png";
|
||||
import Testing from "./assets/testing.png";
|
||||
import Accessibility from "./assets/accessibility.png";
|
||||
import Theming from "./assets/theming.png";
|
||||
import AddonLibrary from "./assets/addon-library.png";
|
||||
|
||||
export const RightArrow = () => <svg
|
||||
viewBox="0 0 14 14"
|
||||
width="8px"
|
||||
height="14px"
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
display: 'inline-block',
|
||||
shapeRendering: 'inherit',
|
||||
verticalAlign: 'middle',
|
||||
fill: 'currentColor',
|
||||
'path fill': 'currentColor'
|
||||
}}
|
||||
>
|
||||
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
|
||||
</svg>
|
||||
|
||||
<Meta title="Configure your project" />
|
||||
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Configure your project
|
||||
|
||||
Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
|
||||
</div>
|
||||
<div className="sb-section">
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
src={Styling}
|
||||
alt="A wall of logos representing different styling technologies"
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Add styling and CSS</h4>
|
||||
<p className="sb-section-item-paragraph">Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Context}
|
||||
alt="An abstraction representing the composition of data for a component"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Provide context and mocking</h4>
|
||||
<p className="sb-section-item-paragraph">Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-stories/decorators/?renderer=react#context-for-mocking"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Assets}
|
||||
alt="A representation of typography and image assets"
|
||||
/>
|
||||
<div>
|
||||
<h4 className="sb-section-item-heading">Load assets and resources</h4>
|
||||
<p className="sb-section-item-paragraph">To link static files (like fonts) to your projects and stories, use the
|
||||
`staticDirs` configuration option to specify folders to load when
|
||||
starting Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/images-and-assets/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Do more with Storybook
|
||||
|
||||
Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
|
||||
</div>
|
||||
|
||||
<div className="sb-section">
|
||||
<div className="sb-features-grid">
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Docs}
|
||||
alt="A screenshot showing the autodocs tag being set, pointing a docs page being generated"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Autodocs</h4>
|
||||
<p className="sb-section-item-paragraph">Auto-generate living,
|
||||
interactive reference documentation from your components and stories.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-docs/autodocs/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Share}
|
||||
alt="A browser window showing a Storybook being published to a chromatic.com URL"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Publish to Chromatic</h4>
|
||||
<p className="sb-section-item-paragraph">Publish your Storybook to review and collaborate with your entire team.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/sharing/publish-storybook/?renderer=react#publish-storybook-with-chromatic"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={FigmaPlugin}
|
||||
alt="Windows showing the Storybook plugin in Figma"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Figma Plugin</h4>
|
||||
<p className="sb-section-item-paragraph">Embed your stories into Figma to cross-reference the design and live
|
||||
implementation in one place.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/sharing/design-integrations/?renderer=react#embed-storybook-in-figma-with-the-plugin"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Testing}
|
||||
alt="Screenshot of tests passing and failing"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Testing</h4>
|
||||
<p className="sb-section-item-paragraph">Use stories to test a component in all its variations, no matter how
|
||||
complex.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-tests/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Accessibility}
|
||||
alt="Screenshot of accessibility tests passing and failing"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Accessibility</h4>
|
||||
<p className="sb-section-item-paragraph">Automatically test your components for a11y issues as you develop.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-tests/accessibility-testing/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Theming}
|
||||
alt="Screenshot of Storybook in light and dark mode"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Theming</h4>
|
||||
<p className="sb-section-item-paragraph">Theme Storybook's UI to personalize it to your project.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/theming/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='sb-addon'>
|
||||
<div className='sb-addon-text'>
|
||||
<h4>Addons</h4>
|
||||
<p className="sb-section-item-paragraph">Integrate your tools with Storybook to connect workflows.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/addons/"
|
||||
target="_blank"
|
||||
>Discover all addons<RightArrow /></a>
|
||||
</div>
|
||||
<div className='sb-addon-img'>
|
||||
<Image
|
||||
width={650}
|
||||
height={347}
|
||||
src={AddonLibrary}
|
||||
alt="Integrate your tools with Storybook to connect workflows."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sb-section sb-socials">
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Github}
|
||||
alt="Github logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
Join our contributors building the future of UI development.
|
||||
|
||||
<a
|
||||
href="https://github.com/storybookjs/storybook"
|
||||
target="_blank"
|
||||
>Star on GitHub<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={33}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Discord}
|
||||
alt="Discord logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<div>
|
||||
Get support and chat with frontend developers.
|
||||
|
||||
<a
|
||||
href="https://discord.gg/storybook"
|
||||
target="_blank"
|
||||
>Join Discord server<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Youtube}
|
||||
alt="Youtube logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<div>
|
||||
Watch tutorials, feature previews and interviews.
|
||||
|
||||
<a
|
||||
href="https://www.youtube.com/@chromaticui"
|
||||
target="_blank"
|
||||
>Watch on YouTube<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={33}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Tutorials}
|
||||
alt="A book"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<p>Follow guided walkthroughs on for key workflows.</p>
|
||||
|
||||
<a
|
||||
href="https://storybook.js.org/tutorials/"
|
||||
target="_blank"
|
||||
>Discover tutorials<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.sb-container {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.sb-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sb-section-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.sb-section a:not(h1 a, h2 a, h3 a) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sb-section-item, .sb-grid-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-section-item-heading {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 5px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.sb-section-item-paragraph {
|
||||
margin: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-chevron {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 32px 20px;
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-explore-image {
|
||||
max-height: 32px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background-color: #EEF3F8;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: #EEF3F8;
|
||||
height: 180px;
|
||||
margin-bottom: 48px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 48px;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.sb-addon-text h4 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
position: absolute;
|
||||
left: 345px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 650px;
|
||||
transform: rotate(-15deg);
|
||||
margin-left: 40px;
|
||||
margin-top: -72px;
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.sb-addon-img {
|
||||
left: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.sb-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
height: 280px;
|
||||
align-items: flex-start;
|
||||
padding-top: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 130px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
width: 124%;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 1200px;
|
||||
transform: rotate(-12deg);
|
||||
margin-left: 0;
|
||||
margin-top: 48px;
|
||||
margin-bottom: -40px;
|
||||
margin-left: -24px;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
59
code/frameworks/nextjs-vite/template/cli/js/Header.jsx
Normal file
59
code/frameworks/nextjs-vite/template/cli/js/Header.jsx
Normal file
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button } from './Button';
|
||||
import './header.css';
|
||||
|
||||
export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (
|
||||
<header>
|
||||
<div className="storybook-header">
|
||||
<div>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
|
||||
fill="#555AB9"
|
||||
/>
|
||||
<path
|
||||
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
|
||||
fill="#91BAF8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<h1>Acme</h1>
|
||||
</div>
|
||||
<div>
|
||||
{user ? (
|
||||
<>
|
||||
<span className="welcome">
|
||||
Welcome, <b>{user.name}</b>!
|
||||
</span>
|
||||
<Button size="small" onClick={onLogout} label="Log out" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="small" onClick={onLogin} label="Log in" />
|
||||
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
Header.propTypes = {
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
}),
|
||||
onLogin: PropTypes.func.isRequired,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
onCreateAccount: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Header.defaultProps = {
|
||||
user: null,
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
import { fn } from '@storybook/test';
|
||||
import { Header } from './Header';
|
||||
|
||||
export default {
|
||||
title: 'Example/Header',
|
||||
component: Header,
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {
|
||||
onLogin: fn(),
|
||||
onLogout: fn(),
|
||||
onCreateAccount: fn(),
|
||||
},
|
||||
};
|
||||
export const LoggedIn = {
|
||||
args: {
|
||||
user: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut = {
|
||||
args: {},
|
||||
};
|
68
code/frameworks/nextjs-vite/template/cli/js/Page.jsx
Normal file
68
code/frameworks/nextjs-vite/template/cli/js/Page.jsx
Normal file
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Header } from './Header';
|
||||
import './page.css';
|
||||
|
||||
export const Page = () => {
|
||||
const [user, setUser] = React.useState();
|
||||
|
||||
return (
|
||||
<article>
|
||||
<Header
|
||||
user={user}
|
||||
onLogin={() => setUser({ name: 'Jane Doe' })}
|
||||
onLogout={() => setUser(undefined)}
|
||||
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
|
||||
/>
|
||||
<section className="storybook-page">
|
||||
<h2>Pages in Storybook</h2>
|
||||
<p>
|
||||
We recommend building UIs with a{' '}
|
||||
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
|
||||
<strong>component-driven</strong>
|
||||
</a>{' '}
|
||||
process starting with atomic components and ending with pages.
|
||||
</p>
|
||||
<p>
|
||||
Render pages with mock data. This makes it easy to build and review page states without
|
||||
needing to navigate to them in your app. Here are some handy patterns for managing page
|
||||
data in Storybook:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Use a higher-level connected component. Storybook helps you compose such data from the
|
||||
"args" of child component stories
|
||||
</li>
|
||||
<li>
|
||||
Assemble data in the page component from your services. You can mock these services out
|
||||
using Storybook.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Get a guided tutorial on component-driven development at{' '}
|
||||
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
|
||||
Storybook tutorials
|
||||
</a>
|
||||
. Read more in the{' '}
|
||||
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
|
||||
docs
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
||||
id="a"
|
||||
fill="#999"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
Viewports addon in the toolbar
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
};
|
27
code/frameworks/nextjs-vite/template/cli/js/Page.stories.js
Normal file
27
code/frameworks/nextjs-vite/template/cli/js/Page.stories.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { within, userEvent, expect } from '@storybook/test';
|
||||
import { Page } from './Page';
|
||||
|
||||
export default {
|
||||
title: 'Example/Page',
|
||||
component: Page,
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut = {};
|
||||
|
||||
// More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testing
|
||||
export const LoggedIn = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const loginButton = canvas.getByRole('button', { name: /Log in/i });
|
||||
await expect(loginButton).toBeInTheDocument();
|
||||
await userEvent.click(loginButton);
|
||||
await expect(loginButton).not.toBeInTheDocument();
|
||||
|
||||
const logoutButton = canvas.getByRole('button', { name: /Log out/i });
|
||||
await expect(logoutButton).toBeInTheDocument();
|
||||
},
|
||||
};
|
@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn } from '@storybook/test';
|
||||
import { Button } from './Button';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
const meta: Meta<typeof Button> = {
|
||||
title: 'Example/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
||||
args: { onClick: fn() },
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Button>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
52
code/frameworks/nextjs-vite/template/cli/ts-3-8/Button.tsx
Normal file
52
code/frameworks/nextjs-vite/template/cli/ts-3-8/Button.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import './button.css';
|
||||
|
||||
export interface ButtonProps {
|
||||
/**
|
||||
* Is this the principal call to action on the page?
|
||||
*/
|
||||
primary?: boolean;
|
||||
/**
|
||||
* What background color to use
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
/**
|
||||
* Button contents
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Optional click handler
|
||||
*/
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({
|
||||
primary = false,
|
||||
size = 'medium',
|
||||
backgroundColor,
|
||||
label,
|
||||
...props
|
||||
}: ButtonProps) => {
|
||||
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
<style jsx>{`
|
||||
button {
|
||||
background-color: ${backgroundColor};
|
||||
}
|
||||
`}</style>
|
||||
</button>
|
||||
);
|
||||
};
|
446
code/frameworks/nextjs-vite/template/cli/ts-3-8/Configure.mdx
Normal file
446
code/frameworks/nextjs-vite/template/cli/ts-3-8/Configure.mdx
Normal file
@ -0,0 +1,446 @@
|
||||
import { Meta } from "@storybook/blocks";
|
||||
import Image from "next/image";
|
||||
|
||||
import Github from "./assets/github.svg";
|
||||
import Discord from "./assets/discord.svg";
|
||||
import Youtube from "./assets/youtube.svg";
|
||||
import Tutorials from "./assets/tutorials.svg";
|
||||
import Styling from "./assets/styling.png";
|
||||
import Context from "./assets/context.png";
|
||||
import Assets from "./assets/assets.png";
|
||||
import Docs from "./assets/docs.png";
|
||||
import Share from "./assets/share.png";
|
||||
import FigmaPlugin from "./assets/figma-plugin.png";
|
||||
import Testing from "./assets/testing.png";
|
||||
import Accessibility from "./assets/accessibility.png";
|
||||
import Theming from "./assets/theming.png";
|
||||
import AddonLibrary from "./assets/addon-library.png";
|
||||
|
||||
export const RightArrow = () => <svg
|
||||
viewBox="0 0 14 14"
|
||||
width="8px"
|
||||
height="14px"
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
display: 'inline-block',
|
||||
shapeRendering: 'inherit',
|
||||
verticalAlign: 'middle',
|
||||
fill: 'currentColor',
|
||||
'path fill': 'currentColor'
|
||||
}}
|
||||
>
|
||||
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
|
||||
</svg>
|
||||
|
||||
<Meta title="Configure your project" />
|
||||
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Configure your project
|
||||
|
||||
Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
|
||||
</div>
|
||||
<div className="sb-section">
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
src={Styling}
|
||||
alt="A wall of logos representing different styling technologies"
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Add styling and CSS</h4>
|
||||
<p className="sb-section-item-paragraph">Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Context}
|
||||
alt="An abstraction representing the composition of data for a component"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Provide context and mocking</h4>
|
||||
<p className="sb-section-item-paragraph">Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-stories/decorators/?renderer=react#context-for-mocking"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Assets}
|
||||
alt="A representation of typography and image assets"
|
||||
/>
|
||||
<div>
|
||||
<h4 className="sb-section-item-heading">Load assets and resources</h4>
|
||||
<p className="sb-section-item-paragraph">To link static files (like fonts) to your projects and stories, use the
|
||||
`staticDirs` configuration option to specify folders to load when
|
||||
starting Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/images-and-assets/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Do more with Storybook
|
||||
|
||||
Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
|
||||
</div>
|
||||
|
||||
<div className="sb-section">
|
||||
<div className="sb-features-grid">
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Docs}
|
||||
alt="A screenshot showing the autodocs tag being set, pointing a docs page being generated"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Autodocs</h4>
|
||||
<p className="sb-section-item-paragraph">Auto-generate living,
|
||||
interactive reference documentation from your components and stories.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-docs/autodocs/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Share}
|
||||
alt="A browser window showing a Storybook being published to a chromatic.com URL"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Publish to Chromatic</h4>
|
||||
<p className="sb-section-item-paragraph">Publish your Storybook to review and collaborate with your entire team.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/sharing/publish-storybook/?renderer=react#publish-storybook-with-chromatic"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={FigmaPlugin}
|
||||
alt="Windows showing the Storybook plugin in Figma"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Figma Plugin</h4>
|
||||
<p className="sb-section-item-paragraph">Embed your stories into Figma to cross-reference the design and live
|
||||
implementation in one place.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/sharing/design-integrations/?renderer=react#embed-storybook-in-figma-with-the-plugin"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Testing}
|
||||
alt="Screenshot of tests passing and failing"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Testing</h4>
|
||||
<p className="sb-section-item-paragraph">Use stories to test a component in all its variations, no matter how
|
||||
complex.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-tests/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Accessibility}
|
||||
alt="Screenshot of accessibility tests passing and failing"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Accessibility</h4>
|
||||
<p className="sb-section-item-paragraph">Automatically test your components for a11y issues as you develop.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-tests/accessibility-testing/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Theming}
|
||||
alt="Screenshot of Storybook in light and dark mode"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Theming</h4>
|
||||
<p className="sb-section-item-paragraph">Theme Storybook's UI to personalize it to your project.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/theming/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='sb-addon'>
|
||||
<div className='sb-addon-text'>
|
||||
<h4>Addons</h4>
|
||||
<p className="sb-section-item-paragraph">Integrate your tools with Storybook to connect workflows.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/addons/"
|
||||
target="_blank"
|
||||
>Discover all addons<RightArrow /></a>
|
||||
</div>
|
||||
<div className='sb-addon-img'>
|
||||
<Image
|
||||
width={650}
|
||||
height={347}
|
||||
src={AddonLibrary}
|
||||
alt="Integrate your tools with Storybook to connect workflows."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sb-section sb-socials">
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Github}
|
||||
alt="Github logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
Join our contributors building the future of UI development.
|
||||
|
||||
<a
|
||||
href="https://github.com/storybookjs/storybook"
|
||||
target="_blank"
|
||||
>Star on GitHub<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={33}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Discord}
|
||||
alt="Discord logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<div>
|
||||
Get support and chat with frontend developers.
|
||||
|
||||
<a
|
||||
href="https://discord.gg/storybook"
|
||||
target="_blank"
|
||||
>Join Discord server<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Youtube}
|
||||
alt="Youtube logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<div>
|
||||
Watch tutorials, feature previews and interviews.
|
||||
|
||||
<a
|
||||
href="https://www.youtube.com/@chromaticui"
|
||||
target="_blank"
|
||||
>Watch on YouTube<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={33}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Tutorials}
|
||||
alt="A book"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<p>Follow guided walkthroughs on for key workflows.</p>
|
||||
|
||||
<a
|
||||
href="https://storybook.js.org/tutorials/"
|
||||
target="_blank"
|
||||
>Discover tutorials<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.sb-container {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.sb-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sb-section-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.sb-section a:not(h1 a, h2 a, h3 a) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sb-section-item, .sb-grid-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-section-item-heading {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 5px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.sb-section-item-paragraph {
|
||||
margin: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-chevron {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 32px 20px;
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-explore-image {
|
||||
max-height: 32px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background-color: #EEF3F8;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: #EEF3F8;
|
||||
height: 180px;
|
||||
margin-bottom: 48px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 48px;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.sb-addon-text h4 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
position: absolute;
|
||||
left: 345px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 650px;
|
||||
transform: rotate(-15deg);
|
||||
margin-left: 40px;
|
||||
margin-top: -72px;
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.sb-addon-img {
|
||||
left: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.sb-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
height: 280px;
|
||||
align-items: flex-start;
|
||||
padding-top: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 130px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
width: 124%;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 1200px;
|
||||
transform: rotate(-12deg);
|
||||
margin-left: 0;
|
||||
margin-top: 48px;
|
||||
margin-bottom: -40px;
|
||||
margin-left: -24px;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
@ -0,0 +1,33 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn } from '@storybook/test';
|
||||
|
||||
import { Header } from './Header';
|
||||
|
||||
const meta: Meta<typeof Header> = {
|
||||
title: 'Example/Header',
|
||||
component: Header,
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {
|
||||
onLogin: fn(),
|
||||
onLogout: fn(),
|
||||
onCreateAccount: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Header>;
|
||||
|
||||
export const LoggedIn: Story = {
|
||||
args: {
|
||||
user: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut: Story = {};
|
56
code/frameworks/nextjs-vite/template/cli/ts-3-8/Header.tsx
Normal file
56
code/frameworks/nextjs-vite/template/cli/ts-3-8/Header.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from './Button';
|
||||
import './header.css';
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export interface HeaderProps {
|
||||
user?: User;
|
||||
onLogin?: () => void;
|
||||
onLogout?: () => void;
|
||||
onCreateAccount?: () => void;
|
||||
}
|
||||
|
||||
export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (
|
||||
<header>
|
||||
<div className="storybook-header">
|
||||
<div>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
|
||||
fill="#555AB9"
|
||||
/>
|
||||
<path
|
||||
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
|
||||
fill="#91BAF8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<h1>Acme</h1>
|
||||
</div>
|
||||
<div>
|
||||
{user ? (
|
||||
<>
|
||||
<span className="welcome">
|
||||
Welcome, <b>{user.name}</b>!
|
||||
</span>
|
||||
<Button size="small" onClick={onLogout} label="Log out" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="small" onClick={onLogin} label="Log in" />
|
||||
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
@ -0,0 +1,32 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { within, userEvent, expect } from '@storybook/test';
|
||||
|
||||
import { Page } from './Page';
|
||||
|
||||
const meta: Meta<typeof Page> = {
|
||||
title: 'Example/Page',
|
||||
component: Page,
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Page>;
|
||||
|
||||
export const LoggedOut: Story = {};
|
||||
|
||||
// More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testing
|
||||
export const LoggedIn: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const loginButton = canvas.getByRole('button', { name: /Log in/i });
|
||||
await expect(loginButton).toBeInTheDocument();
|
||||
await userEvent.click(loginButton);
|
||||
await expect(loginButton).not.toBeInTheDocument();
|
||||
|
||||
const logoutButton = canvas.getByRole('button', { name: /Log out/i });
|
||||
await expect(logoutButton).toBeInTheDocument();
|
||||
},
|
||||
};
|
73
code/frameworks/nextjs-vite/template/cli/ts-3-8/Page.tsx
Normal file
73
code/frameworks/nextjs-vite/template/cli/ts-3-8/Page.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Header } from './Header';
|
||||
import './page.css';
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const Page: React.FC = () => {
|
||||
const [user, setUser] = React.useState<User>();
|
||||
|
||||
return (
|
||||
<article>
|
||||
<Header
|
||||
user={user}
|
||||
onLogin={() => setUser({ name: 'Jane Doe' })}
|
||||
onLogout={() => setUser(undefined)}
|
||||
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
|
||||
/>
|
||||
|
||||
<section className="storybook-page">
|
||||
<h2>Pages in Storybook</h2>
|
||||
<p>
|
||||
We recommend building UIs with a{' '}
|
||||
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
|
||||
<strong>component-driven</strong>
|
||||
</a>{' '}
|
||||
process starting with atomic components and ending with pages.
|
||||
</p>
|
||||
<p>
|
||||
Render pages with mock data. This makes it easy to build and review page states without
|
||||
needing to navigate to them in your app. Here are some handy patterns for managing page
|
||||
data in Storybook:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Use a higher-level connected component. Storybook helps you compose such data from the
|
||||
"args" of child component stories
|
||||
</li>
|
||||
<li>
|
||||
Assemble data in the page component from your services. You can mock these services out
|
||||
using Storybook.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Get a guided tutorial on component-driven development at{' '}
|
||||
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
|
||||
Storybook tutorials
|
||||
</a>
|
||||
. Read more in the{' '}
|
||||
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
|
||||
docs
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
||||
id="a"
|
||||
fill="#999"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
Viewports addon in the toolbar
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
};
|
@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn } from '@storybook/test';
|
||||
import { Button } from './Button';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
const meta = {
|
||||
title: 'Example/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
||||
args: { onClick: fn() },
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
52
code/frameworks/nextjs-vite/template/cli/ts-4-9/Button.tsx
Normal file
52
code/frameworks/nextjs-vite/template/cli/ts-4-9/Button.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import './button.css';
|
||||
|
||||
export interface ButtonProps {
|
||||
/**
|
||||
* Is this the principal call to action on the page?
|
||||
*/
|
||||
primary?: boolean;
|
||||
/**
|
||||
* What background color to use
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
/**
|
||||
* Button contents
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Optional click handler
|
||||
*/
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({
|
||||
primary = false,
|
||||
size = 'medium',
|
||||
backgroundColor,
|
||||
label,
|
||||
...props
|
||||
}: ButtonProps) => {
|
||||
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
<style jsx>{`
|
||||
button {
|
||||
background-color: ${backgroundColor};
|
||||
}
|
||||
`}</style>
|
||||
</button>
|
||||
);
|
||||
};
|
446
code/frameworks/nextjs-vite/template/cli/ts-4-9/Configure.mdx
Normal file
446
code/frameworks/nextjs-vite/template/cli/ts-4-9/Configure.mdx
Normal file
@ -0,0 +1,446 @@
|
||||
import { Meta } from "@storybook/blocks";
|
||||
import Image from "next/image";
|
||||
|
||||
import Github from "./assets/github.svg";
|
||||
import Discord from "./assets/discord.svg";
|
||||
import Youtube from "./assets/youtube.svg";
|
||||
import Tutorials from "./assets/tutorials.svg";
|
||||
import Styling from "./assets/styling.png";
|
||||
import Context from "./assets/context.png";
|
||||
import Assets from "./assets/assets.png";
|
||||
import Docs from "./assets/docs.png";
|
||||
import Share from "./assets/share.png";
|
||||
import FigmaPlugin from "./assets/figma-plugin.png";
|
||||
import Testing from "./assets/testing.png";
|
||||
import Accessibility from "./assets/accessibility.png";
|
||||
import Theming from "./assets/theming.png";
|
||||
import AddonLibrary from "./assets/addon-library.png";
|
||||
|
||||
export const RightArrow = () => <svg
|
||||
viewBox="0 0 14 14"
|
||||
width="8px"
|
||||
height="14px"
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
display: 'inline-block',
|
||||
shapeRendering: 'inherit',
|
||||
verticalAlign: 'middle',
|
||||
fill: 'currentColor',
|
||||
'path fill': 'currentColor'
|
||||
}}
|
||||
>
|
||||
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
|
||||
</svg>
|
||||
|
||||
<Meta title="Configure your project" />
|
||||
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Configure your project
|
||||
|
||||
Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
|
||||
</div>
|
||||
<div className="sb-section">
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
src={Styling}
|
||||
alt="A wall of logos representing different styling technologies"
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Add styling and CSS</h4>
|
||||
<p className="sb-section-item-paragraph">Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Context}
|
||||
alt="An abstraction representing the composition of data for a component"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Provide context and mocking</h4>
|
||||
<p className="sb-section-item-paragraph">Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-stories/decorators/?renderer=react#context-for-mocking"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Assets}
|
||||
alt="A representation of typography and image assets"
|
||||
/>
|
||||
<div>
|
||||
<h4 className="sb-section-item-heading">Load assets and resources</h4>
|
||||
<p className="sb-section-item-paragraph">To link static files (like fonts) to your projects and stories, use the
|
||||
`staticDirs` configuration option to specify folders to load when
|
||||
starting Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/images-and-assets/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Do more with Storybook
|
||||
|
||||
Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
|
||||
</div>
|
||||
|
||||
<div className="sb-section">
|
||||
<div className="sb-features-grid">
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Docs}
|
||||
alt="A screenshot showing the autodocs tag being set, pointing a docs page being generated"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Autodocs</h4>
|
||||
<p className="sb-section-item-paragraph">Auto-generate living,
|
||||
interactive reference documentation from your components and stories.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-docs/autodocs/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Share}
|
||||
alt="A browser window showing a Storybook being published to a chromatic.com URL"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Publish to Chromatic</h4>
|
||||
<p className="sb-section-item-paragraph">Publish your Storybook to review and collaborate with your entire team.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/sharing/publish-storybook/?renderer=react#publish-storybook-with-chromatic"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={FigmaPlugin}
|
||||
alt="Windows showing the Storybook plugin in Figma"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Figma Plugin</h4>
|
||||
<p className="sb-section-item-paragraph">Embed your stories into Figma to cross-reference the design and live
|
||||
implementation in one place.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/sharing/design-integrations/?renderer=react#embed-storybook-in-figma-with-the-plugin"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Testing}
|
||||
alt="Screenshot of tests passing and failing"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Testing</h4>
|
||||
<p className="sb-section-item-paragraph">Use stories to test a component in all its variations, no matter how
|
||||
complex.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-tests/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Accessibility}
|
||||
alt="Screenshot of accessibility tests passing and failing"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Accessibility</h4>
|
||||
<p className="sb-section-item-paragraph">Automatically test your components for a11y issues as you develop.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/writing-tests/accessibility-testing/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<Image
|
||||
width={0}
|
||||
height={0}
|
||||
style={{ width: '100%', height: 'auto' }}
|
||||
src={Theming}
|
||||
alt="Screenshot of Storybook in light and dark mode"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Theming</h4>
|
||||
<p className="sb-section-item-paragraph">Theme Storybook's UI to personalize it to your project.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/configure/theming/?renderer=react"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='sb-addon'>
|
||||
<div className='sb-addon-text'>
|
||||
<h4>Addons</h4>
|
||||
<p className="sb-section-item-paragraph">Integrate your tools with Storybook to connect workflows.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/addons/"
|
||||
target="_blank"
|
||||
>Discover all addons<RightArrow /></a>
|
||||
</div>
|
||||
<div className='sb-addon-img'>
|
||||
<Image
|
||||
width={650}
|
||||
height={347}
|
||||
src={AddonLibrary}
|
||||
alt="Integrate your tools with Storybook to connect workflows."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sb-section sb-socials">
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Github}
|
||||
alt="Github logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
Join our contributors building the future of UI development.
|
||||
|
||||
<a
|
||||
href="https://github.com/storybookjs/storybook"
|
||||
target="_blank"
|
||||
>Star on GitHub<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={33}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Discord}
|
||||
alt="Discord logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<div>
|
||||
Get support and chat with frontend developers.
|
||||
|
||||
<a
|
||||
href="https://discord.gg/storybook"
|
||||
target="_blank"
|
||||
>Join Discord server<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Youtube}
|
||||
alt="Youtube logo"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<div>
|
||||
Watch tutorials, feature previews and interviews.
|
||||
|
||||
<a
|
||||
href="https://www.youtube.com/@chromaticui"
|
||||
target="_blank"
|
||||
>Watch on YouTube<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<Image
|
||||
width={33}
|
||||
height={32}
|
||||
layout="fixed"
|
||||
src={Tutorials}
|
||||
alt="A book"
|
||||
className="sb-explore-image"
|
||||
/>
|
||||
<p>Follow guided walkthroughs on for key workflows.</p>
|
||||
|
||||
<a
|
||||
href="https://storybook.js.org/tutorials/"
|
||||
target="_blank"
|
||||
>Discover tutorials<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.sb-container {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.sb-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sb-section-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.sb-section a:not(h1 a, h2 a, h3 a) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sb-section-item, .sb-grid-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-section-item-heading {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 5px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.sb-section-item-paragraph {
|
||||
margin: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-chevron {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 32px 20px;
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-explore-image {
|
||||
max-height: 32px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background-color: #EEF3F8;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: #EEF3F8;
|
||||
height: 180px;
|
||||
margin-bottom: 48px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 48px;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.sb-addon-text h4 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
position: absolute;
|
||||
left: 345px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 650px;
|
||||
transform: rotate(-15deg);
|
||||
margin-left: 40px;
|
||||
margin-top: -72px;
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.sb-addon-img {
|
||||
left: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.sb-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
height: 280px;
|
||||
align-items: flex-start;
|
||||
padding-top: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 130px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
width: 124%;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 1200px;
|
||||
transform: rotate(-12deg);
|
||||
margin-left: 0;
|
||||
margin-top: 48px;
|
||||
margin-bottom: -40px;
|
||||
margin-left: -24px;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
@ -0,0 +1,32 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn } from '@storybook/test';
|
||||
import { Header } from './Header';
|
||||
|
||||
const meta = {
|
||||
title: 'Example/Header',
|
||||
component: Header,
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {
|
||||
onLogin: fn(),
|
||||
onLogout: fn(),
|
||||
onCreateAccount: fn(),
|
||||
},
|
||||
} satisfies Meta<typeof Header>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const LoggedIn: Story = {
|
||||
args: {
|
||||
user: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut: Story = {};
|
56
code/frameworks/nextjs-vite/template/cli/ts-4-9/Header.tsx
Normal file
56
code/frameworks/nextjs-vite/template/cli/ts-4-9/Header.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from './Button';
|
||||
import './header.css';
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export interface HeaderProps {
|
||||
user?: User;
|
||||
onLogin?: () => void;
|
||||
onLogout?: () => void;
|
||||
onCreateAccount?: () => void;
|
||||
}
|
||||
|
||||
export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (
|
||||
<header>
|
||||
<div className="storybook-header">
|
||||
<div>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
|
||||
fill="#555AB9"
|
||||
/>
|
||||
<path
|
||||
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
|
||||
fill="#91BAF8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<h1>Acme</h1>
|
||||
</div>
|
||||
<div>
|
||||
{user ? (
|
||||
<>
|
||||
<span className="welcome">
|
||||
Welcome, <b>{user.name}</b>!
|
||||
</span>
|
||||
<Button size="small" onClick={onLogout} label="Log out" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="small" onClick={onLogin} label="Log in" />
|
||||
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
@ -0,0 +1,32 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { within, userEvent, expect } from '@storybook/test';
|
||||
|
||||
import { Page } from './Page';
|
||||
|
||||
const meta = {
|
||||
title: 'Example/Page',
|
||||
component: Page,
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
} satisfies Meta<typeof Page>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const LoggedOut: Story = {};
|
||||
|
||||
// More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testing
|
||||
export const LoggedIn: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const loginButton = canvas.getByRole('button', { name: /Log in/i });
|
||||
await expect(loginButton).toBeInTheDocument();
|
||||
await userEvent.click(loginButton);
|
||||
await expect(loginButton).not.toBeInTheDocument();
|
||||
|
||||
const logoutButton = canvas.getByRole('button', { name: /Log out/i });
|
||||
await expect(logoutButton).toBeInTheDocument();
|
||||
},
|
||||
};
|
73
code/frameworks/nextjs-vite/template/cli/ts-4-9/Page.tsx
Normal file
73
code/frameworks/nextjs-vite/template/cli/ts-4-9/Page.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Header } from './Header';
|
||||
import './page.css';
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const Page: React.FC = () => {
|
||||
const [user, setUser] = React.useState<User>();
|
||||
|
||||
return (
|
||||
<article>
|
||||
<Header
|
||||
user={user}
|
||||
onLogin={() => setUser({ name: 'Jane Doe' })}
|
||||
onLogout={() => setUser(undefined)}
|
||||
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
|
||||
/>
|
||||
|
||||
<section className="storybook-page">
|
||||
<h2>Pages in Storybook</h2>
|
||||
<p>
|
||||
We recommend building UIs with a{' '}
|
||||
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
|
||||
<strong>component-driven</strong>
|
||||
</a>{' '}
|
||||
process starting with atomic components and ending with pages.
|
||||
</p>
|
||||
<p>
|
||||
Render pages with mock data. This makes it easy to build and review page states without
|
||||
needing to navigate to them in your app. Here are some handy patterns for managing page
|
||||
data in Storybook:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Use a higher-level connected component. Storybook helps you compose such data from the
|
||||
"args" of child component stories
|
||||
</li>
|
||||
<li>
|
||||
Assemble data in the page component from your services. You can mock these services out
|
||||
using Storybook.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Get a guided tutorial on component-driven development at{' '}
|
||||
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
|
||||
Storybook tutorials
|
||||
</a>
|
||||
. Read more in the{' '}
|
||||
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
|
||||
docs
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
||||
id="a"
|
||||
fill="#999"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
Viewports addon in the toolbar
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
};
|
7
code/frameworks/nextjs-vite/template/next-env.d.ts
vendored
Normal file
7
code/frameworks/nextjs-vite/template/next-env.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Reference necessary since Next.js 13.2.0, because types in `next/navigation` are not exported per default, but
|
||||
// type references are dynamically created during Next.js start up.
|
||||
// See https://github.com/vercel/next.js/commit/cdf1d52d9aed42d01a46539886a4bda14cb77a99
|
||||
// for more insights.
|
||||
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/navigation-types/navigation" />
|
@ -0,0 +1,25 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { Suspense } from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
const DynamicComponent = dynamic(() => import('./DynamicImport'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<Suspense fallback="Loading...">
|
||||
<DynamicComponent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
component: Component,
|
||||
} satisfies Meta<typeof DynamicComponent>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function DynamicComponent() {
|
||||
return <div>I am a dynamically loaded component</div>;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Font from './Font';
|
||||
|
||||
const meta = {
|
||||
component: Font,
|
||||
} satisfies Meta<typeof Font>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const WithClassName: Story = {
|
||||
args: {
|
||||
variant: 'className',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithStyle: Story = {
|
||||
args: {
|
||||
variant: 'style',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithVariable: Story = {
|
||||
args: {
|
||||
variant: 'variable',
|
||||
},
|
||||
};
|
67
code/frameworks/nextjs-vite/template/stories/Font.tsx
Normal file
67
code/frameworks/nextjs-vite/template/stories/Font.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { Rubik_Puddles } from 'next/font/google';
|
||||
import localFont from 'next/font/local';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const rubik = Rubik_Puddles({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-latin-rubik',
|
||||
weight: '400',
|
||||
});
|
||||
|
||||
export const localRubikStorm = localFont({
|
||||
src: '/fonts/RubikStorm-Regular.ttf',
|
||||
variable: '--font-rubik-storm',
|
||||
});
|
||||
|
||||
type FontProps = {
|
||||
variant: 'className' | 'style' | 'variable';
|
||||
};
|
||||
|
||||
export default function Font({ variant }: FontProps) {
|
||||
switch (variant) {
|
||||
case 'className':
|
||||
return (
|
||||
<div>
|
||||
<h1 className={rubik.className}>Google Rubik Puddles</h1>
|
||||
<h1 className={localRubikStorm.className}>Google Local Rubik Storm</h1>
|
||||
</div>
|
||||
);
|
||||
case 'style':
|
||||
return (
|
||||
<div>
|
||||
<h1 style={rubik.style}>Google Rubik Puddles</h1>
|
||||
<h1 style={localRubikStorm.style}>Google Local Rubik Storm</h1>
|
||||
</div>
|
||||
);
|
||||
case 'variable':
|
||||
return (
|
||||
<div>
|
||||
<div className={rubik.variable}>
|
||||
<h1
|
||||
style={{
|
||||
fontFamily: 'var(--font-latin-rubik)',
|
||||
fontStyle: rubik.style.fontStyle,
|
||||
fontWeight: rubik.style.fontWeight,
|
||||
}}
|
||||
>
|
||||
Google Rubik Puddles
|
||||
</h1>
|
||||
</div>
|
||||
<div className={localRubikStorm.variable}>
|
||||
<h1
|
||||
style={{
|
||||
fontFamily: 'var(--font-rubik-storm)',
|
||||
fontStyle: localRubikStorm.style.fontStyle,
|
||||
fontWeight: localRubikStorm.style.fontWeight,
|
||||
}}
|
||||
>
|
||||
Google Local Rubik Storm
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { type ImageProps, getImageProps } from 'next/image';
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
|
||||
|
||||
import Accessibility from '../../assets/accessibility.svg';
|
||||
import Testing from '../../assets/testing.png';
|
||||
|
||||
// referenced from https://nextjs.org/docs/pages/api-reference/components/image#theme-detection-picture
|
||||
const Component = (props: Omit<ImageProps, 'src'>) => {
|
||||
const {
|
||||
props: { srcSet: dark },
|
||||
} = getImageProps({ src: Accessibility, ...props });
|
||||
const {
|
||||
// capture rest on one to spread to img as default; it doesn't matter which barring art direction
|
||||
props: { srcSet: light, ...rest },
|
||||
} = getImageProps({ src: Testing, ...props });
|
||||
|
||||
return (
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcSet={dark} />
|
||||
<source media="(prefers-color-scheme: light)" srcSet={light} />
|
||||
<img {...rest} />
|
||||
</picture>
|
||||
);
|
||||
};
|
||||
|
||||
const meta = {
|
||||
component: Component,
|
||||
args: {
|
||||
alt: 'getImageProps Example',
|
||||
},
|
||||
} satisfies Meta<typeof Component>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
@ -0,0 +1,38 @@
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
import { waitFor, expect } from '@storybook/test';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { StoryObj } from '@storybook/react';
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Next.js Head Title</title>
|
||||
<meta property="og:title" content="My page title" key="title" />
|
||||
</Head>
|
||||
<Head>
|
||||
<meta property="og:title" content="My new title" key="title" />
|
||||
</Head>
|
||||
<p>Hello world!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
component: Component,
|
||||
} satisfies Meta<typeof Component>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async () => {
|
||||
await waitFor(() => expect(document.title).toEqual('Next.js Head Title'));
|
||||
await expect(document.querySelectorAll('meta[property="og:title"]')).toHaveLength(1);
|
||||
await expect(document.querySelector('meta[property="og:title"]').content).toEqual(
|
||||
'My new title'
|
||||
);
|
||||
},
|
||||
};
|
108
code/frameworks/nextjs-vite/template/stories/Image.stories.tsx
Normal file
108
code/frameworks/nextjs-vite/template/stories/Image.stories.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
import Accessibility from '../../assets/accessibility.svg';
|
||||
import AvifImage from '../../assets/avif-test-image.avif';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
const meta = {
|
||||
component: Image,
|
||||
args: {
|
||||
src: Accessibility,
|
||||
alt: 'Accessibility',
|
||||
},
|
||||
} satisfies Meta<typeof Image>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Avif: Story = {
|
||||
args: {
|
||||
src: AvifImage,
|
||||
alt: 'Avif Test Image',
|
||||
},
|
||||
};
|
||||
|
||||
export const BlurredPlaceholder: Story = {
|
||||
args: {
|
||||
placeholder: 'blur',
|
||||
},
|
||||
};
|
||||
|
||||
export const BlurredAbsolutePlaceholder: Story = {
|
||||
args: {
|
||||
src: 'https://storybook.js.org/images/placeholders/50x50.png',
|
||||
width: 50,
|
||||
height: 50,
|
||||
blurDataURL:
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
|
||||
placeholder: 'blur',
|
||||
},
|
||||
parameters: {
|
||||
// ignoring in Chromatic to avoid inconsistent snapshots
|
||||
// given that the switch from blur to image is quite fast
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const FilledParent: Story = {
|
||||
args: {
|
||||
fill: true,
|
||||
},
|
||||
decorators: [
|
||||
(Story) => <div style={{ width: 500, height: 500, position: 'relative' }}>{Story()}</div>,
|
||||
],
|
||||
};
|
||||
|
||||
export const Sized: Story = {
|
||||
args: {
|
||||
fill: true,
|
||||
sizes: '(max-width: 600px) 100vw, 600px',
|
||||
},
|
||||
decorators: [
|
||||
(Story) => <div style={{ width: 800, height: 800, position: 'relative' }}>{Story()}</div>,
|
||||
],
|
||||
};
|
||||
|
||||
export const Lazy: Story = {
|
||||
args: {
|
||||
src: 'https://storybook.js.org/images/placeholders/50x50.png',
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<>
|
||||
<div style={{ height: '200vh' }} />
|
||||
{Story()}
|
||||
</>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Eager: Story = {
|
||||
...Lazy,
|
||||
parameters: {
|
||||
nextjs: {
|
||||
image: {
|
||||
loading: 'eager',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WithRef: Story = {
|
||||
render() {
|
||||
const [ref, setRef] = useState<HTMLImageElement | null>(null);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Image src={Accessibility} alt="Accessibility" ref={setRef} />
|
||||
<p>Alt attribute of image: {ref?.alt}</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/legacy/image';
|
||||
|
||||
import Accessibility from '../../assets/accessibility.svg';
|
||||
|
||||
export default {
|
||||
component: Image,
|
||||
args: {
|
||||
src: Accessibility,
|
||||
alt: 'Accessibility',
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
||||
export const BlurredPlaceholder = {
|
||||
args: {
|
||||
placeholder: 'blur',
|
||||
},
|
||||
};
|
||||
|
||||
export const BlurredAbsolutePlaceholder = {
|
||||
args: {
|
||||
src: 'https://storybook.js.org/images/placeholders/50x50.png',
|
||||
width: 50,
|
||||
height: 50,
|
||||
blurDataURL:
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
|
||||
placeholder: 'blur',
|
||||
},
|
||||
parameters: {
|
||||
// ignoring in Chromatic to avoid inconsistent snapshots
|
||||
// given that the switch from blur to image is quite fast
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
.link {
|
||||
color: green;
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import style from './Link.stories.module.css';
|
||||
|
||||
// `onClick`, `href`, and `ref` need to be passed to the DOM element
|
||||
// for proper handling
|
||||
const MyButton = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>
|
||||
>(function Button({ onClick, href, children }, ref) {
|
||||
return (
|
||||
<a href={href} onClick={onClick} ref={ref}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
const Component = () => (
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/">Normal Link</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/with-url-object',
|
||||
query: { name: 'test' },
|
||||
}}
|
||||
>
|
||||
With URL Object
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/replace-url" replace>
|
||||
Replace the URL instead of push
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/legacy-behaviour" legacyBehavior>
|
||||
<a>Legacy behavior</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/child-is-functional-component" passHref legacyBehavior>
|
||||
<MyButton>child is a functional component</MyButton>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/#hashid" scroll={false}>
|
||||
Disables scrolling to the top
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/no-prefetch" prefetch={false}>
|
||||
No Prefetching
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link style={{ color: 'red' }} href="/with-style">
|
||||
With style
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link className={style.link} href="/with-classname">
|
||||
With className
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
} as Meta<typeof Component>;
|
||||
|
||||
export const Default: StoryObj<typeof Component> = {};
|
||||
|
||||
export const InAppDir: StoryObj<typeof Component> = {
|
||||
parameters: {
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,151 @@
|
||||
import {
|
||||
useRouter,
|
||||
usePathname,
|
||||
useSearchParams,
|
||||
useParams,
|
||||
useSelectedLayoutSegment,
|
||||
useSelectedLayoutSegments,
|
||||
} from 'next/navigation';
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, userEvent, within } from '@storybook/test';
|
||||
import { getRouter } from '@storybook/nextjs-vite/navigation.mock';
|
||||
|
||||
function Component() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const params = useParams();
|
||||
const segment = useSelectedLayoutSegment();
|
||||
const segments = useSelectedLayoutSegments();
|
||||
|
||||
const searchParamsList = searchParams ? Array.from(searchParams.entries()) : [];
|
||||
|
||||
const routerActions = [
|
||||
{
|
||||
cb: () => router.back(),
|
||||
name: 'Go back',
|
||||
},
|
||||
{
|
||||
cb: () => router.forward(),
|
||||
name: 'Go forward',
|
||||
},
|
||||
{
|
||||
cb: () => router.prefetch('/prefetched-html'),
|
||||
name: 'Prefetch',
|
||||
},
|
||||
{
|
||||
// @ts-expect-error (old API)
|
||||
cb: () => router.push('/push-html', { forceOptimisticNavigation: true }),
|
||||
name: 'Push HTML',
|
||||
},
|
||||
{
|
||||
cb: () => router.refresh(),
|
||||
name: 'Refresh',
|
||||
},
|
||||
{
|
||||
// @ts-expect-error (old API)
|
||||
cb: () => router.replace('/replaced-html', { forceOptimisticNavigation: true }),
|
||||
name: 'Replace',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>pathname: {pathname}</div>
|
||||
<div>segment: {segment}</div>
|
||||
<div>segments: {segments.join(',')}</div>
|
||||
<div>
|
||||
searchparams:{' '}
|
||||
<ul>
|
||||
{searchParamsList.map(([key, value]) => (
|
||||
<li key={key}>
|
||||
{key}: {value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
params:{' '}
|
||||
<ul>
|
||||
{Object.entries(params).map(([key, value]) => (
|
||||
<li key={key}>
|
||||
{key}: {value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{routerActions.map(({ cb, name }) => (
|
||||
<div key={name} style={{ marginBottom: '1em' }}>
|
||||
<button type="button" onClick={cb}>
|
||||
{name}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof Component>;
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
parameters: {
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
pathname: '/hello',
|
||||
query: {
|
||||
foo: 'bar',
|
||||
},
|
||||
prefetch: () => {
|
||||
console.log('custom prefetch');
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta<typeof Component>;
|
||||
|
||||
export const Default: StoryObj<typeof Component> = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const routerMock = getRouter();
|
||||
|
||||
await step('Asserts whether forward hook is called', async () => {
|
||||
const forwardBtn = await canvas.findByText('Go forward');
|
||||
await userEvent.click(forwardBtn);
|
||||
await expect(routerMock.forward).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
await step('Asserts whether custom prefetch hook is called', async () => {
|
||||
const prefetchBtn = await canvas.findByText('Prefetch');
|
||||
await userEvent.click(prefetchBtn);
|
||||
await expect(routerMock.prefetch).toHaveBeenCalledWith('/prefetched-html');
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const WithSegmentDefined: Story = {
|
||||
parameters: {
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
segments: ['dashboard', 'settings'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WithSegmentDefinedForParams: Story = {
|
||||
parameters: {
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
segments: [
|
||||
['slug', 'hello'],
|
||||
['framework', 'nextjs'],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
import NextHeader from './NextHeader';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { StoryObj } from '@storybook/react';
|
||||
import { expect, userEvent, within } from '@storybook/test';
|
||||
import { cookies, headers } from '@storybook/nextjs-vite/headers.mock';
|
||||
|
||||
export default {
|
||||
component: NextHeader,
|
||||
} as Meta<typeof NextHeader>;
|
||||
|
||||
type Story = StoryObj<typeof NextHeader>;
|
||||
|
||||
export const Default: Story = {
|
||||
loaders: async () => {
|
||||
cookies().set('firstName', 'Jane');
|
||||
cookies().set({
|
||||
name: 'lastName',
|
||||
value: 'Doe',
|
||||
});
|
||||
headers().set('timezone', 'Central European Summer Time');
|
||||
},
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const headersMock = headers();
|
||||
const cookiesMock = cookies();
|
||||
await step('Cookie and header store apis are called upon rendering', async () => {
|
||||
await expect(cookiesMock.getAll).toHaveBeenCalled();
|
||||
await expect(headersMock.entries).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
await step('Upon clicking on submit, the user-id cookie is set', async () => {
|
||||
const submitButton = await canvas.findByRole('button');
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
await expect(cookiesMock.set).toHaveBeenCalledWith('user-id', 'encrypted-id');
|
||||
});
|
||||
|
||||
await step('The user-id cookie is available in cookie and header stores', async () => {
|
||||
await expect(headersMock.get('cookie')).toContain('user-id=encrypted-id');
|
||||
await expect(cookiesMock.get('user-id')).toEqual({
|
||||
name: 'user-id',
|
||||
value: 'encrypted-id',
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
39
code/frameworks/nextjs-vite/template/stories/NextHeader.tsx
Normal file
39
code/frameworks/nextjs-vite/template/stories/NextHeader.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { cookies, headers } from 'next/headers';
|
||||
|
||||
export default async function Component() {
|
||||
async function handleClick() {
|
||||
'use server';
|
||||
cookies().set('user-id', 'encrypted-id');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3>Cookies:</h3>
|
||||
{cookies()
|
||||
.getAll()
|
||||
.map(({ name, value }) => {
|
||||
return (
|
||||
<p key={name} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
|
||||
<strong>Name:</strong> <span>{name}</span>
|
||||
<strong>Value:</strong> <span>{value}</span>
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
|
||||
<h3>Headers:</h3>
|
||||
{Array.from(headers().entries()).map(([name, value]: [string, string]) => {
|
||||
return (
|
||||
<p key={name} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
|
||||
<strong>Name:</strong> <span>{name}</span>
|
||||
<strong>Value:</strong> <span>{value}</span>
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
|
||||
<form action={handleClick}>
|
||||
<button>add user-id cookie</button>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
6
code/frameworks/nextjs-vite/template/stories/RSC.jsx
Normal file
6
code/frameworks/nextjs-vite/template/stories/RSC.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import 'server-only';
|
||||
|
||||
export const RSC = async ({ label }) => <>RSC {label}</>;
|
||||
|
||||
export const Nested = async ({ children }) => <>Nested {children}</>;
|
35
code/frameworks/nextjs-vite/template/stories/RSC.stories.jsx
Normal file
35
code/frameworks/nextjs-vite/template/stories/RSC.stories.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { RSC, Nested } from './RSC';
|
||||
|
||||
export default {
|
||||
component: RSC,
|
||||
args: { label: 'label' },
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
||||
export const DisableRSC = {
|
||||
tags: ['!test'],
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
nextjs: { rsc: false },
|
||||
},
|
||||
};
|
||||
|
||||
export const Error = {
|
||||
tags: ['!test'],
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
render: () => {
|
||||
throw new Error('RSC Error');
|
||||
},
|
||||
};
|
||||
|
||||
export const NestedRSC = {
|
||||
render: (args) => (
|
||||
<Nested>
|
||||
<RSC {...args} />
|
||||
</Nested>
|
||||
),
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/test';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
let state = 'Bug! Not invalidated';
|
||||
|
||||
export default {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>{state}</div>
|
||||
<form
|
||||
action={() => {
|
||||
state = 'State is invalidated successfully.';
|
||||
redirect('/');
|
||||
}}
|
||||
>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
parameters: {
|
||||
test: {
|
||||
// This is needed until Next will update to the React 19 beta: https://github.com/vercel/next.js/pull/65058
|
||||
// In the React 19 beta ErrorBoundary errors (such as redirect) are only logged, and not thrown.
|
||||
// We will also suspress console.error logs for re the console.error logs for redirect in the next framework.
|
||||
// Using the onCaughtError react root option:
|
||||
// react: {
|
||||
// rootOptions: {
|
||||
// onCaughtError(error: unknown) {
|
||||
// if (isNextRouterError(error)) return;
|
||||
// console.error(error);
|
||||
// },
|
||||
// },
|
||||
// See: code/frameworks/nextjs/src/preview.tsx
|
||||
dangerouslyIgnoreUnhandledErrors: true,
|
||||
},
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
pathname: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['!test'],
|
||||
} as Meta;
|
||||
|
||||
export const SingletonStateGetsInvalidatedAfterRedirecting: StoryObj = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
},
|
||||
};
|
108
code/frameworks/nextjs-vite/template/stories/Router.stories.tsx
Normal file
108
code/frameworks/nextjs-vite/template/stories/Router.stories.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, within, userEvent } from '@storybook/test';
|
||||
import { getRouter } from '@storybook/nextjs-vite/router.mock';
|
||||
import Router, { useRouter } from 'next/router';
|
||||
|
||||
function Component() {
|
||||
const router = useRouter();
|
||||
const searchParams = router.query;
|
||||
|
||||
const routerActions = [
|
||||
{
|
||||
cb: () => router.back(),
|
||||
name: 'Go back',
|
||||
},
|
||||
{
|
||||
cb: () => router.forward(),
|
||||
name: 'Go forward',
|
||||
},
|
||||
{
|
||||
cb: () => router.prefetch('/prefetched-html'),
|
||||
name: 'Prefetch',
|
||||
},
|
||||
{
|
||||
// @ts-expect-error (old API)
|
||||
cb: () => router.push('/push-html', { forceOptimisticNavigation: true }),
|
||||
name: 'Push HTML',
|
||||
},
|
||||
{
|
||||
// @ts-expect-error (old API)
|
||||
cb: () => router.replace('/replaced-html', { forceOptimisticNavigation: true }),
|
||||
name: 'Replace',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Router pathname: {Router.pathname}</div>
|
||||
<div>pathname: {router.pathname}</div>
|
||||
<div>
|
||||
searchparams:{' '}
|
||||
<ul>
|
||||
{Object.entries(searchParams).map(([key, value]) => (
|
||||
<li key={key}>
|
||||
{key}: {value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{routerActions.map(({ cb, name }) => (
|
||||
<div key={name} style={{ marginBottom: '1em' }}>
|
||||
<button type="button" onClick={cb}>
|
||||
{name}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
parameters: {
|
||||
nextjs: {
|
||||
router: {
|
||||
pathname: '/hello',
|
||||
query: {
|
||||
foo: 'bar',
|
||||
},
|
||||
prefetch: () => {
|
||||
console.log('custom prefetch');
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta<typeof Component>;
|
||||
|
||||
export const Default: StoryObj<typeof Component> = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const routerMock = getRouter();
|
||||
|
||||
await step('Router property overrides should be available in useRouter fn', async () => {
|
||||
await expect(Router.pathname).toBe('/hello');
|
||||
await expect(Router.query).toEqual({ foo: 'bar' });
|
||||
});
|
||||
|
||||
await step(
|
||||
'Router property overrides should be available in default export from next/router',
|
||||
async () => {
|
||||
await expect(Router.pathname).toBe('/hello');
|
||||
await expect(Router.query).toEqual({ foo: 'bar' });
|
||||
}
|
||||
);
|
||||
|
||||
await step('Asserts whether forward hook is called', async () => {
|
||||
const forwardBtn = await canvas.findByText('Go forward');
|
||||
await userEvent.click(forwardBtn);
|
||||
await expect(routerMock.forward).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
await step('Asserts whether custom prefetch hook is called', async () => {
|
||||
const prefetchBtn = await canvas.findByText('Prefetch');
|
||||
await userEvent.click(prefetchBtn);
|
||||
await expect(routerMock.prefetch).toHaveBeenCalledWith('/prefetched-html');
|
||||
});
|
||||
},
|
||||
};
|
@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, within, userEvent, waitFor } from '@storybook/test';
|
||||
import { cookies } from '@storybook/nextjs-vite/headers.mock';
|
||||
import { revalidatePath } from '@storybook/nextjs-vite/cache.mock';
|
||||
import { redirect, getRouter } from '@storybook/nextjs-vite/navigation.mock';
|
||||
|
||||
import { accessRoute, login, logout } from './ServerActions';
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<form>
|
||||
<button type="submit" formAction={login}>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
<form>
|
||||
<button type="submit" formAction={logout}>
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
<form>
|
||||
<button type="submit" formAction={accessRoute}>
|
||||
Access protected route
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
tags: ['!test'],
|
||||
parameters: {
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
pathname: '/',
|
||||
},
|
||||
},
|
||||
test: {
|
||||
// This is needed until Next will update to the React 19 beta: https://github.com/vercel/next.js/pull/65058
|
||||
// In the React 19 beta ErrorBoundary errors (such as redirect) are only logged, and not thrown.
|
||||
// We will also suspress console.error logs for re the console.error logs for redirect in the next framework.
|
||||
// Using the onCaughtError react root option:
|
||||
// react: {
|
||||
// rootOptions: {
|
||||
// onCaughtError(error: unknown) {
|
||||
// if (isNextRouterError(error)) return;
|
||||
// console.error(error);
|
||||
// },
|
||||
// },
|
||||
// See: code/frameworks/nextjs/src/preview.tsx
|
||||
dangerouslyIgnoreUnhandledErrors: true,
|
||||
},
|
||||
},
|
||||
} as Meta<typeof Component>;
|
||||
|
||||
export const ProtectedWhileLoggedOut: StoryObj<typeof Component> = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByText('Access protected route'));
|
||||
|
||||
await expect(cookies().get).toHaveBeenCalledWith('user');
|
||||
await expect(redirect).toHaveBeenCalledWith('/');
|
||||
|
||||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
|
||||
},
|
||||
};
|
||||
|
||||
export const ProtectedWhileLoggedIn: StoryObj<typeof Component> = {
|
||||
beforeEach() {
|
||||
cookies().set('user', 'storybookjs');
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByText('Access protected route'));
|
||||
|
||||
await expect(cookies().get).toHaveBeenLastCalledWith('user');
|
||||
await expect(revalidatePath).toHaveBeenLastCalledWith('/');
|
||||
await expect(redirect).toHaveBeenLastCalledWith('/protected');
|
||||
|
||||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
|
||||
},
|
||||
};
|
||||
|
||||
export const Logout: StoryObj<typeof Component> = {
|
||||
beforeEach() {
|
||||
cookies().set('user', 'storybookjs');
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await userEvent.click(canvas.getByText('Logout'));
|
||||
await expect(cookies().delete).toHaveBeenCalled();
|
||||
await expect(revalidatePath).toHaveBeenCalledWith('/');
|
||||
await expect(redirect).toHaveBeenCalledWith('/');
|
||||
|
||||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
|
||||
},
|
||||
};
|
||||
|
||||
export const Login: StoryObj<typeof Component> = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByText('Login'));
|
||||
|
||||
await expect(cookies().set).toHaveBeenCalledWith('user', 'storybookjs');
|
||||
await expect(revalidatePath).toHaveBeenCalledWith('/');
|
||||
await expect(redirect).toHaveBeenCalledWith('/');
|
||||
|
||||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
|
||||
},
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export async function accessRoute() {
|
||||
const user = cookies().get('user');
|
||||
|
||||
if (!user) {
|
||||
redirect('/');
|
||||
}
|
||||
|
||||
revalidatePath('/');
|
||||
redirect(`/protected`);
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
cookies().delete('user');
|
||||
revalidatePath('/');
|
||||
redirect('/');
|
||||
}
|
||||
|
||||
export async function login() {
|
||||
cookies().set('user', 'storybookjs');
|
||||
revalidatePath('/');
|
||||
redirect('/');
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
const Component = () => (
|
||||
<div>
|
||||
<style jsx>{`
|
||||
.main p {
|
||||
color: #ff4785;
|
||||
}
|
||||
`}</style>
|
||||
<main className="main">
|
||||
<p>This is styled using Styled JSX</p>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
};
|
||||
|
||||
export const Default = {};
|
93
code/frameworks/nextjs-vite/template/stories/fonts/OFL.txt
Normal file
93
code/frameworks/nextjs-vite/template/stories/fonts/OFL.txt
Normal file
@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Rubik Filtered Project Authors (https://https://github.com/NaN-xyz/Rubik-Filtered)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Binary file not shown.
8
code/frameworks/nextjs-vite/tsconfig.json
Normal file
8
code/frameworks/nextjs-vite/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "Preserve",
|
||||
"moduleResolution": "Bundler"
|
||||
},
|
||||
"include": ["src/**/*", "template/**/*"]
|
||||
}
|
10
code/frameworks/nextjs-vite/vitest.config.ts
Normal file
10
code/frameworks/nextjs-vite/vitest.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { defineConfig, mergeConfig } from 'vitest/config';
|
||||
import { vitestCommonConfig } from '../../vitest.workspace';
|
||||
|
||||
export default mergeConfig(
|
||||
vitestCommonConfig,
|
||||
defineConfig({
|
||||
// Add custom config here
|
||||
})
|
||||
);
|
@ -196,11 +196,33 @@ const baseTemplates = {
|
||||
renderer: '@storybook/react',
|
||||
builder: '@storybook/builder-webpack5',
|
||||
},
|
||||
modifications: {
|
||||
extraDependencies: ['server-only'],
|
||||
},
|
||||
skipTasks: ['e2e-tests-dev', 'bench'],
|
||||
},
|
||||
'nextjs-vite/default-ts': {
|
||||
name: 'Next.js Latest (Vite | TypeScript)',
|
||||
script:
|
||||
'yarn create next-app {{beforeDir}} --typescript --eslint --tailwind --app --import-alias="@/*" --src-dir',
|
||||
inDevelopment: true,
|
||||
expected: {
|
||||
framework: '@storybook/nextjs-vite',
|
||||
renderer: '@storybook/react',
|
||||
builder: '@storybook/builder-vite',
|
||||
},
|
||||
|
||||
modifications: {
|
||||
mainConfig: {
|
||||
framework: '@storybook/nextjs-vite',
|
||||
features: { experimentalRSC: true },
|
||||
},
|
||||
extraDependencies: ['server-only'],
|
||||
extraDependencies: [
|
||||
'server-only',
|
||||
'vite-plugin-storybook-nextjs',
|
||||
'@storybook/nextjs-vite',
|
||||
'vite',
|
||||
],
|
||||
},
|
||||
skipTasks: ['e2e-tests-dev', 'bench'],
|
||||
},
|
||||
|
426
code/yarn.lock
426
code/yarn.lock
@ -2437,6 +2437,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/runtime@npm:^1.1.1":
|
||||
version: 1.2.0
|
||||
resolution: "@emnapi/runtime@npm:1.2.0"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10c0/7005ff8b67724c9e61b6cd79a3decbdb2ce25d24abd4d3d187472f200ee6e573329c30264335125fb136bd813aa9cf9f4f7c9391d04b07dd1e63ce0a3427be57
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emotion/babel-plugin@npm:^11.11.0":
|
||||
version: 11.11.0
|
||||
resolution: "@emotion/babel-plugin@npm:11.11.0"
|
||||
@ -3369,6 +3378,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-darwin-arm64@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-darwin-arm64@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-darwin-arm64":
|
||||
optional: true
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-darwin-x64@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-darwin-x64@npm:0.33.3"
|
||||
@ -3381,6 +3402,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-darwin-x64@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-darwin-x64@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-darwin-x64": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-darwin-x64":
|
||||
optional: true
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64@npm:1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.2"
|
||||
@ -3449,6 +3482,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-arm64@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-linux-arm64@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-arm64": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-arm64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-arm@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-linux-arm@npm:0.33.3"
|
||||
@ -3461,6 +3506,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-arm@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-linux-arm@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-arm": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-arm":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=arm & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-s390x@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-linux-s390x@npm:0.33.3"
|
||||
@ -3473,6 +3530,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-s390x@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-linux-s390x@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-s390x": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-s390x":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=s390x & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-x64@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-linux-x64@npm:0.33.3"
|
||||
@ -3485,6 +3554,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-x64@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-linux-x64@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-x64": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-x64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linuxmusl-arm64@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.3"
|
||||
@ -3497,6 +3578,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linuxmusl-arm64@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linuxmusl-arm64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linuxmusl-x64@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-linuxmusl-x64@npm:0.33.3"
|
||||
@ -3509,6 +3602,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linuxmusl-x64@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-linuxmusl-x64@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linuxmusl-x64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-wasm32@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-wasm32@npm:0.33.3"
|
||||
@ -3518,6 +3623,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-wasm32@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-wasm32@npm:0.33.4"
|
||||
dependencies:
|
||||
"@emnapi/runtime": "npm:^1.1.1"
|
||||
conditions: cpu=wasm32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-win32-ia32@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-win32-ia32@npm:0.33.3"
|
||||
@ -3525,6 +3639,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-win32-ia32@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-win32-ia32@npm:0.33.4"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-win32-x64@npm:0.33.3":
|
||||
version: 0.33.3
|
||||
resolution: "@img/sharp-win32-x64@npm:0.33.3"
|
||||
@ -3532,6 +3653,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-win32-x64@npm:0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "@img/sharp-win32-x64@npm:0.33.4"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@isaacs/cliui@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||
@ -3788,6 +3916,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:14.2.5, @next/env@npm:^14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/env@npm:14.2.5"
|
||||
checksum: 10c0/63d8b88ac450b3c37940a9e2119a63a1074aca89908574ade6157a8aa295275dcb3ac5f69e00883fc55d0f12963b73b74e87ba32a5768a489f9609c6be57b699
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-darwin-arm64@npm:14.1.0"
|
||||
@ -3795,6 +3930,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-darwin-arm64@npm:14.2.5"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-darwin-x64@npm:14.1.0"
|
||||
@ -3802,6 +3944,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-darwin-x64@npm:14.2.5"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:14.1.0"
|
||||
@ -3809,6 +3958,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:14.2.5"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:14.1.0"
|
||||
@ -3816,6 +3972,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:14.2.5"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:14.1.0"
|
||||
@ -3823,6 +3986,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:14.2.5"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-linux-x64-musl@npm:14.1.0"
|
||||
@ -3830,6 +4000,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-linux-x64-musl@npm:14.2.5"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:14.1.0"
|
||||
@ -3837,6 +4014,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:14.2.5"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-ia32-msvc@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:14.1.0"
|
||||
@ -3844,6 +4028,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-ia32-msvc@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:14.2.5"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:14.1.0"
|
||||
@ -3851,6 +4042,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:14.2.5"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ngtools/webpack@npm:17.3.0":
|
||||
version: 17.3.0
|
||||
resolution: "@ngtools/webpack@npm:17.3.0"
|
||||
@ -6084,6 +6282,35 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@storybook/nextjs-vite@workspace:frameworks/nextjs-vite":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@storybook/nextjs-vite@workspace:frameworks/nextjs-vite"
|
||||
dependencies:
|
||||
"@storybook/builder-vite": "workspace:*"
|
||||
"@storybook/react": "workspace:*"
|
||||
"@storybook/test": "workspace:*"
|
||||
"@types/node": "npm:^18.0.0"
|
||||
next: "npm:^14.2.5"
|
||||
sharp: "npm:^0.33.3"
|
||||
styled-jsx: "npm:5.1.6"
|
||||
typescript: "npm:^5.3.2"
|
||||
vite-plugin-storybook-nextjs: "npm:^0.0.13"
|
||||
peerDependencies:
|
||||
next: ^14.2.5
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
storybook: "workspace:^"
|
||||
vite: ^5.0.0
|
||||
vite-plugin-storybook-nextjs: ^0.0.13
|
||||
dependenciesMeta:
|
||||
sharp:
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@storybook/nextjs@workspace:*, @storybook/nextjs@workspace:frameworks/nextjs":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@storybook/nextjs@workspace:frameworks/nextjs"
|
||||
@ -6964,6 +7191,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/counter@npm:^0.1.3":
|
||||
version: 0.1.3
|
||||
resolution: "@swc/counter@npm:0.1.3"
|
||||
checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:0.5.2":
|
||||
version: 0.5.2
|
||||
resolution: "@swc/helpers@npm:0.5.2"
|
||||
@ -6973,6 +7207,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:0.5.5":
|
||||
version: 0.5.5
|
||||
resolution: "@swc/helpers@npm:0.5.5"
|
||||
dependencies:
|
||||
"@swc/counter": "npm:^0.1.3"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10c0/21a9b9cfe7e00865f9c9f3eb4c1cc5b397143464f7abee76a2c5366e591e06b0155b5aac93fe8269ef8d548df253f6fd931e9ddfc0fd12efd405f90f45506e7d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:~0.5.0":
|
||||
version: 0.5.6
|
||||
resolution: "@swc/helpers@npm:0.5.6"
|
||||
@ -16741,6 +16985,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"image-size@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "image-size@npm:1.1.1"
|
||||
dependencies:
|
||||
queue: "npm:6.0.2"
|
||||
bin:
|
||||
image-size: bin/image-size.js
|
||||
checksum: 10c0/2660470096d12be82195f7e80fe03274689fbd14184afb78eaf66ade7cd06352518325814f88af4bde4b26647889fe49e573129f6e7ba8f5ff5b85cc7f559000
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"image-size@npm:~0.5.0":
|
||||
version: 0.5.5
|
||||
resolution: "image-size@npm:0.5.5"
|
||||
@ -20489,6 +20744,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"module-alias@npm:^2.2.3":
|
||||
version: 2.2.3
|
||||
resolution: "module-alias@npm:2.2.3"
|
||||
checksum: 10c0/47dc5b6d04f6e7df0ff330ca9b2a37c688a682ed661e9432b0b327e1e6c43eedad052151b8d50d6beea8b924828d2a92fa4625c18d651bf2d93d8f03aa0172fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mri@npm:^1.1.0, mri@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "mri@npm:1.2.0"
|
||||
@ -20683,6 +20945,64 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:^14.2.5":
|
||||
version: 14.2.5
|
||||
resolution: "next@npm:14.2.5"
|
||||
dependencies:
|
||||
"@next/env": "npm:14.2.5"
|
||||
"@next/swc-darwin-arm64": "npm:14.2.5"
|
||||
"@next/swc-darwin-x64": "npm:14.2.5"
|
||||
"@next/swc-linux-arm64-gnu": "npm:14.2.5"
|
||||
"@next/swc-linux-arm64-musl": "npm:14.2.5"
|
||||
"@next/swc-linux-x64-gnu": "npm:14.2.5"
|
||||
"@next/swc-linux-x64-musl": "npm:14.2.5"
|
||||
"@next/swc-win32-arm64-msvc": "npm:14.2.5"
|
||||
"@next/swc-win32-ia32-msvc": "npm:14.2.5"
|
||||
"@next/swc-win32-x64-msvc": "npm:14.2.5"
|
||||
"@swc/helpers": "npm:0.5.5"
|
||||
busboy: "npm:1.6.0"
|
||||
caniuse-lite: "npm:^1.0.30001579"
|
||||
graceful-fs: "npm:^4.2.11"
|
||||
postcss: "npm:8.4.31"
|
||||
styled-jsx: "npm:5.1.1"
|
||||
peerDependencies:
|
||||
"@opentelemetry/api": ^1.1.0
|
||||
"@playwright/test": ^1.41.2
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
sass: ^1.3.0
|
||||
dependenciesMeta:
|
||||
"@next/swc-darwin-arm64":
|
||||
optional: true
|
||||
"@next/swc-darwin-x64":
|
||||
optional: true
|
||||
"@next/swc-linux-arm64-gnu":
|
||||
optional: true
|
||||
"@next/swc-linux-arm64-musl":
|
||||
optional: true
|
||||
"@next/swc-linux-x64-gnu":
|
||||
optional: true
|
||||
"@next/swc-linux-x64-musl":
|
||||
optional: true
|
||||
"@next/swc-win32-arm64-msvc":
|
||||
optional: true
|
||||
"@next/swc-win32-ia32-msvc":
|
||||
optional: true
|
||||
"@next/swc-win32-x64-msvc":
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
"@opentelemetry/api":
|
||||
optional: true
|
||||
"@playwright/test":
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
bin:
|
||||
next: dist/bin/next
|
||||
checksum: 10c0/8df7d8ccc1a5bab03fa50dd6656c8a6f3750e81ef0b087dc329fea9346847c3094a933a890a8e87151dc32f0bc55020b8f6386d4565856d83bcc10895d29ec08
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nice-napi@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "nice-napi@npm:1.0.2"
|
||||
@ -24953,6 +25273,75 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sharp@npm:^0.33.4":
|
||||
version: 0.33.4
|
||||
resolution: "sharp@npm:0.33.4"
|
||||
dependencies:
|
||||
"@img/sharp-darwin-arm64": "npm:0.33.4"
|
||||
"@img/sharp-darwin-x64": "npm:0.33.4"
|
||||
"@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
|
||||
"@img/sharp-libvips-darwin-x64": "npm:1.0.2"
|
||||
"@img/sharp-libvips-linux-arm": "npm:1.0.2"
|
||||
"@img/sharp-libvips-linux-arm64": "npm:1.0.2"
|
||||
"@img/sharp-libvips-linux-s390x": "npm:1.0.2"
|
||||
"@img/sharp-libvips-linux-x64": "npm:1.0.2"
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
|
||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
|
||||
"@img/sharp-linux-arm": "npm:0.33.4"
|
||||
"@img/sharp-linux-arm64": "npm:0.33.4"
|
||||
"@img/sharp-linux-s390x": "npm:0.33.4"
|
||||
"@img/sharp-linux-x64": "npm:0.33.4"
|
||||
"@img/sharp-linuxmusl-arm64": "npm:0.33.4"
|
||||
"@img/sharp-linuxmusl-x64": "npm:0.33.4"
|
||||
"@img/sharp-wasm32": "npm:0.33.4"
|
||||
"@img/sharp-win32-ia32": "npm:0.33.4"
|
||||
"@img/sharp-win32-x64": "npm:0.33.4"
|
||||
color: "npm:^4.2.3"
|
||||
detect-libc: "npm:^2.0.3"
|
||||
semver: "npm:^7.6.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-darwin-arm64":
|
||||
optional: true
|
||||
"@img/sharp-darwin-x64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-darwin-arm64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-darwin-x64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-arm":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-arm64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-s390x":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-x64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linuxmusl-arm64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linuxmusl-x64":
|
||||
optional: true
|
||||
"@img/sharp-linux-arm":
|
||||
optional: true
|
||||
"@img/sharp-linux-arm64":
|
||||
optional: true
|
||||
"@img/sharp-linux-s390x":
|
||||
optional: true
|
||||
"@img/sharp-linux-x64":
|
||||
optional: true
|
||||
"@img/sharp-linuxmusl-arm64":
|
||||
optional: true
|
||||
"@img/sharp-linuxmusl-x64":
|
||||
optional: true
|
||||
"@img/sharp-wasm32":
|
||||
optional: true
|
||||
"@img/sharp-win32-ia32":
|
||||
optional: true
|
||||
"@img/sharp-win32-x64":
|
||||
optional: true
|
||||
checksum: 10c0/428c5c6a84ff8968effe50c2de931002f5f30b9f263e1c026d0384e581673c13088a49322f7748114d3d9be4ae9476a74bf003a3af34743e97ef2f880d1cfe45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shebang-command@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "shebang-command@npm:1.2.0"
|
||||
@ -25884,6 +26273,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"styled-jsx@npm:5.1.6":
|
||||
version: 5.1.6
|
||||
resolution: "styled-jsx@npm:5.1.6"
|
||||
dependencies:
|
||||
client-only: "npm:0.0.1"
|
||||
peerDependencies:
|
||||
react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
|
||||
peerDependenciesMeta:
|
||||
"@babel/core":
|
||||
optional: true
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
checksum: 10c0/ace50e7ea5ae5ae6a3b65a50994c51fca6ae7df9c7ecfd0104c36be0b4b3a9c5c1a2374d16e2a11e256d0b20be6d47256d768ecb4f91ab390f60752a075780f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stylis@npm:4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "stylis@npm:4.2.0"
|
||||
@ -27742,6 +28147,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-plugin-storybook-nextjs@npm:^0.0.13":
|
||||
version: 0.0.13
|
||||
resolution: "vite-plugin-storybook-nextjs@npm:0.0.13"
|
||||
dependencies:
|
||||
"@next/env": "npm:^14.2.5"
|
||||
image-size: "npm:^1.1.1"
|
||||
module-alias: "npm:^2.2.3"
|
||||
sharp: "npm:^0.33.4"
|
||||
ts-dedent: "npm:^2.2.0"
|
||||
peerDependencies:
|
||||
"@storybook/test": ^8.3.0-alpha.3
|
||||
next: ^14.2.5
|
||||
storybook: ^8.3.0-alpha.3
|
||||
vite: ^5.0.0
|
||||
dependenciesMeta:
|
||||
sharp:
|
||||
optional: true
|
||||
checksum: 10c0/331e43c91f6c395e3ed87b6a2f372f1372fa7dfc00d0cb8e0a9daad129d779f6f56b5d01fb4b1ab6c231917c458189c02535ecbe2d9d447bc8bf4c3939cc42aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:5.1.5, vite@npm:^5.0.0":
|
||||
version: 5.1.5
|
||||
resolution: "vite@npm:5.1.5"
|
||||
|
Loading…
x
Reference in New Issue
Block a user