Storybook 搭配 Bootstrap 的主题切换:@storybook/addon-themes 接入与 withThemeByDataAttribute 源码解析
Storybook 搭配 Bootstrap 的主题切换storybook/addon-themes 接入与 withThemeByDataAttribute 源码解析【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook本文以 Storybook 仓库中 Bootstrap 主题接入指南 为核心完整讲解如何将storybook/addon-themes插件接入 Storybook 项目让基于 Bootstrap 构建的 Story 支持 light/dark 颜色模式一键切换。读完本文你将掌握该插件的安装注册流程、withThemeByDataAttribute装饰器的完整参数配置以及插件通过 channel 事件在 Preview 与 Manager 之间同步主题状态的底层机制。为什么需要 addon-themesstorybook/addon-themes是 Storybook 官方的主题切换插件用于在 Preview 中切换组件的多个主题。它的典型价值在于设计系统往往同时存在浅色、深色甚至品牌定制模式如果每个 Story 都要手工改代码才能预览另一种模式评审效率会很低。该插件在 Manager 工具栏注入一个带画笔图标的切换器点击即可切换当前 Story 的主题并支持在单个 Story 上锁定覆盖。从 插件的 package.json 可以看到几个关键事实插件名称为storybook/addon-themesdisplayName 为Themes按 README 说明要求Storybook 7.0 或更高版本unsupportedFrameworks配置为react-native即 React Native 框架下不受支持。它提供了三种装饰器策略withThemeByDataAttribute、withThemeByClassName、withThemeByJSXProvider见 decorators 目录本文聚焦 Bootstrap 官方推荐的数据属性data attribute策略。第一步安装插件在项目中以 dev dependency 安装插件支持三种包管理器# yarn yarn add -D storybook/addon-themes# npm npm install -D storybook/addon-themes# pnpm pnpm add -D storybook/addon-themes同时别忘了安装 Bootstrap 本体bootstrap包——它是 Story 样式的来源不属于插件的依赖。第二步在 main 配置中注册插件在.storybook/main.js的addons数组中加入storybook/addon-themesexport default { stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|ts|tsx)], addons: [ storybook/addon-essentials, storybook/addon-themes, ], };注册后插件会同时加载两部分运行时代码Manager 侧的工具栏组件src/theme-switcher.tsx与 Preview 侧的初始 globalssrc/preview.ts。从 Preview 侧源码看插件默认初始化了一个空的themeglobal// code/addons/themes/src/preview.ts export const initialGlobals: ProjectAnnotationsRenderer[initialGlobals] { [KEY]: , // KEY 即 theme };这保证了即使用户没有显式设置globals.theme装饰器读取全局状态时也不会遇到undefined。第三步在 preview 中引入 Bootstrap 的样式与脚本要让 Story 能用到 Bootstrap 的样式和交互脚本如模态框、下拉菜单需要在.storybook/preview.js中导入import { Preview } from storybook/your-renderer; import bootstrap/dist/css/bootstrap.min.css; import bootstrap/dist/js/bootstrap.bundle; const preview: Preview { parameters: { /* ... */ }, }; export default preview;bootstrap.bundle已包含 Popper.js因此无需额外引入。这一步与插件本身无关是 Bootstrap 项目本身的常规做法但放在 preview 中导入能确保所有 Story 共享同一份全局样式。第四步用 withThemeByDataAttribute 声明主题策略Bootstrap 原生支持 light 与 dark 两种颜色模式也允许自定义模式切换机制是在某个父元素上设置data-bs-theme属性。基于这个机制只需在 preview 中挂载withThemeByDataAttribute装饰器即可让工具栏的切换动作落到data-bs-theme上-import { Preview } from storybook/your-renderer; import { Preview, Renderer } from storybook/your-renderer; import { withThemeByDataAttribute } from storybook/addon-themes; import bootstrap/dist/css/bootstrap.min.css; import bootstrap/dist/js/bootstrap.bundle; const preview: Preview { parameters: { /* ... */ }, decorators: [ withThemeByDataAttributeRenderer({ themes: { light: light, dark: dark, }, defaultTheme: light, attributeName: data-bs-theme, }), ] }; export default preview;参数详解对照装饰器源码>const themeKey themeOverride || selected || defaultTheme;优先级为story 级parameters.themes.themeOverride 全局globals.theme工具栏选择结果defaultTheme。写入 DOM 属性。在useEffect中通过document.querySelector(parentSelector)找到父元素并执行setAttribute(attributeName, themes[themeKey])依赖数组为[themeOverride, selected]——即仅当覆盖值或全局主题变化时才触发 DOM 更新避免不必要的重渲染。对 Bootstrap 场景效果就是工具栏点击“dark”后html上出现data-bs-themedarkBootstrap 5.3 的颜色模式机制接管其余工作所有 Story 组件随之换肤。工具栏切换器的呈现逻辑了解 theme-switcher.tsx 可以实现工具栏组件会根据注册的主题数量自适应形态恰好 2 个主题如 Bootstrap 的 light/dark渲染一个带画笔图标的切换按钮点击直接切到另一个主题多于 2 个主题渲染一个Select下拉框列出全部主题0 或 1 个主题不渲染任何控件hasMultipleThemes不成立时返回null。此外还有两个细节值得注意锁定态isLocked当当前 Story 在 meta 或 story 级设置了globals: { theme: ... }或设置了themeOverride参数时工具栏控件被禁用并显示 “Story override” 标签源码提示该 Story 的主题已被故事本身接管整体禁用在parameters.themes中设置disable: true可隐藏工具栏控件并关闭插件行为参数类型定义见 types.ts。在单个 Story 上覆盖主题虽然工具栏负责全局切换但有时某个组件只适合固定主题预览例如深色模式下的告警条。可以在 meta 或 story 级用globals.theme锁定export default { title: Example/Button, component: Button, globals: { theme: dark }, // meta 级覆盖 }; export const Primary { args: { primary: true, label: Button }, }; export const PrimaryDark { args: { primary: true, label: Button }, globals: { theme: dark }, // story 级覆盖 };这正对应装饰器取值优先级中的selected来源——pluckThemeFromContexthelpers.ts从context.globals中读取theme键。此外 types.ts 还定义了 story 级parameters.themes.themeOverride它的优先级高于globals.theme适合在参数中做更细粒度的覆盖。完整配置速查将上面各步合并后一个最小可用的 Bootstrap 主题切换配置如下// .storybook/main.js export default { stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|ts|tsx)], addons: [storybook/addon-essentials, storybook/addon-themes], };// .storybook/preview.js import { Preview, Renderer } from storybook/your-renderer; import { withThemeByDataAttribute } from storybook/addon-themes; import bootstrap/dist/css/bootstrap.min.css; import bootstrap/dist/js/bootstrap.bundle; const preview { parameters: { /* ... */ }, decorators: [ withThemeByDataAttributeRenderer({ themes: { light: light, dark: dark }, defaultTheme: light, attributeName: data-bs-theme, }), ], }; export default preview;最后需要提醒三个适用前提与限制一是插件要求 Storybook 7.0 且不支持 React Native 框架二是withThemeByDataAttribute依赖浏览器 DOM内部使用document.querySelector与 React 的useEffect因此仅适用于基于 DOM 的渲染环境三是它只对parentSelector选中的元素生效若你的组件渲染在独立 Shadow DOM 或 iframe 内属性不会自动穿透需要自定义策略可参考 插件 API 文档 中“编写自定义装饰器”的思路。如果你想换用其他主题方案同一目录下还有 emotion、styled-components、material-ui、tailwind 等针对各自工具链的接入指南其核心装饰器用法与本文一致只是落地的属性或 Provider 不同。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
