Documenting Storybook with TS and JSDocs

/** 
 * JSDoc 
 */

JSDoc

/**
/** Adds two numbers together */
function add(a, b) {
  return a + b;
}

JSDoc

/**
 * Adds two numbers together
 * @deprecated since version 2.0
 * @param {number} a any number
 * @param {number} b any number
 */
function add(a, b) {
  return a + b;
}

JSDoc

Benefits

  • Documentation lives with the code.
  • Tools can create documentation from those code comments

Limitations

  • Advanced types are not supported
    •  e.g. generics
  • Types are only documented and not enforced
// No errors, including runtime
add("1", 2) // returns "12"

TypeScript

function add(a: number, b: number): number {
  return a + b;
}

TypeScript

Benefits

  • Advanced types are supported
  • New JavaScript features are supported through transpilation.
  • Types are enforced and will error at compilation time.

Limitations

  • TypeScript not intended to document code.   
interface Props {
  size?: number | string;
}

What is the size prop really looking for?

TS + JSDoc = 💪

/**
 * Adds two numbers together
 * @deprecated since version 2.0
 */
function add(a: number, b: number): number {
  return a + b;
}
/** 
 * JSDoc 
 */

Storybook 7 Autodocs

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?: Function;
}
/**
 * Primary UI component for user interaction
 */
export function Button({
  primary = false,
  backgroundColor,
  size = "medium",
  label
}: ButtonProps) {
  /* implementation details here */
}

Storybook 7 Autodocs

// Button.stories.tsx
import type { Meta } from "@storybook/react";

import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  component: Button,
  tags: ["autodocs"],
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    primary: true,
    label: "Button",
  },
};

export const Secondary: Story = {
  args: {
    ...Primary.args,
    primary: false,
  },
};

Storybook 7 Autodocs

Storybook 7 Autodocs

type ColorOption = "brand" | "accent" | "alert" | "none";

export interface ButtonProps {
  /**
   * Is this the principal call to action on the page?
   */
  primary: boolean;
  /**
   * What background color option to use
   */
  backgroundColor?: ColorOption;
  /**
   * How large should the button be?
   */
  size?: "small" | "medium" | "large";
  /**
   * Button contents
   */
  label: string;
  /**
   * Optional click handler
   */
  onClick?: Function;
}
/**
 * Primary UI component for user interaction
 */
export function Button({
  primary = false,
  backgroundColor = 'none',
  size = "medium",
  label
}: ButtonProps) {
  /* implementation details here */
}

Storybook 7 Autodocs

Can be enable globally

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/your-framework';

const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: ['@storybook/addon-essentials'],
  docs: {
    autodocs: 'tag',
    defaultName: 'Documentation',
  },
};

export default config;

Change the default template

{/* DocumentationTemplate.mdx */}

import { Meta, Title, Subtitle, Description, Primary, Controls, Stories } from '@storybook/blocks';

<Meta isTemplate />

<Title />

# Default implementation

<Primary />

## Inputs

The component accepts the following inputs (props):

<Controls />

---

## Additional variations

Listed below are additional variations of the component.

<Stories />

Change the default template

// .storybook/preview.jsx

import DocumentationTemplate from './DocumentationTemplate.mdx';

export default {
  parameters: {
    docs: {
      page: DocumentationTemplate,
    },
  },
};

Custom Doc Blocks

// .storybook/blocks/StoryName.jsx

import { useOf } from '@storybook/blocks';

export const StoryName = ({ of }) => {
  const resolvedOf = useOf(of || 'story', ['story', 'meta']);
  switch (resolvedOf.type) {
    case 'story': {
      return <h1>{resolvedOf.story.name}</h1>;
    }
    case 'meta': {
      return <h1>{resolvedOf.preparedMeta.title}</h1>;
    }
  }
  return null;
};
{/* ButtonDocs.mdx */}

import { Meta } from '@storybook/blocks';
import { StoryName } from '../.storybook/blocks/StoryName';
import * as ButtonStories from './Button.stories';

<Meta of={ButtonStories} />

{/* renders "Secondary" */}
<StoryName of={ButtonStories.Secondary} />

{/* renders "Primary" */}
<StoryName />

{/* renders "Button" */}
<StoryName of={ButtonStories} />
  • Reduced Manual Effort
  • Accurate and up-to-date documentation
  • Encourages documentation to be updated as part of the code review process
  • User has access to the best documentation where they are at, not just in Storybook

Benefits of using JSDoc, TS, and StoryBook

Made with Slides.com