or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

decorators.mdhooks.mdindex.mdpreview-system.mdstory-composition.mdstory-store.mdtesting-simulation.md

decorators.mddocs/

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

```