or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdevent-system.mdindex.mdinternal-apis.mdreact-integration.md

event-system.mddocs/

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

```