Scalar Storybook 主题与全局配置:基于 `@scalar/helpers/storybook` 的共享可视化基础设施
Scalar Storybook 主题与全局配置基于scalar/helpers/storybook的共享可视化基础设施【免费下载链接】scalarScalar is an open-source API platform: Modern REST API Client Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalarscalar/helpers/storybook是 Scalar 开源 API 平台中面向内部包的共享 Storybook 配置模块为scalar/components与scalar/api-reference两个包提供统一的主题切换与颜色模式控制能力。在引入该模块之前这两个包的.storybook目录各自维护着一份字节级相同的复制代码如今它们统一消费同一套themes与globals子模块。读完本文你将掌握如何把 Scalar 的全部主题预设接入 Storybook 工具栏、如何通过?globalsURL 参数驱动视觉测试以及这套机制在 CSS 级联层Cascade Layers与自定义属性Custom Properties层面的底层原理。模块定位内部工具而非公共 API该模块位于 packages/helpers/src/storybook/README.md其定位在文档开头就写得很清楚这个目录下的任何内容都不是给scalar/helpers的消费方使用的它是恰好寄居在同一包内的内部工具——正如scalar/helpers/playwright一样参见 packages/helpers 包结构。当前只有两个包实际运行 Storybook 并消费该模块scalar/components组件库scalar/api-referenceAPI 参考文档渲染器二者此前各自维护着字节级一致的复制代码如今统一由该模块提供。目录下仅有三个文件packages/helpers/src/storybook/ ├── README.md ├── globals.ts └── themes.ts其中themes.ts负责把 Scalar 主题预设表达为 Storybook 工具栏选项globals.ts负责把主题与颜色模式两个全局量应用到文档并暴露给预览Preview配置。主题预设themes子模块packages/helpers/src/storybook/themes.ts 从scalar/themes导入themeIds、themeLabels与getThemeStyles并向外导出三个核心对象/类型themeVariants、defaultThemeVariant与ThemeVariantId。themeVariants全部主题 两个圆角极端值themeVariants是“一个 story 可以在其下渲染的每一种主题”的映射以 id 为键。它由两部分拼合而成1. 预设变体presetVariants来自scalar/themes的themeIds。当前仓库中 packages/themes/src/index.ts 定义了完整的主题 id 列表export const themeIds [ alternate, default, moon, purple, solarized, bluePlanet, deepSpace, saturn, kepler, elysiajs, fastify, mars, laserwave, none, ] as const构造逻辑中有两个值得注意的取舍default变体的css为null不注入任何样式因为default本来就是preview.ts已加载的主题保持原样可以让 stories 的渲染结果与没有该装饰器时完全一致none预设被显式过滤掉因为getThemeStyles会把none解析回默认预设留着没有意义。2. 圆角变体radiusVariants两个额外注入的变体把--scalar-radius推到刻度两端以便对组件在两个极端外观下做快照const radiusVariants { rounded-none: { label: Rounded None (0), css: layer scalar-theme { :root { --scalar-radius: 0px; } }, }, rounded-full: { label: Rounded Full (9999px), css: layer scalar-theme { :root { --scalar-radius: 9999px; } }, }, } as const之所以命名为rounded-none/rounded-full是因为它们强制整个界面呈现 Tailwind 同名类的外观前者把所有圆角拍平为直角后者把所有圆角封顶为胶囊形。源码注释明确指出所有预设主题都达不到这两个极端——唯一碰过圆角的 Laserwave 主题也只是固定了自己更大的圆角值而不是从--scalar-radius派生见 packages/themes/src/presets/laserwave.css。关于圆角为什么要覆盖在:root上源码给出了精辟的解释整个圆角刻度都由--scalar-radius派生自定义属性custom property是在声明它的元素上完成var()代入的而派生的 token 也在:root上声明。因此如果把覆盖放在更深的元素上只会移动基准值而不会带动派生值所以必须覆盖在:root。applyThemeVariant(id)替换而非堆叠样式export const applyThemeVariant (id: ThemeVariantId) { document.getElementById(STYLE_ELEMENT_ID)?.remove() const css themeVariants[id]?.css if (!css) { return } const style document.createElement(style) style.id STYLE_ELEMENT_ID style.textContent css document.head.append(style) }该函数把变体的 CSS 注入到一个由它独占的 style 元素id 为scalar-storybook-theme中每次调用先移除旧元素再追加新元素从而做到“替换”而非“叠加”——反复切换主题不会在head里累积样式。注入的预设 CSS 被包裹在layer scalar-theme中而默认预设加载进的是scalar-base层因此主题样式依靠层顺序layer order胜出而不是选择器特异性。源码注释特别强调这正是 API reference 在运行时应用主题所用的同一机制对应getThemeStyles的layer参数默认值为scalar-theme见 packages/themes/src/index.ts。defaultThemeVariant 与 ThemeVariantIddefaultThemeVariant: ThemeVariantId default除非某个 story 或测试指定了其他变体否则 story 默认在此主题下渲染ThemeVariantId keyof typeof themeVariants所有合法变体 id 的联合类型13 个预设主题中除none外的 12 个 2 个圆角变体。在scalar/components的测试基建中可以看到它的实际用法packages/components/test/helpers.ts引入defaultThemeVariant作为 theme 的默认参数packages/components/test/gallery/main.ts在渲染画廊时调用applyThemeVariant(window.__scalarTheme ?? defaultThemeVariant)第 11、161 行packages/components/test/shared.ts还据此拼接快照文件名后缀当主题不是默认值时追加-{slug}第 133 行。全局控制globals子模块packages/helpers/src/storybook/globals.ts 为所有 Scalar Storybook 提供两个共享的工具栏控制项全局名工具栏标题说明themeTheme枚举themeVariants的全部选项画笔图标paintbrushdynamicTitle: truecolorModeColor modelight/dark两个选项太阳/月亮图标dynamicTitle: true颜色模式的默认值跟随操作系统颜色模式默认值取自操作系统偏好在预览加载时解析一次export const defaultColorMode: DarkLightMode getSystemColorMode()getSystemColorMode的实现位于 packages/helpers/src/theme/color-mode.ts细节很有参考价值无windowSSR 环境时返回light——没有偏好可读也没有任何东西在渲染有window但没有matchMedia时返回dark——这个看似不一致的行为是为兼容useColorMode的历史解析方式而保留的实践中只有 stub 过的测试环境会走到该分支正常情况用window.matchMedia((prefers-color-scheme: dark))判定。所以 Storybook 打开时会自动落在整台机器当前所处的模式上。这里用的是“解析一次”而非“持续监听”因为工具栏本身就是为了覆盖该默认值而存在的。快照测试永远不会看到这个默认值测试会为每张截图包括浅色截图自行设置模式 class因此基线不依赖运行测试的机器偏好。scalarGlobalTypes / scalarInitialGlobals纯对象字面量这两个导出被设计成普通的对象字面量而非 Storybook 的GlobalTypes类型目的在源码注释里写得很直白这样本包就不必依赖 Storybook——每个 preview 在把它赋给Preview.globalTypes/initialGlobals时才做类型检查。export const scalarGlobalTypes { theme: { description: Scalar theme, toolbar: { title: Theme, icon: paintbrush, items: /* themeVariants 全部条目 */, dynamicTitle: true }, }, colorMode: { description: Color mode, toolbar: { title: Color mode, icon: contrast, items: [light/dark], dynamicTitle: true }, }, } export const scalarInitialGlobals { theme: defaultThemeVariant, colorMode: defaultColorMode, }声明 globals 还有一个关键副作用Storybook 会丢弃任何未声明的 global。因此“声明”本身就让视觉测试可以通过 story URL 挑选主题与颜色模式?globalstheme:laserwave;colorMode:darkapplyScalarGlobals(globals)装饰器内联动应用export const applyScalarGlobals (globals: Recordstring, unknown): void { applyThemeVariant(globals.theme as ThemeVariantId) applyColorMode(globals.colorMode dark ? dark : light) }它同时应用两个全局量主题交给applyThemeVariant颜色模式交给applyColorMode。applyColorMode的实现color-mode.ts基于一个设计事实每个 Scalar 主题都以light-mode/dark-mode一对 class 而非媒体查询发布两种模式所以切换模式就是交换这两个 class且两个都被 toggle确保元素永远不会同时带上两个 mode class。调用方需要自行确认 DOM 已存在——因为默认参数会触及document在 SSR 下会抛错而不是静默无操作。文档推荐的用法是放进 decorator这样工具栏每次变化都会重新执行decorators: [ (story, context) { applyScalarGlobals(context.globals) return story() }, ]在 preview 中装配两个包的消费方式scalar/components与scalar/api-reference的.storybook/preview.ts几乎一模一样都以三行完成接入import { applyScalarGlobals, scalarGlobalTypes, scalarInitialGlobals } from scalar/helpers/storybook/globals const preview: Preview { globalTypes: scalarGlobalTypes, initialGlobals: scalarInitialGlobals, decorators: [ (story, context) { applyScalarGlobals(context.globals) return story() }, ], }可对照的完整实现见 packages/api-reference/.storybook/preview.ts 与 packages/components/.storybook/preview.ts。两个 preview 的差异主要体现在补充样式上api-reference的 preview 引入scalar/themes/fonts.css与自身的src/style.css其全局样式会拉入scalar/themes/style.css和 Tailwind 主题让 stories 与 reference 本身使用完全相同的 token 和 resetcomponents的 preview 额外引入scalar/themes/style.css并在 controls 中把--scalar-color-1、--scalar-background-1、--scalar-border-color等 16 个设计 token 注册为 preset colors方便在 controls 面板直接引用。两个 preview 都在document.body上添加了scalar-appclass以复用 Scalar 应用级的全局样式上下文。从 URL 参数到视觉测试的完整链路将上述机制串起来就是一条“从 URL 到快照基线”的完整链路测试在 story URL 上附加?globalstheme:laserwave;colorModedark;分隔、key:value形式Storybook 校验该 global 已在initialGlobals中声明否则直接丢弃渲染 story 时 decorator 读取context.globals调用applyScalarGlobalsapplyThemeVariant移除旧的scalar-storybook-themestyle 元素、按 id 查找themeVariants并注入新 CSS被layer scalar-theme包裹applyColorMode在 body 上切换light-mode/dark-modeclass快照测试为每张截图显式设置 mode class使基线不依赖运行机器。这套机制在scalar/components的测试与画廊基建中被实际消费packages/components/test/helpers.ts、packages/components/test/gallery/main.ts、packages/components/test/shared.ts均直接引用defaultThemeVariant/applyThemeVariant/ThemeVariantIdpackages/api-reference/test/helpers.ts也引入了同一条 storybook 路径印证了 README 中“两个包共用同一份配置”的陈述。环境依赖devDependency 约定模块引入前需要留意一个环境约定themes.ts导入了scalar/themes而它是本包的devDependency。因此任何引入scalar/helpers/storybook/*的包都必须自行提供该依赖——这与scalar/helpers/playwright/docker期望调用方提供playwright/test的约定完全同构参见 packages/helpers/src/playwright 相关实现。这也再次印证该模块是面向包内部工具链而非终端消费者的。推荐的最小导入方式import { applyScalarGlobals, scalarGlobalTypes, scalarInitialGlobals } from scalar/helpers/storybook/globals import { type ThemeVariantId, defaultThemeVariant } from scalar/helpers/storybook/themes小结scalar/helpers/storybook用约 150 行代码解决了 Scalar 多个包之间 Storybook 配置重复的问题并在细节上体现了几处值得借鉴的设计决策用“独占 style 元素 移除再追加”避免样式堆叠、用级联层而非特异性控制主题优先级、用纯对象字面量保持包对 Storybook 的零依赖、用“解析一次的系统偏好”作为颜色模式默认值以及通过声明 globals 顺带获得 URL 驱动的视觉测试能力。对于任何需要让 Storybook 与运行时主题保持一致的多包前端仓库这套模式都提供了可直接复制的参考实现。【免费下载链接】scalarScalar is an open-source API platform: Modern REST API Client Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

WinBoat 中文显示成方框?字体渲染与缩放优化的完整配置指南

WinBoat 中文显示成方框?字体渲染与缩放优化的完整配置指南

WinBoat 中文显示成方框?字体渲染与缩放优化的完整配置指南 【免费下载链接】winboat Run Windows apps on 🐧 Linux with ✨ seamless integration 项目地址: https://gitcode.com/GitHub_Trending/wi/winboat 在 Linux 上用 WinBoat 跑 Windows…

2026/10/20 2:52:55 阅读更多 →
STM32F103用HAL库读写AT24C02:I2C地址与页边界实战

STM32F103用HAL库读写AT24C02:I2C地址与页边界实战

简介:面向嵌入式初学者的STM32F103单片机HAL库例程,聚焦I2C接口读写AT24C02外部EEPROM存储芯片,适用于物联网项目开发中的参数保存、掉电存储等场景。工程基于KEIL MDK编写,当前在STM32F103上运行,若更换同系列其他型号…

2026/11/5 18:27:36 阅读更多 →
Apache DolphinScheduler GRPC 任务节点实战:参数配置、状态码校验与底层调用原理

Apache DolphinScheduler GRPC 任务节点实战:参数配置、状态码校验与底层调用原理

Apache DolphinScheduler GRPC 任务节点实战:参数配置、状态码校验与底层调用原理 【免费下载链接】dolphinscheduler Apache DolphinScheduler is the modern data orchestration platform. Agile to create high performance workflow with low-code 项目地址: …

2026/10/21 16:07:17 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/13 5:13:25 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/13 5:13:25 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/13 5:13:25 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/12 18:22:26 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/12 18:22:25 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/13 5:13:25 阅读更多 →

日新闻

周新闻

月新闻