UNPKG

storybook-addon-data-theme-switcher

Version:

A Storybook addon to switch the data-theme attribute on the html element

138 lines (137 loc) 5.26 kB
import { addons, types, useGlobals } from "storybook/manager-api"; import React, { useCallback, useMemo, useState } from "react"; import { ActionList, PopoverProvider, ToggleButton } from "storybook/internal/components"; import { styled } from "storybook/theming"; import * as Icon from "@storybook/icons"; //#region src/constants.ts const ADDON_ID = "storybook/addon-data-theme-switcher"; const TOOL_ID = `${ADDON_ID}/tool`; const TAB_ID = `${ADDON_ID}/tab`; const DATA_THEME_KEY = "dataTheme"; //#endregion //#region src/utils/index.ts const defaultOptions = { clearable: true, list: [] }; function getConfig(parameters) { const options = Array.isArray(parameters) ? { list: parameters } : parameters; return { ...defaultOptions, ...options }; } function getSelectedThemeName(list, defaultTheme, currentSelectedValue) { if (!list.length) return "none"; if (currentSelectedValue === "none") return currentSelectedValue; if (currentSelectedValue && list.some((i) => i.name === currentSelectedValue)) return currentSelectedValue; if (defaultTheme && list.some((i) => i.dataTheme === defaultTheme)) return list.find((i) => i.dataTheme === defaultTheme)?.name ?? "none"; if (defaultTheme) return defaultTheme; if (list.some((i) => i.default)) return list.find((i) => i.default)?.name ?? "none"; return "none"; } function getSelectedTheme(list, themeName) { return list.find(({ name }) => name === themeName); } //#endregion //#region src/components/ColorIcon.tsx const ColorIcon = styled.span(({ background }) => ({ borderRadius: "50%", display: "block", height: 14, width: 14, flexShrink: 0, background }), ({ theme }) => ({ boxShadow: `${theme.appBorderColor} 0 0 0 1px inset` })); //#endregion //#region src/Tool.tsx const MenuButton = styled(ActionList.Button)(({ theme }) => ({ width: "100%", fontWeight: theme.typography.weight.regular, color: theme.color.defaultText, justifyContent: "flex-start" })); const MenuText = styled(ActionList.Text)({ flexGrow: 0 }); const ThemeMenu = ({ items, onHide }) => /* @__PURE__ */ React.createElement(ActionList, { style: { minWidth: 180 } }, items.map((item) => /* @__PURE__ */ React.createElement(ActionList.Item, { key: item.id, active: item.active }, /* @__PURE__ */ React.createElement(MenuButton, { ariaLabel: false, onClick: () => { item.onClick(); onHide(); } }, item.left && /* @__PURE__ */ React.createElement(ActionList.Icon, null, item.left), /* @__PURE__ */ React.createElement(MenuText, null, item.title))))); const DataThemeSelector = () => { const [isOpen, setIsOpen] = useState(false); const [{ dataTheme, dataThemes: themes }, updateThemeGlobals] = useGlobals(); const themeConfig = useMemo(() => ({ default: dataTheme, ...themes }), [dataTheme, themes]); const change = useCallback((newSelected) => { const { list, onChange } = getConfig(themeConfig); const selectedTheme = getSelectedTheme(list, newSelected); updateThemeGlobals({ [DATA_THEME_KEY]: selectedTheme?.dataTheme ?? "none" }); if (typeof onChange === "function") onChange(selectedTheme); }, [themeConfig, updateThemeGlobals]); const { items, selectedTheme } = useMemo(() => { const { clearable, list, default: defaultTheme } = getConfig(themeConfig); const selectedThemeName = getSelectedThemeName(list, defaultTheme); const selectorItems = []; if (selectedThemeName !== "none" && clearable) selectorItems.push({ id: "none", title: "Clear data-theme", onClick: () => change("none"), value: "none", left: /* @__PURE__ */ React.createElement(Icon.UndoIcon, null), active: false }); if (list.length) for (const { dataTheme, color, name } of list) { const id = name ?? dataTheme; selectorItems.push({ id, title: id, onClick: () => change(id), value: id, left: /* @__PURE__ */ React.createElement(ColorIcon, { background: color ?? "transparent" }), active: name === selectedThemeName }); } return { items: selectorItems, selectedTheme: getSelectedTheme(list, selectedThemeName) }; }, [themeConfig, change]); const iconKey = themeConfig.toolbar?.icon ?? "PaintBrushIcon"; const ThemeConfigIcon = useMemo(() => Icon[iconKey], [iconKey]); const renderPopover = useCallback((props) => /* @__PURE__ */ React.createElement(ThemeMenu, { items, onHide: props.onHide }), [items]); return themeConfig.list && themeConfig.list.length > 0 ? /* @__PURE__ */ React.createElement(PopoverProvider, { ariaLabel: "Theme selector", placement: "top-start", padding: 0, onVisibleChange: setIsOpen, popover: renderPopover }, /* @__PURE__ */ React.createElement(ToggleButton, { variant: "ghost", pressed: selectedTheme !== void 0, key: TOOL_ID, ariaLabel: "Change data-theme attribute", tooltip: themeConfig.toolbar?.title ?? "Change data-theme attribute", disableAllTooltips: isOpen }, /* @__PURE__ */ React.createElement(ThemeConfigIcon, null), selectedTheme ? ` ${selectedTheme.name}` : null)) : null; }; //#endregion //#region src/preset/manager.ts addons.register(ADDON_ID, () => { addons.add(TOOL_ID, { title: "Data Theme Switcher", type: types.TOOL, match: ({ viewMode, tabId }) => !!(viewMode?.match(/^(story|docs)$/) || tabId === TAB_ID), render: DataThemeSelector }); }); //#endregion