0
# Event System
1
2
Event-based theme control and integration system for manual theme management and custom component integration using Storybook's addon channel.
3
4
## Capabilities
5
6
### Event Constants
7
8
Event names for listening to and triggering theme changes.
9
10
```typescript { .api }
11
/** Event emitted when theme changes, payload: boolean (true = dark, false = light) */
12
const DARK_MODE_EVENT_NAME: 'DARK_MODE';
13
14
/** Event that can be emitted to trigger theme change */
15
const UPDATE_DARK_MODE_EVENT_NAME: 'UPDATE_DARK_MODE';
16
```
17
18
### Event Listener Integration
19
20
Listen for theme changes using Storybook's addon channel for custom theme integration.
21
22
**Usage Examples:**
23
24
```typescript
25
import React, { useState, useEffect } from 'react';
26
import addons from '@storybook/addons';
27
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
28
29
// Hook-based event listener
30
function useThemeEventListener() {
31
const [isDark, setIsDark] = useState(false);
32
33
useEffect(() => {
34
const channel = addons.getChannel();
35
channel.on(DARK_MODE_EVENT_NAME, setIsDark);
36
return () => channel.off(DARK_MODE_EVENT_NAME, setIsDark);
37
}, []);
38
39
return isDark;
40
}
41
42
// Component using event listener
43
function EventBasedThemeWrapper({ children }) {
44
const [isDark, setDark] = useState(false);
45
46
useEffect(() => {
47
const channel = addons.getChannel();
48
49
// Listen to DARK_MODE event
50
channel.on(DARK_MODE_EVENT_NAME, setDark);
51
return () => channel.off(DARK_MODE_EVENT_NAME, setDark);
52
}, []);
53
54
// Apply theme to context provider
55
return (
56
<ThemeContext.Provider value={isDark ? darkTheme : lightTheme}>
57
{children}
58
</ThemeContext.Provider>
59
);
60
}
61
```
62
63
### Manual Theme Control
64
65
Programmatically trigger theme changes by emitting events to the addon channel.
66
67
```typescript { .api }
68
/**
69
* Trigger theme change programmatically
70
* @param mode - Optional specific mode, or undefined to toggle
71
*/
72
function triggerThemeChange(mode?: 'light' | 'dark'): void;
73
```
74
75
**Usage Examples:**
76
77
```typescript
78
import addons from '@storybook/addons';
79
import { UPDATE_DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
80
81
// Get the addon channel
82
const channel = addons.getChannel();
83
84
// Toggle theme (switch to opposite of current)
85
channel.emit(UPDATE_DARK_MODE_EVENT_NAME);
86
87
// Set specific theme
88
channel.emit(UPDATE_DARK_MODE_EVENT_NAME, 'dark');
89
channel.emit(UPDATE_DARK_MODE_EVENT_NAME, 'light');
90
91
// Example: Custom theme toggle button
92
function CustomThemeToggle() {
93
const handleToggle = () => {
94
const channel = addons.getChannel();
95
channel.emit(UPDATE_DARK_MODE_EVENT_NAME);
96
};
97
98
return (
99
<button onClick={handleToggle}>
100
Toggle Theme
101
</button>
102
);
103
}
104
```
105
106
### Docs Mode Integration
107
108
Special integration for Storybook docs mode where the toolbar is not visible, allowing custom theme controls.
109
110
```typescript
111
import React from 'react';
112
import addons from '@storybook/addons';
113
import { DocsContainer } from '@storybook/addon-docs';
114
import { themes } from '@storybook/theming';
115
import {
116
DARK_MODE_EVENT_NAME,
117
UPDATE_DARK_MODE_EVENT_NAME
118
} from 'storybook-dark-mode';
119
120
const channel = addons.getChannel();
121
122
// Custom docs container with theme control
123
function CustomDocsContainer({ children, ...props }) {
124
const [isDark, setIsDark] = React.useState(false);
125
126
React.useEffect(() => {
127
// Listen for theme changes
128
channel.on(DARK_MODE_EVENT_NAME, setIsDark);
129
return () => channel.off(DARK_MODE_EVENT_NAME, setIsDark);
130
}, []);
131
132
const toggleTheme = () => {
133
channel.emit(UPDATE_DARK_MODE_EVENT_NAME);
134
};
135
136
return (
137
<DocsContainer
138
{...props}
139
theme={isDark ? themes.dark : themes.light}
140
>
141
<button
142
onClick={toggleTheme}
143
style={{
144
position: 'fixed',
145
top: 10,
146
right: 10,
147
zIndex: 1000
148
}}
149
>
150
{isDark ? 'โ๏ธ' : '๐'}
151
</button>
152
{children}
153
</DocsContainer>
154
);
155
}
156
157
export const parameters = {
158
docs: {
159
container: CustomDocsContainer
160
}
161
};
162
```
163
164
### Event Payload Structure
165
166
Event payloads and their types for proper TypeScript integration.
167
168
```typescript { .api }
169
/** DARK_MODE_EVENT_NAME payload */
170
type DarkModeEventPayload = boolean; // true = dark mode, false = light mode
171
172
/** UPDATE_DARK_MODE_EVENT_NAME payload */
173
type UpdateDarkModeEventPayload = 'light' | 'dark' | undefined; // undefined = toggle
174
```
175
176
### Advanced Integration Patterns
177
178
#### Multi-Component Theme Synchronization
179
180
```typescript
181
import React, { createContext, useContext, useEffect, useState } from 'react';
182
import addons from '@storybook/addons';
183
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
184
185
// Create theme context
186
const ThemeEventContext = createContext(false);
187
188
// Provider component
189
function ThemeEventProvider({ children }) {
190
const [isDark, setIsDark] = useState(false);
191
192
useEffect(() => {
193
const channel = addons.getChannel();
194
channel.on(DARK_MODE_EVENT_NAME, setIsDark);
195
return () => channel.off(DARK_MODE_EVENT_NAME, setIsDark);
196
}, []);
197
198
return (
199
<ThemeEventContext.Provider value={isDark}>
200
{children}
201
</ThemeEventContext.Provider>
202
);
203
}
204
205
// Hook for consuming theme state
206
function useThemeEvent() {
207
return useContext(ThemeEventContext);
208
}
209
```
210
211
#### Conditional Story Rendering
212
213
```typescript
214
import React, { useEffect, useState } from 'react';
215
import addons from '@storybook/addons';
216
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
217
218
// Story that changes based on theme
219
export const ThemeAwareStory = () => {
220
const [isDark, setIsDark] = useState(false);
221
222
useEffect(() => {
223
const channel = addons.getChannel();
224
channel.on(DARK_MODE_EVENT_NAME, setIsDark);
225
return () => channel.off(DARK_MODE_EVENT_NAME, setIsDark);
226
}, []);
227
228
if (isDark) {
229
return <DarkModeComponent />;
230
}
231
232
return <LightModeComponent />;
233
};
234
```
235
236
### Error Handling
237
238
Event listeners should include proper error handling and cleanup:
239
240
```typescript
241
import addons from '@storybook/addons';
242
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
243
244
function safeEventListener() {
245
const [isDark, setIsDark] = useState(false);
246
247
useEffect(() => {
248
const channel = addons.getChannel();
249
250
const handleThemeChange = (newIsDark) => {
251
try {
252
setIsDark(newIsDark);
253
// Additional theme change logic
254
} catch (error) {
255
console.error('Theme change error:', error);
256
}
257
};
258
259
channel.on(DARK_MODE_EVENT_NAME, handleThemeChange);
260
261
return () => {
262
try {
263
channel.off(DARK_MODE_EVENT_NAME, handleThemeChange);
264
} catch (error) {
265
console.error('Cleanup error:', error);
266
}
267
};
268
}, []);
269
270
return isDark;
271
}
272
```