CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-storybook--preview-api

Storybook's core preview API providing hooks, decorators, story composition utilities, and simulation tools for building UI components in isolation

Pending
Overview
Eval results
Files

decorators.mddocs/

Decorators

System for creating reusable story enhancements and middleware. Decorators wrap stories to provide additional functionality like theming, layouts, data providers, or interactive controls.

Capabilities

Decorator Creation

Primary function for creating Storybook decorators with advanced configuration options.

/**
 * Creates a Storybook decorator with configurable behavior
 * @param options - Configuration for the decorator
 * @returns Decorator function and configuration utilities
 */
function makeDecorator(options: MakeDecoratorOptions): MakeDecoratorResult;

interface MakeDecoratorOptions {
  /** Name of the decorator for identification */
  name: string;
  /** Parameter key for configuration in story parameters */
  parameterName: string;
  /** Skip execution if no parameters or options are provided */
  skipIfNoParametersOrOptions?: boolean;
  /** Core wrapper function that enhances the story */
  wrapper: (
    storyFn: StoryFn,
    context: StoryContext,
    settings: { parameters?: any; options?: any }
  ) => any;
}

interface MakeDecoratorResult {
  /** The decorator function to be used in stories */
  decorator: DecoratorFunction;
  /** Configure the decorator with specific options */
  configure: (options: any) => DecoratorFunction;
}

interface StoryFn<TRenderer = any> {
  (context: StoryContext<TRenderer>): any;
}

interface DecoratorFunction<TRenderer = any> {
  (story: StoryFn<TRenderer>, context: StoryContext<TRenderer>): any;
  decoratorName?: string;
}

Usage Examples:

import { makeDecorator } from "@storybook/preview-api";

// Create a theme decorator
export const withTheme = makeDecorator({
  name: 'withTheme',
  parameterName: 'theme',
  wrapper: (storyFn, context, { parameters }) => {
    const theme = parameters?.theme || 'light';
    
    return (
      <div className={`theme-${theme}`} data-theme={theme}>
        {storyFn(context)}
      </div>
    );
  }
});

// Create a data provider decorator
export const withData = makeDecorator({
  name: 'withData',
  parameterName: 'mockData',
  skipIfNoParametersOrOptions: true,
  wrapper: (storyFn, context, { parameters }) => {
    const mockData = parameters?.mockData || {};
    
    return (
      <DataProvider data={mockData}>
        {storyFn(context)}
      </DataProvider>
    );
  }
});

// Configure decorator with specific options
const withCustomTheme = withTheme.configure({
  themes: ['light', 'dark', 'high-contrast']
});

Advanced Decorator Patterns

Complex decorator implementations using hooks and context management.

// Decorator with state management
export const withCounter = makeDecorator({
  name: 'withCounter',
  parameterName: 'counter',
  wrapper: (storyFn, context, { parameters }) => {
    const [count, setCount] = useState(parameters?.initialCount || 0);
    
    const contextWithCounter = {
      ...context,
      args: {
        ...context.args,
        count,
        increment: () => setCount(c => c + 1),
        decrement: () => setCount(c => c - 1),
        reset: () => setCount(parameters?.initialCount || 0)
      }
    };
    
    return (
      <div>
        <div>Count: {count}</div>
        <button onClick={() => setCount(c => c + 1)}>+</button>
        <button onClick={() => setCount(c => c - 1)}>-</button>
        {storyFn(contextWithCounter)}
      </div>
    );
  }
});

// Decorator with async data loading
export const withAsyncData = makeDecorator({
  name: 'withAsyncData',
  parameterName: 'asyncData',
  wrapper: (storyFn, context, { parameters }) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
      const loadData = async () => {
        setLoading(true);
        try {
          const result = await parameters?.dataLoader?.();
          setData(result);
        } catch (error) {
          console.error('Failed to load data:', error);
        } finally {
          setLoading(false);
        }
      };
      
      if (parameters?.dataLoader) {
        loadData();
      } else {
        setLoading(false);
      }
    }, [parameters?.dataLoader]);
    
    if (loading) {
      return <div>Loading...</div>;
    }
    
    const contextWithData = {
      ...context,
      args: { ...context.args, data }
    };
    
    return storyFn(contextWithData);
  }
});

Decorator Usage in Stories

How to apply decorators to individual stories and components.

import type { Meta, StoryObj } from '@storybook/react';
import { withTheme, withData } from './decorators';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Example/Button',
  component: Button,
  decorators: [withTheme, withData],
  parameters: {
    theme: 'dark',
    mockData: {
      user: { name: 'John', role: 'admin' },
      permissions: ['read', 'write']
    }
  }
};

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

export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
  },
  // Override decorator parameters for this story
  parameters: {
    theme: 'light'
  }
};

export const WithCustomData: Story = {
  args: {
    label: 'Custom Button',
  },
  decorators: [
    (Story, context) => {
      // Inline decorator for specific story
      return (
        <div style={{ padding: '20px', border: '2px solid red' }}>
          <Story />
        </div>
      );
    }
  ]
};

Legacy Addon API (Deprecated)

⚠️ DEPRECATED: This API is deprecated and maintained only for backward compatibility. Use the modern hooks and decorator system instead.

/**
 * @deprecated Use modern addon system instead
 * Legacy addon store instance
 */
const addons: {
  getChannel(): Channel;
  addPanel(name: string, panel: any): void;
  addDecorator(decorator: DecoratorFunction): void;
};

/**
 * Creates mock communication channel for testing
 * @deprecated Use mockChannel from testing-simulation.md instead
 * @returns Mock channel implementation
 */
function mockChannel(): Channel;

interface Channel {
  emit(eventId: string, ...args: any[]): void;
  on(eventId: string, listener: Function): void;
  off(eventId: string, listener: Function): void;
  once(eventId: string, listener: Function): void;
  addListener(eventId: string, listener: Function): void;
  removeListener(eventId: string, listener: Function): void;
  removeAllListeners(eventId?: string): void;
}

Types & Interfaces

interface StoryContext<TRenderer = any> {
  id: string;
  name: string;
  title: string;
  parameters: Parameters;
  args: Args;
  argTypes: ArgTypes;
  globals: Args;
  hooks: HooksContext<TRenderer>;
  viewMode: 'story' | 'docs';
  loaded: Record<string, any>;
}

interface Parameters {
  [key: string]: any;
}

interface Args {
  [key: string]: any;
}

interface ArgTypes {
  [key: string]: ArgType;
}

Install with Tessl CLI

npx tessl i tessl/npm-storybook--preview-api

docs

decorators.md

hooks.md

index.md

preview-system.md

story-composition.md

story-store.md

testing-simulation.md

tile.json