;
21 | };
22 | CapitalReplaced.storyName = "Champs Élysées";
23 |
--------------------------------------------------------------------------------
/packages/ladle/tests/mdx-to-stories.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "vitest";
2 | import { VFile } from "vfile";
3 | import { createFormatAwareProcessors } from "@mdx-js/mdx/internal-create-format-aware-processors";
4 | import mdxToStories from "../lib/cli/vite-plugin/mdx-to-stories.js";
5 | import fs from "fs/promises";
6 |
7 | const { process } = createFormatAwareProcessors({
8 | jsx: true,
9 | });
10 |
11 | const getFixture = async (path: string) => {
12 | const value = await fs.readFile(new URL(path, import.meta.url), {
13 | encoding: "utf8",
14 | });
15 | const file = new VFile({ value, path });
16 | const compiled = await process(file);
17 | return [String(compiled.value), path];
18 | };
19 |
20 | test(" component works", async () => {
21 | const fixture = await getFixture("./fixtures/story.stories.mdx");
22 | const output = await mdxToStories(...fixture);
23 | expect(output).toMatchSnapshot();
24 | });
25 |
26 | test(" component works", async () => {
27 | const fixture = await getFixture("./fixtures/meta.stories.mdx");
28 | const output = await mdxToStories(...fixture);
29 | expect(output).toMatchSnapshot();
30 | });
31 |
--------------------------------------------------------------------------------
/packages/ladle/tests/naming-utils.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "vitest";
2 | import { kebabCase } from "../lib/cli/vite-plugin/naming-utils.js";
3 |
4 | test("kebabCase", () => {
5 | expect(kebabCase("ChampsÉlysées")).toBe("champs-élysées");
6 | expect(kebabCase("our-animals--mammals")).toBe("our-animals--mammals");
7 | expect(kebabCase("the quick brown fox")).toBe("the-quick-brown-fox");
8 | expect(kebabCase("the-quick-brown-fox")).toBe("the-quick-brown-fox");
9 | expect(kebabCase("the_quick_brown_fox")).toBe("the-quick-brown-fox");
10 | expect(kebabCase("theQuickBrownFox")).toBe("the-quick-brown-fox");
11 | expect(kebabCase("thequickbrownfox")).toBe("thequickbrownfox");
12 | expect(kebabCase("theQUICKBrownFox")).toBe("the-quick-brown-fox");
13 | });
14 |
--------------------------------------------------------------------------------
/packages/ladle/tests/parse/.pnpm-debug.log:
--------------------------------------------------------------------------------
1 | {
2 | "0 debug pnpm:scope": {
3 | "selected": 1,
4 | "workspacePrefix": "/Users/vojtech/Projects/ladle"
5 | },
6 | "1 error pnpm": {
7 | "errno": 1,
8 | "code": "ELIFECYCLE",
9 | "pkgid": "@ladle/react@2.4.2",
10 | "stage": "test",
11 | "script": "vitest",
12 | "pkgname": "@ladle/react",
13 | "err": {
14 | "name": "pnpm",
15 | "message": "@ladle/react@2.4.2 test: `vitest`\nExit status 1",
16 | "code": "ELIFECYCLE",
17 | "stack": "pnpm: @ladle/react@2.4.2 test: `vitest`\nExit status 1\n at EventEmitter. (/Users/vojtech/.node/corepack/pnpm/7.3.0/dist/pnpm.cjs:108415:20)\n at EventEmitter.emit (node:events:527:28)\n at ChildProcess. (/Users/vojtech/.node/corepack/pnpm/7.3.0/dist/pnpm.cjs:94981:18)\n at ChildProcess.emit (node:events:527:28)\n at maybeClose (node:internal/child_process:1092:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5)"
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/packages/ladle/tests/parse/get-entry-data.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "vitest";
2 | import { getSingleEntry } from "../../lib/cli/vite-plugin/parse/get-entry-data.js";
3 |
4 | test("Single file with two stories", async () => {
5 | const entryData = await getSingleEntry("tests/fixtures/animals.stories.tsx");
6 | expect(entryData).toMatchSnapshot();
7 | });
8 |
9 | test("Capital letters in story names converted into delimiters", async () => {
10 | const entryData = await getSingleEntry(
11 | "tests/fixtures/capitalization.stories.tsx",
12 | );
13 | expect(entryData).toMatchSnapshot();
14 | });
15 |
16 | test("Capital letters in the filename converted into delimiters", async () => {
17 | const entryData = await getSingleEntry(
18 | "tests/fixtures/filenameCapitalization.stories.tsx",
19 | );
20 | expect(entryData).toMatchSnapshot();
21 | });
22 |
23 | test("Turn file name delimiters into spaces and levels correctly", async () => {
24 | const entryData = await getSingleEntry(
25 | "tests/fixtures/our-animals--mammals.stories.tsx",
26 | );
27 | expect(entryData).toMatchSnapshot();
28 | });
29 |
30 | test("Default title is used instead of the file name", async () => {
31 | const entryData = await getSingleEntry(
32 | "tests/fixtures/default-title.stories.tsx",
33 | );
34 | expect(entryData).toMatchSnapshot();
35 | });
36 |
37 | test("Story name replaces named export as a story name", async () => {
38 | const entryData = await getSingleEntry(
39 | "tests/fixtures/storyname.stories.tsx",
40 | );
41 | expect(entryData).toMatchSnapshot();
42 | });
43 |
44 | test("Extract default meta", async () => {
45 | const entryData = await getSingleEntry(
46 | "tests/fixtures/default-meta.stories.tsx",
47 | );
48 | expect(entryData).toMatchSnapshot();
49 | });
50 |
51 | test("Extract and merge story meta", async () => {
52 | const entryData = await getSingleEntry(
53 | "tests/fixtures/story-meta.stories.tsx",
54 | );
55 | expect(entryData).toMatchSnapshot();
56 | });
57 |
--------------------------------------------------------------------------------
/packages/ladle/tests/parse/utils.ts:
--------------------------------------------------------------------------------
1 | import traverse from "@babel/traverse";
2 | import cloneDeep from "../../lib/cli/deps/lodash.clonedeep.js";
3 | import merge from "lodash.merge";
4 | import getAst from "../../lib/cli/vite-plugin/get-ast.js";
5 | import type { ParsedStoriesResult } from "../../lib/shared/types";
6 |
7 | export const parseWithFn = (
8 | code: string,
9 | input: Partial,
10 | fn: any,
11 | visitor: string,
12 | filename = "foo.stories.js",
13 | ): ParsedStoriesResult => {
14 | const start: ParsedStoriesResult = merge(
15 | {
16 | entry: "file.js",
17 | stories: [],
18 | exportDefaultProps: { title: undefined, meta: undefined },
19 | namedExportToMeta: {},
20 | namedExportToStoryName: {},
21 | storyParams: {},
22 | fileId: "file",
23 | },
24 | input,
25 | );
26 | const end: ParsedStoriesResult = cloneDeep(start);
27 | (traverse as any)(getAst(code, filename) as any, {
28 | [visitor]: fn.bind(this, end),
29 | });
30 | return end;
31 | };
32 |
33 | export const getOutput = (
34 | input: Partial,
35 | ): ParsedStoriesResult => {
36 | return merge(
37 | {
38 | entry: "file.js",
39 | stories: [],
40 | exportDefaultProps: { title: undefined, meta: undefined },
41 | namedExportToMeta: {},
42 | namedExportToStoryName: {},
43 | storyParams: {},
44 | fileId: "file",
45 | },
46 | input,
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/packages/ladle/tests/story-name.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect, describe } from "vitest";
2 | import { sortStories } from "../lib/app/src/story-name";
3 |
4 | const identity = (s: string[]) => s;
5 |
6 | describe("sortStories", () => {
7 | test("single level with default sort", () => {
8 | expect(sortStories(["a", "c", "b"], identity)).toStrictEqual([
9 | "a",
10 | "b",
11 | "c",
12 | ]);
13 | });
14 | test("two levels with default sort", () => {
15 | expect(
16 | sortStories(["a--b", "a--a", "a", "c--b", "b"], identity),
17 | ).toStrictEqual(["a--a", "a--b", "c--b", "a", "b"]);
18 | });
19 | test("config.sortOrder is array", () => {
20 | expect(
21 | sortStories(["a--b", "a--a", "a", "c", "b"], ["a", "b"]),
22 | ).toStrictEqual(["a", "b"]);
23 | });
24 | test("config.sortOrder is array with non-existing story", () => {
25 | expect(() =>
26 | sortStories(["a--b", "a--a", "a", "c", "b"], ["a", "b", "x"]),
27 | ).toThrow(
28 | `Story "x" does not exist in your storybook. Please check your storyOrder config.`,
29 | );
30 | });
31 | test("config.sortOrder is array with wild card", () => {
32 | expect(
33 | sortStories(
34 | ["a--b", "a--a", "a", "c--a", "c--b", "b", "b--x"],
35 | ["c--*", "a*", "b", "a"],
36 | ),
37 | ).toStrictEqual(["c--a", "c--b", "a--a", "a--b", "a", "b"]);
38 | });
39 | test("config.sortOrder is fn", () => {
40 | expect(
41 | sortStories(["a--b", "a--a", "a", "c--a", "c--b", "b", "b--x"], () => [
42 | "c--a",
43 | "b",
44 | "b--x",
45 | ]),
46 | ).toStrictEqual(["c--a", "b", "b--x"]);
47 | });
48 | test("config.sortOrder is fn with wild card", () => {
49 | expect(
50 | sortStories(["a--b", "a--a", "a", "c--a", "c--b", "b", "b--x"], () => [
51 | "c--*",
52 | "a*",
53 | "b",
54 | "a",
55 | ]),
56 | ).toStrictEqual(["c--a", "c--b", "a--a", "a--b", "a", "b"]);
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/packages/ladle/tsconfig.typesoutput.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "noEmit": false,
5 | "declaration": true,
6 | "emitDeclarationOnly": false,
7 | "declarationDir": "typings-for-build",
8 | "outDir": "typings-for-build"
9 | },
10 | "include": ["lib/**/*", "src/**/*"],
11 | "exclude": ["typings-for-build"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/ladle/types/generated-list.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { GlobalState, GlobalAction, Config } from "../lib/shared/types";
3 | import { Args, ArgTypes } from "../lib/app/exports";
4 |
5 | type ReactNodeWithoutObject =
6 | | React.ReactElement
7 | | string
8 | | number
9 | | boolean
10 | | null
11 | | undefined;
12 |
13 | declare module "virtual:generated-list" {
14 | export const list: string[];
15 | export const config: Config;
16 | export const errorMessage: string;
17 | export const args: Args;
18 | export const argTypes: ArgTypes;
19 | export const stories: {
20 | [key: string]: {
21 | entry: string;
22 | locStart: number;
23 | locEnd: number;
24 | component: React.FC;
25 | meta: any;
26 | };
27 | };
28 | export const storySource: { [key: string]: string };
29 | export const Provider: React.FC<{
30 | globalState: GlobalState;
31 | dispatch: React.Dispatch;
32 | config: Config;
33 | children: ReactNodeWithoutObject;
34 | storyMeta?: any;
35 | }>;
36 | export const StorySourceHeader: React.FC<{
37 | path: string;
38 | locStart: number;
39 | locEnd: number;
40 | }>;
41 | }
42 |
--------------------------------------------------------------------------------
/packages/website/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/packages/website/README.md:
--------------------------------------------------------------------------------
1 | # Website
2 |
3 | This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
4 |
5 | ## Installation
6 |
7 | ```console
8 | pnpm install
9 | ```
10 |
11 | ## Local Development
12 |
13 | ```console
14 | pnpm start
15 | ```
16 |
17 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
18 |
19 | ## Build
20 |
21 | ```console
22 | pnpm build
23 | ```
24 |
25 | This command generates static content into the `build` directory and can be served using any static contents hosting service.
26 |
27 | ## Deployment
28 |
29 | ```console
30 | GIT_USER= USE_SSH=true pnpm deploy
31 | ```
32 |
33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
34 |
--------------------------------------------------------------------------------
/packages/website/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
3 | };
4 |
--------------------------------------------------------------------------------
/packages/website/blog/authors.yml:
--------------------------------------------------------------------------------
1 | vojtech:
2 | name: Vojtech Miksu
3 | title: Developer Platform, Uber
4 | url: https://www.miksu.cz
5 | image_url: https://miksu.cz/images/profile.jpg
6 | email: vojtech@miksu.cz
7 | socials:
8 | x: vmiksu
9 | github: tajo
10 |
--------------------------------------------------------------------------------
/packages/website/docs/a11y.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | id: a11y
3 | title: Accessibility
4 | ---
5 |
6 | import Image from "@theme/IdealImage";
7 | import imgA11y from "../static/img/a11y.png";
8 |
9 | Ladle is accessible. If you want to build accessible component, the wrapping environment should be accessible and keyboard friendly too. That's why your story comes first and the side navigation / addons second.
10 |
11 | Ladle also includes an a11y testing through [axe-core](https://github.com/dequelabs/axe-core), so you can fix any a11y violations in your components.
12 |
13 | You have to enable it in your `.ladle/config.mjs`:
14 |
15 | ```js
16 | /** @type {import('@ladle/react').UserConfig} */
17 | export default {
18 | addons: {
19 | a11y: {
20 | enabled: true,
21 | },
22 | },
23 | };
24 | ```
25 |
26 |
27 |
--------------------------------------------------------------------------------
/packages/website/docs/actions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | id: actions
3 | title: Actions
4 | ---
5 |
6 | import Image from "@theme/IdealImage";
7 | import imgActions from "../static/img/actions.png";
8 |
9 | When your stories have interactive elements you can track user actions through this addon. It's a simple alternative to the combination of `console.log` and devtools. It makes the output easier to notice and parse.
10 |
11 | ```tsx
12 | import type { Story } from "@ladle/react";
13 |
14 | export const MyStory: Story<{
15 | onClick: () => void;
16 | }> = ({ onClick }) => {
17 | return ;
18 | };
19 |
20 | MyStory.argTypes = {
21 | onClick: {
22 | action: "clicked",
23 | },
24 | };
25 | ```
26 |
27 | The click on this button creates a notification so you can inspect the event that was emitted:
28 |
29 |
30 |
31 | # Direct usage
32 |
33 | You can also use this feature directly without argTypes:
34 |
35 | ```tsx
36 | import { action } from "@ladle/react";
37 |
38 | export const MyStory = () => (
39 |
40 | );
41 | ```
42 |
43 | The only argument of `action` is the label describing the event.
44 |
--------------------------------------------------------------------------------
/packages/website/docs/addons.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: addons
3 | title: Addons
4 | ---
5 |
6 | Ladle currently does not support third-party addons, but it might in the future. For now, it comes with a set of default addons you can find in the bottom left corner (button icons):
7 |
8 | - [Accessibility](./a11y) (Axe)
9 | - [Background](./background)
10 | - [Controls](./controls) (only if `args` or `argTypes` are defined)
11 | - Dark theme
12 | - [Links](./links)
13 | - [MSW](./msw) (API mocking)
14 | - Preview mode
15 | - Right-to-left
16 | - [Story source code](./source)
17 | - [Width](./width)
18 |
19 | There are also other features you might not even notice at first:
20 |
21 | - Addons and all their state is persisted through the URL. That's useful for sharing or testing a specific story state. The browser navigation works as expected.
22 | - **Ladle is accessible**. If you want to build an accessible component, the wrapping environment should be accessible and keyboard friendly too. That's why your story comes first and the side navigation / addons second.
23 | - **Ladle is responsive**. There is no addon for different viewports. Just use your browser's dev tools.
24 | - Ladle is a single application with no iframes but is careful to not affect your stories in unintended ways. This makes the build faster and smaller and debugging through _Elements_ more straightforward.
25 | - Ladle code-splits stories by default.
26 | - React Fast Refresh is enabled by default.
27 |
28 | ## Storybook interoperability
29 |
30 | Ladle currently supports or partially supports most major features of the [Component Story Format](https://storybook.js.org/docs/react/api/csf). We are long-time users of Storybook, and it is our priority to make the transition between the two tools as seamless as possible (aka no changes in the user code). However, the goal is not to implement every single feature or addon that Storybook provides.
31 |
32 | Some features that are currently missing but are on our roadmap:
33 |
34 | - Generating controls through TS types
35 |
--------------------------------------------------------------------------------
/packages/website/docs/babel.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: babel
3 | title: Babel
4 | ---
5 |
6 | Ladle uses [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) by default. This plugin uses [SWC](https://swc.rs/) to transform all React code and is many times faster than Babel.
7 |
8 | If you want to use [Babel](https://babeljs.io/) instead of SWC (in case you need to apply some custom babel plugins), you can do it by installing and adding [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react) into `./vite.config.js`:
9 |
10 | ```js title="vite.config.js"
11 | import react from "@vitejs/plugin-react";
12 |
13 | export default {
14 | plugins: [react()],
15 | };
16 | ```
17 |
18 | Ladle automatically disables the default SWC plugin.
19 |
20 | SWC is up to 20x faster than Babel and can supercharge your development experience. The only downside is that you can't utilize a rich ecosystem of Babel plugins if your project uses a special syntax. However, there are also a few SWC [plugins](https://github.com/swc-project/plugins) and there is a [plugin API](https://swc.rs/docs/plugin/ecmascript/getting-started) so you can write your own.
21 |
--------------------------------------------------------------------------------
/packages/website/docs/background.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: background
3 | title: Background
4 | ---
5 |
6 | You can set the background color of the story canvas through a special [control](./controls) with type `background`:
7 |
8 | ```tsx title=".ladle/components.tsx"
9 | export const argTypes = {
10 | background: {
11 | control: { type: "background" },
12 | options: ["purple", "blue", "white", "pink"],
13 | defaultValue: "purple",
14 | },
15 | };
16 | ```
17 |
18 | This will make it accessible to all stories through the standard control's UI. You can also set it per story level as other controls and even override the global settings:
19 |
20 | ```tsx title="src/hello.stories.tsx"
21 | export const Story = () =>
Hello
;
22 | Story.argTypes = {
23 | background: {
24 | name: "Canvas background",
25 | control: { type: "background" },
26 | options: ["green", "yellow", "pink"],
27 | defaultValue: "pink",
28 | },
29 | };
30 | ```
31 |
32 | Note that there can be only one active background control.
33 |
--------------------------------------------------------------------------------
/packages/website/docs/decorators.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: decorators
3 | title: Decorators
4 | ---
5 |
6 | Ladle supports story decorators, so you can wrap all stories in a file with additional React component(s). This is useful if your stories/components rely on [React Context](https://reactjs.org/docs/context.html) and libraries like `react-router`, `redux` or [next](https://ladle.dev/docs/nextjs/). In the example below, we are adding an extra `margin: 3em` to each story:
7 |
8 | ```tsx
9 | import type { StoryDefault } from "@ladle/react";
10 |
11 | export default {
12 | decorators: [
13 | (Component) => (
14 |
15 |
16 |
17 | ),
18 | ],
19 | } satisfies StoryDefault;
20 | ```
21 |
22 | `decorators` is an array, so you can compose multiple components together. You can also set a [global decorator or provider](./providers).
23 |
24 | Decorators can be also applied to a specific story:
25 |
26 | ```tsx
27 | import type { Story } from "@ladle/react";
28 |
29 | export const MyStory: Story = () =>
36 | ),
37 | ];
38 | ```
39 |
40 | ## Context Parameter
41 |
42 | You can also access Ladle's context through the second parameter. This way, your decorators can control every aspect of Ladle, including the state of controls and other addons:
43 |
44 | ```tsx
45 | import type { StoryDefault, Story } from "@ladle/react";
46 |
47 | type Props = { label: string };
48 |
49 | export default {
50 | decorators: [
51 | (Component, context) => (
52 |
;
61 | Card.args = {
62 | label: "Hello",
63 | };
64 | ```
65 |
--------------------------------------------------------------------------------
/packages/website/docs/hotkeys.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | id: hotkeys
3 | title: Hotkeys
4 | ---
5 |
6 | import Image from "@theme/IdealImage";
7 | import imgHotkeys from "../static/img/hotkeys.png";
8 |
9 | Ladle has a few hotkeys to make your life easier:
10 |
11 | - `/` or `⌘ cmd + p` - Focus search input in the sidebar
12 | - `⌥ opt + →` - Go to the next story
13 | - `⌥ opt + ←` - Go to the previous story
14 | - `⌥ opt + ↓` - Go to the next component
15 | - `⌥ opt + ↑` - Go to the previous component
16 | - `c` - Toggle controls addon
17 | - `d` - Toggle dark mode
18 | - `f` - Toggle fullscreen mode
19 | - `w` - Toggle width addon
20 | - `r` - Toggle right-to-left mode
21 | - `s` - Toggle story source addon
22 | - `a` - Toggle accessibility addon
23 |
24 | ## Settings
25 |
26 | These defaults can be customized through the [configuration](config#hotkeys).
27 |
28 | Some stories might have utilize their own set of hotkeys. If you want to prevent conflicts with Ladle, you can disable all Ladle shortcuts for a specific story by using the `meta` parameter:
29 |
30 | ```tsx
31 | export default {
32 | meta: {
33 | hotkeys: false,
34 | },
35 | };
36 | Story.meta = {
37 | hotkeys: false,
38 | };
39 | ```
40 |
41 | ## Cheat Sheet
42 |
43 | If you need a quick reminder of all the hotkeys, you can open the about addon:
44 |
45 |
46 |
--------------------------------------------------------------------------------
/packages/website/docs/introduction.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | id: introduction
3 | title: Introduction
4 | slug: /
5 | ---
6 |
7 | import Image from "@theme/IdealImage";
8 | import imgBaseweb from "../static/img/ladle-baseweb.png";
9 |
10 | **Ladle is a drop-in alternative to Storybook**. It is a tool for developing and testing your React components in an environment that's isolated and faster than most real-world applications. Ladle also creates an index of your components, so you can easily test them through tools like Playwright.
11 |
12 |
13 |
14 | ## Why? Performance!
15 |
16 | Ladle supports only React, embraces the latest standards (ES Modules) and focuses on performance. It's built around [Vite](https://vitejs.dev/) - modules are directly served to the browser and the bundling step is completely skipped. This means **instant server starts** no matter how many components it needs to load.
17 |
18 | Ladle still produces an optimized bundle using [rollup](https://rollupjs.org/guide/en/) when it's time to deploy it. Without adding a single component **Storybook 6.4 outputs 5.1MB of assets. Ladle only 250KB**. Ladle is almost 20x smaller.
19 |
20 | Each Ladle story gets automatically code-split, so it doesn't matter how many components you want it to handle. Ladle always loads fast.
21 |
--------------------------------------------------------------------------------
/packages/website/docs/links.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: links
3 | title: Links
4 | ---
5 |
6 | You can link story from another story:
7 |
8 | ```tsx
9 | import * as React from "react";
10 | import { linkTo } from "@ladle/react";
11 | import type { Story } from "@ladle/react";
12 |
13 | export const Link: Story = () => {
14 | return ;
15 | };
16 | ```
17 |
18 | The id used `controls--first` is what you find in the URL as the `?story=` parameter.
19 |
--------------------------------------------------------------------------------
/packages/website/docs/meta.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: meta
3 | title: Meta
4 | ---
5 |
6 | Ladle exports `meta.json` with the list of all stories and some additional information. In the `serve` mode, it is accessible as `http://localhost:61000/meta.json` and `build` just outputs `meta.json` into the build folder. Example:
7 |
8 | ```json title="meta.json"
9 | {
10 | "about": {
11 | "homepage": "https://www.ladle.dev",
12 | "github": "https://github.com/tajo/ladle",
13 | "version": 1
14 | },
15 | "stories": {
16 | "control--first": {
17 | "name": "First",
18 | "levels": ["Control"],
19 | "locStart": 12,
20 | "locEnd": 12,
21 | "filePath": "src/control.stories.tsx",
22 | "meta": {}
23 | }
24 | }
25 | }
26 | ```
27 |
28 | You can also add additional annotations to each story (or stories) through the `meta` object. It needs to be statically analyzable.
29 |
30 | ```tsx title="control.stories.tsx"
31 | export default {
32 | meta: {
33 | baseweb: "test",
34 | browsers: ["chrome"],
35 | },
36 | };
37 |
38 | export const First = () =>
First
;
39 | First.meta = {
40 | browsers: ["firefox"],
41 | };
42 | ```
43 |
44 | ```json title="meta.json"
45 | {
46 | "stories": {
47 | "control--first": {
48 | "name": "First",
49 | "levels": ["Control"],
50 | "locStart": 8,
51 | "locEnd": 8,
52 | "filePath": "src/control.stories.tsx",
53 | "meta": { "baseweb": "test", "browsers": ["firefox"] }
54 | }
55 | }
56 | }
57 | ```
58 |
59 | This is useful for further automation. For example, you can load this file in your CI and create visual snapshots for each story.
60 |
61 | ## Testing
62 |
63 | If you use Ladle for end-to-end testing with a framework as [Playwright](https://playwright.dev/), make sure your story is fully loaded before you run the test. Stories are code-split and loaded later in the process. Ladle adds `data-storyloaded` attribute to the `` tag, so you can `await` for it in Playwright:
64 |
65 | ```tsx
66 | await page.waitForSelector("[data-storyloaded]");
67 | ```
68 |
--------------------------------------------------------------------------------
/packages/website/docs/mock-date.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | id: mock-date
3 | title: Mock Date
4 | ---
5 |
6 | When you test your stories, it might be useful to mock `new Date()` so the displayed components always render same values. You can use `meta.mockDate` parameter to set a specific date and time:
7 |
8 | ```js title="date-picker.stories.tsx"
9 | import type { Story } from "@ladle/react";
10 |
11 | export const DatePicker: Story = () => {
12 | const date = new Date();
13 | return (
14 |
{date.toLocaleDateString("en-US")}
15 | );
16 | };
17 |
18 | DatePicker.meta = {
19 | mockDate: "1995-12-17T03:24:00",
20 | };
21 | ```
22 |
--------------------------------------------------------------------------------
/packages/website/docs/programmatic.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: programmatic
3 | title: Programmatic
4 | ---
5 |
6 | Ladle can be also used through its JavaScript API:
7 |
8 | ```tsx
9 | import serve from "@ladle/react/serve";
10 | import build from "@ladle/react/build";
11 | import preview from "@ladle/react/preview";
12 |
13 | await serve({
14 | // config: {}
15 | });
16 | await build({
17 | // config: {}
18 | });
19 | await preview({
20 | // config: {}
21 | });
22 | ```
23 |
24 | Explore all config.mjs [options](./config#ladleconfigmjs).
25 |
--------------------------------------------------------------------------------
/packages/website/docs/source.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | id: source
3 | title: Story Source
4 | ---
5 |
6 | import Image from "@theme/IdealImage";
7 | import imgSource from "../static/img/story-source.png";
8 |
9 | You can preview the source code of the active story and its origin.
10 |
11 |
12 |
13 | ## Hyperlink
14 |
15 | You can customize the header of the source addon through `.ladle/components.tsx`:
16 |
17 | ```tsx title=".ladle/components.tsx"
18 | import type { SourceHeader } from "@ladle/react";
19 | export const StorySourceHeader: SourceHeader = ({ path }) => {
20 | return (
21 |
22 | Github link? {path}
23 |
24 | );
25 | };
26 | ```
27 |
28 | This might be useful if you want provide a hyperlink.
29 |
--------------------------------------------------------------------------------
/packages/website/docs/troubleshooting.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: troubleshooting
3 | title: Troubleshooting
4 | ---
5 |
6 | If you run into problems, try to enable verbose output:
7 |
8 | ```bash
9 | DEBUG=ladle* pnpm ladle serve
10 | DEBUG=ladle* pnpm ladle build
11 | ```
12 |
13 | You can also enable verbose output in the browser console by adding an item into local storage `debug: ladle*` where `debug` is the key and `ladle*` the value. In Chrome, you can do that by opening the dev tools and insert this into the console:
14 |
15 | ```
16 | localStorage.debug = 'ladle*'
17 | ```
18 |
19 | ## Create Issue
20 |
21 | You can also search [existing issues](https://github.com/tajo/ladle/issues) or add a new one.
22 |
23 | ## Discord
24 |
25 | Join our [community](https://discord.gg/H6FSHjyW7e).
26 |
27 | ## ES Modules
28 |
29 | Ladle embraces ES Modules and is implemented as an ES module. That requires `Node 20+` and environment that fully supports ESM.
30 |
--------------------------------------------------------------------------------
/packages/website/docs/typescript.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: typescript
3 | title: TypeScript
4 | ---
5 |
6 | # TypeScript
7 |
8 | Ladle is written in TypeScript and provides first-class support for TypeScript.
9 |
10 | ## `tsconfig.json`
11 |
12 | Ladle uses [jsx-runtime](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) so there is not need to import React at the top of each module. However, you need to let TypeScript know:
13 |
14 | ```json title="tsconfig.json"
15 | {
16 | "compilerOptions": {
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src", ".ladle"]
20 | }
21 | ```
22 |
23 | ## Exported Types
24 |
25 | You can import [many types](https://github.com/tajo/ladle/blob/main/packages/ladle/lib/app/exports.ts#L52-L115) from `@ladle/react` to improve your development experience:
26 |
27 | ```ts
28 | import type { StoryDefault, Story } from "@ladle/react";
29 |
30 | type Props = { label: string };
31 |
32 | export default {
33 | title: "New title",
34 | } satisfies StoryDefault;
35 |
36 | const Card: Story = ({ label }) =>