diff --git a/README.md b/README.md index d402661b..bdf8093b 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,14 @@ export default { title: "Simple Component", parameters: { cssprops: { - "--color-primary": [ - { - value: "#ff017d", - selector: ":root" - } - ] + customProperties: { + "--color-primary": [ + { + value: "#ff017d", + selector: ":root" + } + ] + } } }, } as Meta; diff --git a/packages/examples/stories/Simple/Simple.mdx b/packages/examples/stories/Simple/Simple.mdx index f0d20ffc..83fe9160 100644 --- a/packages/examples/stories/Simple/Simple.mdx +++ b/packages/examples/stories/Simple/Simple.mdx @@ -16,6 +16,6 @@ import { CssPropsBlock } from "@kickstartds/storybook-addon-component-tokens"; - + - + diff --git a/packages/examples/stories/Simple/Simple.stories.tsx b/packages/examples/stories/Simple/Simple.stories.tsx index 0fa0e6f2..d7b2f755 100644 --- a/packages/examples/stories/Simple/Simple.stories.tsx +++ b/packages/examples/stories/Simple/Simple.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import CustomDoc from "./Simple.mdx"; import "./style.css"; -const cssprops = { +const customProperties = { "--color-primary": [ { value: "#ff017d", selector: ":root" }, { value: "blue", selector: ":root", media: "(min-width: 960px)" }, @@ -15,15 +15,36 @@ const cssprops = { export default { title: "Simple Component/CSF", parameters: { - cssprops, + cssprops: { customProperties }, docs: { page: CustomDoc }, }, component: (args) =>
Hello world!
, } as Meta; -export const DefaultStory: StoryObj = { +export const DefaultGroup: StoryObj = { args: { className: "foo", }, }; -export const SecondaryStory: StoryObj = {}; +export const FlatGroup: StoryObj = { + parameters: { + cssprops: { + group({ name, media, selector }) { + return { label: `${name} @ ${selector}${media ? ` ${media}` : ""}` }; + }, + }, + }, +}; +export const SubcategoryGroup: StoryObj = { + parameters: { + cssprops: { + group({ name, media, selector }) { + return { + label: media || "default", + category: selector, + subcategory: name, + }; + }, + }, + }, +}; diff --git a/packages/examples/stories/Simple/Simple2.stories.tsx b/packages/examples/stories/Simple/Simple2.stories.tsx index a9ef5aa7..6543c72e 100644 --- a/packages/examples/stories/Simple/Simple2.stories.tsx +++ b/packages/examples/stories/Simple/Simple2.stories.tsx @@ -2,14 +2,14 @@ import * as React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import "./style.css"; -const cssprops = { +const customProperties = { "--width": [{ value: "100%", selector: ":root" }], }; export default { title: "Simple Component/CSF 2", parameters: { - cssprops, + cssprops: { customProperties }, }, component: () =>
Lorem Ipsum
, } as Meta; diff --git a/packages/storybook-addon-component-tokens/src/components/CssPropsBlock.tsx b/packages/storybook-addon-component-tokens/src/components/CssPropsBlock.tsx index 0230434d..a8c78712 100644 --- a/packages/storybook-addon-component-tokens/src/components/CssPropsBlock.tsx +++ b/packages/storybook-addon-component-tokens/src/components/CssPropsBlock.tsx @@ -1,22 +1,20 @@ import { FC } from "react"; import { useOf, Of } from "@storybook/addon-docs"; -import { FullExtractResult } from "custom-property-extract/dist/types"; +import { PARAM_KEY, CssPropsParameter } from "../constants"; import { CssPropsTable } from "./CssPropsTable"; import { hasEntries } from "./utils"; -interface CssPropsBlockProps { - of?: Of; - customProperties?: FullExtractResult; -} +type CssPropsBlockProps = CssPropsParameter & { of?: Of }; /** * For use inside Storybook Docs and MDX */ -export const CssPropsBlock: FC = (props) => { - let cssprops: FullExtractResult = {}; - +export const CssPropsBlock: FC = ({ + of = "meta", + ...cssprops +}) => { try { - const resolvedOf = useOf(props.of || "meta"); + const resolvedOf = useOf(of); const { parameters = {} } = resolvedOf.type === "meta" ? resolvedOf.csfFile.meta @@ -25,12 +23,13 @@ export const CssPropsBlock: FC = (props) => { : resolvedOf.type === "component" ? resolvedOf.projectAnnotations : {}; - cssprops = { ...parameters.cssprops }; - } catch (error) {} - cssprops = props.customProperties || cssprops; + if (parameters[PARAM_KEY]) { + cssprops = parameters[PARAM_KEY]; + } + } catch (error) {} - return hasEntries(cssprops) ? ( - + return hasEntries(cssprops.customProperties) ? ( + ) : null; }; diff --git a/packages/storybook-addon-component-tokens/src/components/CssPropsPanel.tsx b/packages/storybook-addon-component-tokens/src/components/CssPropsPanel.tsx index 4029eb78..d39a90af 100644 --- a/packages/storybook-addon-component-tokens/src/components/CssPropsPanel.tsx +++ b/packages/storybook-addon-component-tokens/src/components/CssPropsPanel.tsx @@ -1,19 +1,20 @@ import { FC } from "react"; -import { FullExtractResult } from "custom-property-extract/dist/types"; +import { useParameter } from "@storybook/api"; +import { CssPropsParameter, PARAM_KEY } from "../constants"; import { CssPropsTable } from "./CssPropsTable"; import { NoCustomPropertiesWarning } from "./NoCustomPropertiesWarning"; -import { useParameter } from "@storybook/api"; -import { PARAM_KEY } from "../constants"; import { hasEntries } from "./utils"; /** * Used by the Storybook Addons Panel */ export const CssPropsPanel: FC = () => { - const cssprops = useParameter(PARAM_KEY, {}); + const cssprops = useParameter(PARAM_KEY, { + customProperties: {}, + }); - return hasEntries(cssprops) ? ( - + return hasEntries(cssprops.customProperties) ? ( + ) : ( ); diff --git a/packages/storybook-addon-component-tokens/src/components/CssPropsTable.tsx b/packages/storybook-addon-component-tokens/src/components/CssPropsTable.tsx index bd1c0f39..fd536e12 100644 --- a/packages/storybook-addon-component-tokens/src/components/CssPropsTable.tsx +++ b/packages/storybook-addon-component-tokens/src/components/CssPropsTable.tsx @@ -1,9 +1,9 @@ import { FC, useMemo, useState } from "react"; -import { FullExtractResult } from "custom-property-extract/dist/types"; import { components, Placeholder } from "@storybook/components"; import { PureArgsTable } from "@storybook/blocks"; import { ArgTypes, Args } from "@storybook/types"; -import { isValidColor } from "./utils"; +import { CssPropsParameter } from "../constants"; +import { isValidColor, groupBySelector } from "./utils"; import { resetStorage, updateStorage, @@ -13,37 +13,36 @@ import { useInjectStyle } from "./useInjectStyle"; const ResetWrapper = components.resetwrapper; -interface CssPropsTableProps { - customProperties: FullExtractResult; - inAddonPanel?: boolean; -} +type CssPropsTableProps = CssPropsParameter & { inAddonPanel?: boolean }; export const CssPropsTable: FC = ({ customProperties = {}, + group = groupBySelector, inAddonPanel, }) => { const customPropertiesJSON = JSON.stringify(customProperties); const { rows, initialArgs, argsKeys } = useMemo( () => Object.entries(customProperties).reduce( - (prev, [key, values]) => { + (prev, [name, values]) => { values.forEach((item) => { if (!item.selector) return; - const argKey = `${item.selector.trim()}/${key.trim()}${ + const argKey = `${item.selector.trim()}/${name.trim()}${ item.media ? `/${item.media}` : "" }`; prev.argsKeys.push(argKey); + + const { label, ...table } = group({ name, ...item }); prev.rows[argKey] = { control: { type: isValidColor(item.value) ? "color" : "text" }, defaultValue: item.value, - name: `${key}${item.media ? ` @ ${item.media}` : ""}`, - table: { - category: item.selector, - }, + name: label, + table, description: item.name, key: argKey, type: { name: "string" }, }; + prev.initialArgs[argKey] = item.value; }); return prev; @@ -54,7 +53,7 @@ export const CssPropsTable: FC = ({ argsKeys: [] as string[], }, ), - [customPropertiesJSON], + [customPropertiesJSON, group], ); const [prevProps, setPrevProps] = useState(customPropertiesJSON); diff --git a/packages/storybook-addon-component-tokens/src/components/utils.ts b/packages/storybook-addon-component-tokens/src/components/utils.ts index dc555e04..78e9fcc9 100644 --- a/packages/storybook-addon-component-tokens/src/components/utils.ts +++ b/packages/storybook-addon-component-tokens/src/components/utils.ts @@ -1,7 +1,9 @@ import { useRef, useEffect } from "react"; +import { FullCustomPropertyValue } from "custom-property-extract/dist/types"; +import { Group } from "../constants"; -export const hasEntries = (customProperties: { [key: string]: any }) => - !!Object.keys(customProperties).length; +export const hasEntries = (customProperties?: { [key: string]: any }) => + !!(customProperties && Object.keys(customProperties).length); // https://www.regextester.com/103656 const colorRe = @@ -34,3 +36,12 @@ export const useDocument = () => { }, []); return docRef; }; + +export const groupBySelector: Group = ({ + name, + media, + selector, +}: FullCustomPropertyValue) => ({ + label: `${name}${media ? ` @ ${media}` : ""}`, + category: selector, +}); diff --git a/packages/storybook-addon-component-tokens/src/constants.ts b/packages/storybook-addon-component-tokens/src/constants.ts index 054677d7..c316a787 100644 --- a/packages/storybook-addon-component-tokens/src/constants.ts +++ b/packages/storybook-addon-component-tokens/src/constants.ts @@ -1,2 +1,16 @@ +import { + FullCustomPropertyValue, + FullExtractResult, +} from "custom-property-extract/dist/types"; + export const PARAM_KEY = "cssprops" as const; export const ADDON_ID = "addon-component-tokens" as const; +export type Group = (customProperty: FullCustomPropertyValue) => { + label: string; + category?: string; + subcategory?: string; +}; +export type CssPropsParameter = { + customProperties?: FullExtractResult; + group?: Group; +}; diff --git a/packages/storybook-addon-component-tokens/src/index.ts b/packages/storybook-addon-component-tokens/src/index.ts index f73a7189..8d747cc9 100644 --- a/packages/storybook-addon-component-tokens/src/index.ts +++ b/packages/storybook-addon-component-tokens/src/index.ts @@ -1,4 +1,6 @@ export { CssPropsBlock } from "./components/CssPropsBlock"; +export { groupBySelector } from "./components/utils"; +export type { CssPropsParameter } from "./constants"; if (module && module.hot && module.hot.decline) { module.hot.decline(); diff --git a/packages/storybook-addon-component-tokens/src/register.tsx b/packages/storybook-addon-component-tokens/src/register.tsx index 2473ffd5..60efba85 100644 --- a/packages/storybook-addon-component-tokens/src/register.tsx +++ b/packages/storybook-addon-component-tokens/src/register.tsx @@ -10,8 +10,8 @@ addons.register(ADDON_ID, (api: API) => { title: getTitle, type: types.PANEL, paramKey: PARAM_KEY, - render: ({ key, active }) => ( - + render: ({ active }) => ( + ), diff --git a/packages/storybook-addon-component-tokens/src/title.ts b/packages/storybook-addon-component-tokens/src/title.ts index a15cd3e6..3017f625 100644 --- a/packages/storybook-addon-component-tokens/src/title.ts +++ b/packages/storybook-addon-component-tokens/src/title.ts @@ -1,12 +1,11 @@ import { useParameter } from "@storybook/api"; -import { PARAM_KEY } from "./constants"; -import { FullExtractResult } from "custom-property-extract/dist/types"; +import { CssPropsParameter, PARAM_KEY } from "./constants"; export function getTitle(): string { - const cssprops = useParameter(PARAM_KEY, {}); - const controlsCount = Object.values(cssprops).filter( - (cssprop) => cssprop.length, - ).length; + const { customProperties = {} } = useParameter(PARAM_KEY, { + customProperties: {}, + }); + const controlsCount = Object.values(customProperties).flat().length; const suffix = controlsCount === 0 ? "" : ` (${controlsCount})`; return `Component Tokens${suffix}`; }