diff --git a/.storybook/preview.ts b/.storybook/preview.ts index bc90f99..5d96e5c 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,5 +1,5 @@ import type { Preview } from "@storybook/react"; -import Themes from './themes.json' +import Themes from '../EXAMPLES_THEMES.json'; const preview: Preview = { parameters: { diff --git a/.storybook/themes.json b/.storybook/themes.json deleted file mode 100644 index ff4b781..0000000 --- a/.storybook/themes.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "name": "Theme one", - "tokens": { - "font-family": "Roboto, sans-serif", - "primary": "#fcfc30", - "primary-text": "#00A8FF", - "primary-container": "#fcf130", - "primary-light": "#fcfc30", - "primary-dark": "#FFFFFF", - "primary-hover": "#fae128", - "secondary": "#465eff", - "secondary-text": "#fff", - "secondary-container": "#2f4af8", - "secondary-light": "#465eff", - "secondary-dark": "#465eff", - "secondary-hover": "#465eff", - "background": "#fcfc30", - "background-gradient": "linear-gradient(to right, var(--secondary), var(--secondary-dark))", - "text": "#000" - } - }, - { - "name": "Theme Two", - "miniLogo": "https://javisperez.github.io/tailwindcolorshades/img/icons/favicon-16x16.png", - "tokens": { - "primary": "#c8c615", - "primary-text": "#faf9e8", - "primary-container": "#b4b213", - "primary-light": "#e9e8a1", - "primary-dark": "#62610a", - "primary-hover": "#969510", - "secondary": "#8a59a7", - "secondary-text": "#f3eef6", - "secondary-container": "#7c5096", - "secondary-light": "#d0bddc", - "secondary-dark": "#533564", - "secondary-hover": "#533564", - "background": "#fcfc30", - "background-gradient": "linear-gradient(to right, var(--secondary), var(--secondary-dark))", - "text": "#000" - } - }, - { - "name": "Theme Three", - "miniLogo": "https://www.gclaims.com.br/assets/images/favicon/favicon-16x16.png", - "tokens": { - "primary": "#00a8ff", - "primary-text": "#e6f6ff", - "primary-container": "#007ebf", - "primary-light": "#99dcff", - "primary-dark": "#006599", - "primary-hover": "#007ebf", - "secondary": "#00c9a8", - "secondary-text": "#e6faf6", - "secondary-container": "#00c9a8", - "secondary-light": "#4dd9c2", - "secondary-dark": "#00977e", - "secondary-hover": "#00b597", - "background": "#fcfc30", - "background-gradient": "linear-gradient(to right, var(--primary), var(--primary-dark))", - "text": "#000" - } - } - ] diff --git a/EXAMPLES_THEMES.json b/EXAMPLES_THEMES.json new file mode 100644 index 0000000..96f46d8 --- /dev/null +++ b/EXAMPLES_THEMES.json @@ -0,0 +1,65 @@ +[ + { + "name": "Theme one", + "tokens": { + "--font-family": "Roboto, sans-serif", + "--primary": "#fcfc30", + "--primary-text": "#00A8FF", + "--primary-container": "#fcf130", + "--primary-light": "#fcfc30", + "--primary-dark": "#FFFFFF", + "--primary-hover": "#fae128", + "--secondary": "#465eff", + "--secondary-text": "#fff", + "--secondary-container": "#2f4af8", + "--secondary-light": "#465eff", + "--secondary-dark": "#465eff", + "--secondary-hover": "#465eff", + "--background": "#fcfc30", + "--background-gradient": "linear-gradient(to right, var(--secondary), var(--secondary-dark))", + "--text": "#000" + } + }, + { + "name": "Theme Two", + "miniLogo": "https://javisperez.github.io/tailwindcolorshades/img/icons/favicon-16x16.png", + "tokens": { + "--primary": "#c8c615", + "--primary-text": "#faf9e8", + "--primary-container": "#b4b213", + "--primary-light": "#e9e8a1", + "--primary-dark": "#62610a", + "--primary-hover": "#969510", + "--secondary": "#8a59a7", + "--secondary-text": "#f3eef6", + "--secondary-container": "#7c5096", + "--secondary-light": "#d0bddc", + "--secondary-dark": "#533564", + "--secondary-hover": "#533564", + "--background": "#fcfc30", + "--background-gradient": "linear-gradient(to right, var(--secondary), var(--secondary-dark))", + "--text": "#000" + } + }, + { + "name": "Theme Three", + "miniLogo": "https://www.gclaims.com.br/assets/images/favicon/favicon-16x16.png", + "tokens": { + "--primary": "#00a8ff", + "--primary-text": "#e6f6ff", + "--primary-container": "#007ebf", + "--primary-light": "#99dcff", + "--primary-dark": "#006599", + "--primary-hover": "#007ebf", + "--secondary": "#00c9a8", + "--secondary-text": "#e6faf6", + "--secondary-container": "#00c9a8", + "--secondary-light": "#4dd9c2", + "--secondary-dark": "#00977e", + "--secondary-hover": "#00b597", + "--background": "#fcfc30", + "--background-gradient": "linear-gradient(to right, var(--primary), var(--primary-dark))", + "--text": "#000" + } + } + ] diff --git a/README.md b/README.md index b1107b0..0b5ecd9 100644 --- a/README.md +++ b/README.md @@ -70,12 +70,12 @@ export const parameters = { "miniLogo": "", "tokens": { ## add all yours css variables here - "yours-primary": "#c8c615", - "yours-primary-text": "#faf9e8", - "yours-primary-container": "#b4b213", - "yours-primary-light": "#e9e8a1", - "yours-primary-dark": "#62610a", - "yours-primary-hover": "#969510" + "--yours-primary": "#c8c615", + "--yours-primary-text": "#faf9e8", + "--yours-primary-container": "#b4b213", + "--yours-primary-light": "#e9e8a1", + "--yours-primary-dark": "#62610a", + "--yours-primary-hover": "#969510" ## add all yours css variables here } }, @@ -84,12 +84,12 @@ export const parameters = { "miniLogo": "https://javisperez.github.io/tailwindcolorshades/img/icons/favicon-16x16.png", "tokens": { ## add all yours css variables here - "yours-primary": "#c8c615", - "yours-primary-text": "#faf9e8", - "yours-primary-container": "#b4b213", - "yours-primary-light": "#e9e8a1", - "yours-primary-dark": "#62610a", - "yours-primary-hover": "#969510" + "--yours-primary": "#c8c615", + "--yours-primary-text": "#faf9e8", + "--yours-primary-container": "#b4b213", + "--yours-primary-light": "#e9e8a1", + "--yours-primary-dark": "#62610a", + "--yours-primary-hover": "#969510" ## add all yours css variables here } }, diff --git a/src/Panel.tsx b/src/Panel.tsx deleted file mode 100644 index 76c7bcd..0000000 --- a/src/Panel.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import { useAddonState, useChannel } from "@storybook/manager-api"; -import { AddonPanel } from "@storybook/components"; -import { ADDON_ID, EVENTS } from "./constants"; -import { PanelContent } from "./components/PanelContent"; - -interface PanelProps { - active: boolean; -} - -export const Panel: React.FC<PanelProps> = (props) => { - // https://storybook.js.org/docs/react/addons/addons-api#useaddonstate - const [results, setState] = useAddonState(ADDON_ID, { - danger: [], - warning: [], - }); - - // https://storybook.js.org/docs/react/addons/addons-api#usechannel - const emit = useChannel({ - [EVENTS.RESULT]: (newResults) => setState(newResults), - }); - - return ( - <AddonPanel {...props}> - <PanelContent - results={results} - fetchData={() => { - emit(EVENTS.REQUEST); - }} - clearData={() => { - emit(EVENTS.CLEAR); - }} - /> - </AddonPanel> - ); -}; diff --git a/src/Tab.tsx b/src/Tab.tsx deleted file mode 100644 index 49c7e27..0000000 --- a/src/Tab.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; -import { useParameter } from "@storybook/manager-api"; -import { PARAM_KEY } from "./constants"; -import { TabContent } from "./components/TabContent"; - -interface TabProps { - active: boolean; -} - -export const Tab: React.FC<TabProps> = ({ active }) => { - // https://storybook.js.org/docs/react/addons/addons-api#useparameter - const paramData = useParameter<string>(PARAM_KEY, ""); - - return active ? <TabContent code={paramData} /> : null; -}; diff --git a/src/Tool.tsx b/src/Tool.tsx index 65f7720..762b1e4 100644 --- a/src/Tool.tsx +++ b/src/Tool.tsx @@ -5,17 +5,19 @@ import { PARAM_KEY, TOOL_ID } from "./constants"; import { DropdownTool } from "./components/Dropdown"; import { ThemeTokensType, ThemeType } from "./utils/types"; import { DisplayToolState } from "./utils/actions"; +import { GetDataStorage } from "./utils/persist"; export const Tool = memo(function MyAddonSelector() { - const [{ themeVariableCss }] = useGlobals(); + const [ global, updateGlobals] = useGlobals(); const tokensCss = useParameter<ThemeTokensType>(PARAM_KEY) + useEffect( () => { - if(tokensCss && themeVariableCss) { - const themeSelected = tokensCss?.themes.filter((_theme: ThemeType) => _theme.name === themeVariableCss?.name)[0]; - DisplayToolState('html', {isInDocs: false, themeVariableCss: themeVariableCss.name, themeSelected: themeSelected} ) + if(tokensCss && global.themeVariableCss) { + const themeSelected = tokensCss?.themes.filter((_theme: ThemeType) => _theme.name === global.themeVariableCss?.name)[0]; + DisplayToolState('html', {isInDocs: false, themeVariableCss: global.themeVariableCss.name, themeSelected: themeSelected} ) } - }, [themeVariableCss]) + }, [global.themeVariableCss]) if (!tokensCss && !tokensCss?.themes?.length) { return null diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index 6098672..822a9bc 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -6,6 +6,7 @@ import { MountedOptions, ValidatorArrayTheme } from "../utils"; import { OptionsThemeType, ThemeType } from "../utils/types"; import { ButtonItem, ItemSelected, ViewToolTip } from "./styles"; import { useGlobals } from "@storybook/manager-api"; +import { GetDataStorage } from "src/utils/persist"; interface propsDropDown { list: ThemeType[] @@ -19,13 +20,21 @@ export const DropdownTool = memo( function myDropdownMemo(props: propsDropDown) const [options, setOptions] = React.useState<OptionsThemeType[]>([]); React.useEffect(() => { + const dataLocal: any = GetDataStorage(); + if(dataLocal?.selected) { + updateGlobals({ + ...global, + themeVariableCss: dataLocal.selected + }) + } setListOptionsItem() }, []) + const LabelState = React.useCallback(() => { return <> {!!themeVariableCss && !!themeVariableCss?.name ? - <ItemSelected shadow className={`button-` + PARAM_KEY}> + <ItemSelected shadow className={`button-` + PARAM_KEY + '-selected'}> {!!themeVariableCss?.miniLogo && <img src={themeVariableCss.miniLogo} alt=""/> } @@ -74,10 +83,15 @@ export const DropdownTool = memo( function myDropdownMemo(props: propsDropDown) return ( <ButtonItem key={index} + className={ + `button-` + PARAM_KEY + + (themeVariableCss?.name === item.name ? ' selected' : '') + } onClick={() => toggleMyTool(item)} + > - {item.type === 'icon' && <Icons icon="lightning" /> } - {item.type === 'image' && <img src={item.miniLogo} alt=""/> } + {item.type === 'icon' && <Icons icon="starhollow" /> } + {(item.type === 'image' && !!item.miniLogo) && <img src={item.miniLogo} alt=""/> } {item.name} </ButtonItem> ) @@ -88,7 +102,7 @@ export const DropdownTool = memo( function myDropdownMemo(props: propsDropDown) return ( <WithTooltip placement="top" trigger="click" tooltip={ () => ComponentItem() }> - {LabelState()} + <LabelState /> </WithTooltip> ); }); \ No newline at end of file diff --git a/src/components/List.tsx b/src/components/List.tsx deleted file mode 100644 index d3d6cfa..0000000 --- a/src/components/List.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { Fragment, useState } from "react"; -import { styled, themes, convert } from "@storybook/theming"; -import { Icons, IconsProps } from "@storybook/components"; - -const ListWrapper = styled.ul({ - listStyle: "none", - fontSize: 14, - padding: 0, - margin: 0, -}); - -const Wrapper = styled.div({ - display: "flex", - width: "100%", - borderBottom: `1px solid ${convert(themes.normal).appBorderColor}`, - "&:hover": { - background: convert(themes.normal).background.hoverable, - }, -}); - -const Icon = styled(Icons)<IconsProps>({ - height: 10, - width: 10, - minWidth: 10, - color: convert(themes.normal).color.mediumdark, - marginRight: 10, - transition: "transform 0.1s ease-in-out", - alignSelf: "center", - display: "inline-flex", -}); - -const HeaderBar = styled.div({ - padding: convert(themes.normal).layoutMargin, - paddingLeft: convert(themes.normal).layoutMargin - 3, - background: "none", - color: "inherit", - textAlign: "left", - cursor: "pointer", - borderLeft: "3px solid transparent", - width: "100%", - - "&:focus": { - outline: "0 none", - borderLeft: `3px solid ${convert(themes.normal).color.secondary}`, - }, -}); - -const Description = styled.div({ - padding: convert(themes.normal).layoutMargin, - marginBottom: convert(themes.normal).layoutMargin, - fontStyle: "italic", -}); - -type Item = { - title: string; - description: string; -}; - -interface ListItemProps { - item: Item; -} - -export const ListItem: React.FC<ListItemProps> = ({ item }) => { - const [open, onToggle] = useState(false); - - return ( - <Fragment> - <Wrapper> - <HeaderBar onClick={() => onToggle(!open)} role="button"> - <Icon - icon="arrowdown" - color={convert(themes.normal).appBorderColor} - style={{ - transform: `rotate(${open ? 0 : -90}deg)`, - }} - /> - {item.title} - </HeaderBar> - </Wrapper> - {open ? <Description>{item.description}</Description> : null} - </Fragment> - ); -}; - -interface ListProps { - items: Item[]; -} - -export const List: React.FC<ListProps> = ({ items }) => ( - <ListWrapper> - {items.map((item, idx) => ( - <ListItem key={idx} item={item}></ListItem> - ))} - </ListWrapper> -); diff --git a/src/components/PanelContent.tsx b/src/components/PanelContent.tsx deleted file mode 100644 index bf3db52..0000000 --- a/src/components/PanelContent.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { Fragment } from "react"; -import { styled, themes, convert } from "@storybook/theming"; -import { TabsState, Placeholder, Button } from "@storybook/components"; -import { List } from "./List"; - -export const RequestDataButton = styled(Button)({ - marginTop: "1rem", -}); - -type Results = { - danger: any[]; - warning: any[]; -}; - -interface PanelContentProps { - results: Results; - fetchData: () => void; - clearData: () => void; -} - -/** - * Checkout https://github.com/storybookjs/storybook/blob/next/code/addons/jest/src/components/Panel.tsx - * for a real world example - */ -export const PanelContent: React.FC<PanelContentProps> = ({ - results, - fetchData, - clearData, -}) => ( - <TabsState - initial="overview" - backgroundColor={convert(themes.normal).background.hoverable} - > - <div - id="overview" - title="Overview" - color={convert(themes.normal).color.positive} - > - <Placeholder> - <Fragment> - Addons can gather details about how a story is rendered. This is panel - uses a tab pattern. Click the button below to fetch data for the other - two tabs. - </Fragment> - <Fragment> - <RequestDataButton - secondary - small - onClick={fetchData} - style={{ marginRight: 16 }} - > - Request data - </RequestDataButton> - - <RequestDataButton outline small onClick={clearData}> - Clear data - </RequestDataButton> - </Fragment> - </Placeholder> - </div> - <div - id="danger" - title={`${results.danger.length} Danger`} - color={convert(themes.normal).color.negative} - > - <List items={results.danger} /> - </div> - <div - id="warning" - title={`${results.warning.length} Warning`} - color={convert(themes.normal).color.warning} - > - <List items={results.warning} /> - </div> - </TabsState> -); diff --git a/src/components/TabContent.tsx b/src/components/TabContent.tsx deleted file mode 100644 index 29d289c..0000000 --- a/src/components/TabContent.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import { styled } from "@storybook/theming"; -import { H1, Link, Code } from "@storybook/components"; - -const TabWrapper = styled.div(({ theme }) => ({ - background: theme.background.content, - padding: "4rem 20px", - minHeight: "100vh", - boxSizing: "border-box", -})); - -const TabInner = styled.div({ - maxWidth: 768, - marginLeft: "auto", - marginRight: "auto", -}); - -interface TabContentProps { - code: string; -} - -export const TabContent: React.FC<TabContentProps> = ({ code }) => ( - <TabWrapper> - <TabInner> - <H1>My Addon</H1> - <p> - Your addon can create a custom tab in Storybook. - </p> - <p> - You have full control over what content is being rendered here. You can - use components from{" "} - <Link href="https://github.com/storybookjs/storybook/tree/next/code/ui/components"> - @storybook/components - </Link>{" "} - to match the look and feel of Storybook, for example the{" "} - <code><Code /></code> component below. Or build a completely - custom UI. - </p> - <Code>{code}</Code> - </TabInner> - </TabWrapper> -); diff --git a/src/components/styles.ts b/src/components/styles.ts index bdaeb9a..de6ef43 100644 --- a/src/components/styles.ts +++ b/src/components/styles.ts @@ -34,6 +34,10 @@ export const ButtonItem = styled(Button)({ width: 16, height: 16, objectFit: 'contain', + }, + '&.selected': { + color: convert(themes.normal).color.secondary, + fontWeight: 'bold' } }); diff --git a/src/constants.ts b/src/constants.ts index 5ab8bdc..2501b8d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,8 +2,3 @@ export const ADDON_ID = 'storybook-addon-designTokensCss'; export const TOOL_ID = `${ADDON_ID}/tool`; export const PARAM_KEY = 'designTokensCss'; -export const EVENTS = { - RESULT: `${ADDON_ID}/result`, - REQUEST: `${ADDON_ID}/request`, - CLEAR: `${ADDON_ID}/clear`, -}; diff --git a/src/styles.ts b/src/styles.ts deleted file mode 100644 index bdaeb9a..0000000 --- a/src/styles.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Button } from "@storybook/components"; -import { convert, styled, themes } from "@storybook/theming"; - - -export const ViewToolTip = styled.div({ - background: convert(themes.normal).background.content, - borderRadius: convert(themes.normal).appBorderRadius, - maxHeight: 300, - maxWidth: 300, - overflowY: 'auto', - display: 'grid', - gridTemplateColumns: '1fr', - -}) - - - -export const ButtonItem = styled(Button)({ - width: '100%', - maxWidth: 300, - borderRadius: convert(themes.normal).appBorderRadius, - borderBottom: '1px solid ' + convert(themes.normal).background.hoverable, - padding: '8px 10px', - display: "grid", - gridTemplateColumns: '16px 1fr', - gap: '8px', - textAlign: 'left', - alignItems: 'center', - ':hover': { - background: convert(themes.normal).background.hoverable, - cursor: 'pointer' - }, - 'img': { - width: 16, - height: 16, - objectFit: 'contain', - } -}); - - -export const ItemSelected = styled.div((props: {shadow?: boolean}) => ({ - width: '100%', - height: 'calc(100% - 10px)', - maxWidth: 300, - padding: '8px 10px', - display: "flex", - textAlign: 'left', - alignItems: 'center', - backgroundColor: 'rgb(93 28 28 / 6%)', - borderRadius: 15, - margin: '5px auto', - 'span': { - fontSize: 13 - }, - 'svg': { - width: 14, - marginRight: 2 - }, - 'img': { - width: 16, - height: 16, - objectFit: 'contain', - marginRight: 8 - } -})); diff --git a/src/withGlobals.ts b/src/withGlobals.ts index 80c4e81..2c16ef0 100644 --- a/src/withGlobals.ts +++ b/src/withGlobals.ts @@ -8,6 +8,7 @@ import { GeneratorId } from "./utils"; import { DisplayToolState } from "./utils/actions"; import { GetDataStorage, SetDataStorage } from "./utils/persist"; import { ThemeType } from "./utils/types"; +import React from "react"; export const withGlobals = ( StoryFn: StoryFunction<Renderer>, @@ -22,7 +23,6 @@ export const withGlobals = ( useEffect(() => { const dataLocal: any = GetDataStorage(); if(dataLocal && dataLocal.themes && dataLocal.selected) { - updateGlobals({themeVariableCss: dataLocal.selected}); const selectorId = isInDocs ? `#anchor--${context.id} .docs-story` : `#root`; DisplayToolState(selectorId, { isInDocs, themeVariableCss: dataLocal.selected.name, themeSelected: dataLocal.selected }) } @@ -38,7 +38,7 @@ export const withGlobals = ( } const selectorId = isInDocs ? `#anchor--${context.id} .docs-story` : `#root`; DisplayToolState(selectorId, { isInDocs, themeVariableCss, themeSelected}) - }, [themeVariableCss]); + }, [themeVariableCss]); return StoryFn(); }; \ No newline at end of file diff --git a/src/withRoundTrip.ts b/src/withRoundTrip.ts deleted file mode 100644 index 33bd59b..0000000 --- a/src/withRoundTrip.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useChannel } from "@storybook/preview-api"; -import type { - Renderer, - PartialStoryFn as StoryFunction, -} from "@storybook/types"; -import { STORY_CHANGED } from "@storybook/core-events"; -import { EVENTS } from "./constants"; - -export const withRoundTrip = (storyFn: StoryFunction<Renderer>) => { - const emit = useChannel({ - [EVENTS.REQUEST]: () => { - emit(EVENTS.RESULT, { - danger: [ - { - title: "Panels are the most common type of addon in the ecosystem", - description: - "For example the official @storybook/actions and @storybook/a11y use this pattern", - }, - { - title: - "You can specify a custom title for your addon panel and have full control over what content it renders", - description: - "@storybook/components offers components to help you addons with the look and feel of Storybook itself", - }, - ], - warning: [ - { - title: - 'This tabbed UI pattern is a popular option to display "test" reports.', - description: - "It's used by @storybook/addon-jest and @storybook/addon-a11y. @storybook/components offers this and other components to help you quickly build an addon", - }, - ], - }); - }, - [STORY_CHANGED]: () => { - emit(EVENTS.RESULT, { - danger: [], - warning: [], - }); - }, - [EVENTS.CLEAR]: () => { - emit(EVENTS.RESULT, { - danger: [], - warning: [], - }); - }, - }); - - return storyFn(); -};