0
# Decorators
1
2
System for creating reusable story enhancements and middleware. Decorators wrap stories to provide additional functionality like theming, layouts, data providers, or interactive controls.
3
4
## Capabilities
5
6
### Decorator Creation
7
8
Primary function for creating Storybook decorators with advanced configuration options.
9
10
```typescript { .api }
11
/**
12
* Creates a Storybook decorator with configurable behavior
13
* @param options - Configuration for the decorator
14
* @returns Decorator function and configuration utilities
15
*/
16
function makeDecorator(options: MakeDecoratorOptions): MakeDecoratorResult;
17
18
interface MakeDecoratorOptions {
19
/** Name of the decorator for identification */
20
name: string;
21
/** Parameter key for configuration in story parameters */
22
parameterName: string;
23
/** Skip execution if no parameters or options are provided */
24
skipIfNoParametersOrOptions?: boolean;
25
/** Core wrapper function that enhances the story */
26
wrapper: (
27
storyFn: StoryFn,
28
context: StoryContext,
29
settings: { parameters?: any; options?: any }
30
) => any;
31
}
32
33
interface MakeDecoratorResult {
34
/** The decorator function to be used in stories */
35
decorator: DecoratorFunction;
36
/** Configure the decorator with specific options */
37
configure: (options: any) => DecoratorFunction;
38
}
39
40
interface StoryFn<TRenderer = any> {
41
(context: StoryContext<TRenderer>): any;
42
}
43
44
interface DecoratorFunction<TRenderer = any> {
45
(story: StoryFn<TRenderer>, context: StoryContext<TRenderer>): any;
46
decoratorName?: string;
47
}
48
```
49
50
**Usage Examples:**
51
52
```typescript
53
import { makeDecorator } from "@storybook/preview-api";
54
55
// Create a theme decorator
56
export const withTheme = makeDecorator({
57
name: 'withTheme',
58
parameterName: 'theme',
59
wrapper: (storyFn, context, { parameters }) => {
60
const theme = parameters?.theme || 'light';
61
62
return (
63
<div className={`theme-${theme}`} data-theme={theme}>
64
{storyFn(context)}
65
</div>
66
);
67
}
68
});
69
70
// Create a data provider decorator
71
export const withData = makeDecorator({
72
name: 'withData',
73
parameterName: 'mockData',
74
skipIfNoParametersOrOptions: true,
75
wrapper: (storyFn, context, { parameters }) => {
76
const mockData = parameters?.mockData || {};
77
78
return (
79
<DataProvider data={mockData}>
80
{storyFn(context)}
81
</DataProvider>
82
);
83
}
84
});
85
86
// Configure decorator with specific options
87
const withCustomTheme = withTheme.configure({
88
themes: ['light', 'dark', 'high-contrast']
89
});
90
```
91
92
### Advanced Decorator Patterns
93
94
Complex decorator implementations using hooks and context management.
95
96
```typescript
97
// Decorator with state management
98
export const withCounter = makeDecorator({
99
name: 'withCounter',
100
parameterName: 'counter',
101
wrapper: (storyFn, context, { parameters }) => {
102
const [count, setCount] = useState(parameters?.initialCount || 0);
103
104
const contextWithCounter = {
105
...context,
106
args: {
107
...context.args,
108
count,
109
increment: () => setCount(c => c + 1),
110
decrement: () => setCount(c => c - 1),
111
reset: () => setCount(parameters?.initialCount || 0)
112
}
113
};
114
115
return (
116
<div>
117
<div>Count: {count}</div>
118
<button onClick={() => setCount(c => c + 1)}>+</button>
119
<button onClick={() => setCount(c => c - 1)}>-</button>
120
{storyFn(contextWithCounter)}
121
</div>
122
);
123
}
124
});
125
126
// Decorator with async data loading
127
export const withAsyncData = makeDecorator({
128
name: 'withAsyncData',
129
parameterName: 'asyncData',
130
wrapper: (storyFn, context, { parameters }) => {
131
const [data, setData] = useState(null);
132
const [loading, setLoading] = useState(true);
133
134
useEffect(() => {
135
const loadData = async () => {
136
setLoading(true);
137
try {
138
const result = await parameters?.dataLoader?.();
139
setData(result);
140
} catch (error) {
141
console.error('Failed to load data:', error);
142
} finally {
143
setLoading(false);
144
}
145
};
146
147
if (parameters?.dataLoader) {
148
loadData();
149
} else {
150
setLoading(false);
151
}
152
}, [parameters?.dataLoader]);
153
154
if (loading) {
155
return <div>Loading...</div>;
156
}
157
158
const contextWithData = {
159
...context,
160
args: { ...context.args, data }
161
};
162
163
return storyFn(contextWithData);
164
}
165
});
166
```
167
168
### Decorator Usage in Stories
169
170
How to apply decorators to individual stories and components.
171
172
```typescript
173
import type { Meta, StoryObj } from '@storybook/react';
174
import { withTheme, withData } from './decorators';
175
import { Button } from './Button';
176
177
const meta: Meta<typeof Button> = {
178
title: 'Example/Button',
179
component: Button,
180
decorators: [withTheme, withData],
181
parameters: {
182
theme: 'dark',
183
mockData: {
184
user: { name: 'John', role: 'admin' },
185
permissions: ['read', 'write']
186
}
187
}
188
};
189
190
export default meta;
191
type Story = StoryObj<typeof meta>;
192
193
export const Primary: Story = {
194
args: {
195
primary: true,
196
label: 'Button',
197
},
198
// Override decorator parameters for this story
199
parameters: {
200
theme: 'light'
201
}
202
};
203
204
export const WithCustomData: Story = {
205
args: {
206
label: 'Custom Button',
207
},
208
decorators: [
209
(Story, context) => {
210
// Inline decorator for specific story
211
return (
212
<div style={{ padding: '20px', border: '2px solid red' }}>
213
<Story />
214
</div>
215
);
216
}
217
]
218
};
219
```
220
221
## Legacy Addon API (Deprecated)
222
223
**⚠️ DEPRECATED:** This API is deprecated and maintained only for backward compatibility. Use the modern hooks and decorator system instead.
224
225
```typescript { .api }
226
/**
227
* @deprecated Use modern addon system instead
228
* Legacy addon store instance
229
*/
230
const addons: {
231
getChannel(): Channel;
232
addPanel(name: string, panel: any): void;
233
addDecorator(decorator: DecoratorFunction): void;
234
};
235
236
/**
237
* Creates mock communication channel for testing
238
* @deprecated Use mockChannel from testing-simulation.md instead
239
* @returns Mock channel implementation
240
*/
241
function mockChannel(): Channel;
242
243
interface Channel {
244
emit(eventId: string, ...args: any[]): void;
245
on(eventId: string, listener: Function): void;
246
off(eventId: string, listener: Function): void;
247
once(eventId: string, listener: Function): void;
248
addListener(eventId: string, listener: Function): void;
249
removeListener(eventId: string, listener: Function): void;
250
removeAllListeners(eventId?: string): void;
251
}
252
```
253
254
## Types & Interfaces
255
256
```typescript { .api }
257
interface StoryContext<TRenderer = any> {
258
id: string;
259
name: string;
260
title: string;
261
parameters: Parameters;
262
args: Args;
263
argTypes: ArgTypes;
264
globals: Args;
265
hooks: HooksContext<TRenderer>;
266
viewMode: 'story' | 'docs';
267
loaded: Record<string, any>;
268
}
269
270
interface Parameters {
271
[key: string]: any;
272
}
273
274
interface Args {
275
[key: string]: any;
276
}
277
278
interface ArgTypes {
279
[key: string]: ArgType;
280
}
281
```