diff --git a/.storybook/toolbar/ThemeNextToolbar.css b/.storybook/toolbar/ThemeNextToolbar.css new file mode 100644 index 00000000000..d1c18499c94 --- /dev/null +++ b/.storybook/toolbar/ThemeNextToolbar.css @@ -0,0 +1,9 @@ +/* Custom Toolbar */ +.theme-next-toolbar-group-wrapper { + cursor: not-allowed; +} + +.theme-next-toolbar-group-wrapper > span > span { + font-weight: bold; + color: darkgray; +} diff --git a/.storybook/toolbar/ThemeNextToolbar.tsx b/.storybook/toolbar/ThemeNextToolbar.tsx index 1ecb699dcdf..52505469241 100644 --- a/.storybook/toolbar/ThemeNextToolbar.tsx +++ b/.storybook/toolbar/ThemeNextToolbar.tsx @@ -1,13 +1,16 @@ import type { TooltipLinkListLink } from "@storybook/components"; import { IconButton, + Separator, TooltipLinkList, WithTooltip, } from "@storybook/components"; import { BeakerIcon, CheckIcon } from "@storybook/icons"; -import { GLOBALS_UPDATED } from "@storybook/core-events"; -import { useGlobals, useStorybookApi } from "@storybook/manager-api"; -import React, { useState } from "react"; +import { useGlobals } from "@storybook/manager-api"; +import { clsx } from "clsx"; +import React, { AnchorHTMLAttributes } from "react"; + +import "./ThemeNextToolbar.css"; const description = "Theme next controls"; @@ -52,55 +55,66 @@ export const globalOptions: Record< }, }; -export const ThemeNextToolbar = ({ active }: { active?: boolean }) => { - // `initialized` is for avoiding incorrect `isActive` when component initializes - // what leads to blinking effect (inactive -> active -> inactive) - const [initialized, setInitialized] = useState(true); - - useStorybookApi() - .getChannel() - ?.on(GLOBALS_UPDATED, () => setInitialized(true)); +const GroupWrapper = ({ + className, + children, +}: AnchorHTMLAttributes) => { + return ( +
+ ); +}; +export const ThemeNextToolbar = ({ active }: { active?: boolean }) => { const [globals, updateGlobals] = useGlobals(); - const isActive = initialized; - - if (!isActive) return null; - const items: TooltipLinkListLink[] = Object.keys(globalOptions).flatMap( (globalKey) => { return [ { id: `theme-next-${globalKey}-header`, - disabled: true, // Fake a group header title: camelCaseToWords(globalKey), + LinkWrapper: GroupWrapper, // Custom wrapper to render group + href: "#", // Without href, `LinkWrapper` will not work }, - ...globalOptions[globalKey].items.map((value) => ({ - id: `theme-next-${globalKey}-${value}`, - right: - globals[globalKey] === value ? ( + ...globalOptions[globalKey].items.map((value) => { + const disabled = + globalKey === "themeNext" + ? false + : globals["themeNext"] !== "enable"; + const active = globals[globalKey] === value; + + return { + id: `theme-next-${globalKey}-${value}`, + right: active ? ( ) : undefined, - title: camelCaseToWords(value), - onClick: () => { - updateGlobals({ [globalKey]: value }); - }, - })), + active, + title: camelCaseToWords(value), + onClick: () => { + !disabled && updateGlobals({ [globalKey]: value }); + }, + disabled, + }; + }), ]; } ); - // console.log({ globals, isActive, active }); - return ( - } - trigger="click" - closeOnOutsideClick - > - - Theme Next - - + <> + + } + trigger="click" + closeOnOutsideClick + > + + Theme Next + + + ); };