UNPKG

@vitus-labs/rocketstories

Version:

Rocketstyle is ultra powerful and extensible styling system for building React components blazingly fast, easily and make them easily extensible and reusable.

1,149 lines (1,121 loc) 35.3 kB
import { HTML_TAGS, config, get, isEmpty, pick } from "@vitus-labs/core"; import rocketstyle, { isRocketComponent } from "@vitus-labs/rocketstyle"; import { Element, List } from "@vitus-labs/elements"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { Fragment as Fragment$1, createContext, createElement, useContext } from "react"; import { styles } from "@vitus-labs/unistyle"; //#region src/controls/element.ts /** * Default Storybook control definitions for Element-based components. * Covers layout props (direction, alignX, alignY), content slots * (beforeContent, afterContent), CSS extension props, and HTML tag selection. */ const group$4 = "Element (Vitus-Labs)"; const directionType = "inline | rows | reverseRows | reverseInline"; const alignXType = "left | center | right | block | spaceBetween | spaceAround"; const alignYType = "top | center | block | spaceBetween | spaceAround"; const CssType = "string | (css) => css`` | css``"; const DIRECTION = { group: group$4, type: "select", options: ["-----", ...directionType.split(" | ")], value: "rows", valueType: `${directionType} | Record<string, ${directionType}> | Array<${directionType}` }; const ALIGN_X = { group: group$4, type: "select", options: alignXType.split(" | "), value: "left", valueType: `${alignXType} | Record<string, ${alignXType}> | Array<${alignXType}` }; const ALIGN_Y = { group: group$4, type: "select", options: alignYType.split(" | "), value: "center", valueType: `${alignYType} | Record<string, ${alignYType}> | Array<${alignYType}` }; const CSS = { group: group$4, type: "text", valueType: `${CssType} | Record<string,${CssType}> | Array<${CssType}>` }; var element_default$1 = { tag: { group: group$4, type: "select", options: HTML_TAGS, valueType: "HTMLTag", description: "A prop which will change **HTML tag** of the element." }, children: { group: group$4, type: "", valueType: "ReactNode", description: "React children. Priorities when rendering are **children** → **content** → **label**, therefore _children_ has the highest priority." }, content: { group: group$4, type: "text", valueType: "ReactNode", description: "A prop which can be used instead of _children_. Priorities when rendering are **children** → **content** → **label**, therefore _content_ has the middle priority." }, label: { group: group$4, type: "text", valueType: "ReactNode", description: "A prop which can be used instead of _children_. Priorities when rendering are **children** → **content** → **label**, therefore _label_ has the lowest priority." }, block: { group: group$4, type: "boolean", valueType: "boolean | Record<string, boolean> | Array<boolean>", description: "Defines whether should behave as **inline** or **block** element." }, direction: { ...DIRECTION, value: void 0, description: "Define whether element should render **horizontally** or **vertically**." }, alignX: { ...ALIGN_X, description: "Define alignment of **beforeContent**, **content**, and **afterContent** with respect to root element on **axis X**." }, alignY: { ...ALIGN_Y, description: "Define alignment of **beforeContent**, **content**, and **afterContent** with respect to the root element on **axis Y**." }, contentDirection: { ...DIRECTION, description: "Define whether the children in **content** wrapper should be rendered in _line_ or in _rows_." }, contentAlignX: { ...ALIGN_X, description: "Define how the children in **content** wrapper should be aligned on **axis X**." }, contentAlignY: { ...ALIGN_Y, description: "Define how the children in **content** wrapper should be aligned on **axis Y**." }, beforeContentDirection: { ...DIRECTION, description: "Define whether children in **beforeContent** wrapper should be rendered in _line_ or in _rows_." }, beforeContentAlignX: { ...ALIGN_X, description: "Define how children in **beforeContent** wrapper should be aligned on **axis X**." }, beforeContentAlignY: { ...ALIGN_Y, description: "Define how children in **beforeContent** wrapper should be aligned on **axis Y**." }, afterContentDirection: { ...DIRECTION, description: "Define whether children in **afterContent** wrapper should be rendered in _line_ or in _rows_." }, afterContentAlignX: { ...ALIGN_X, description: "Define how children in **afterContent** wrapper should be aligned on **axis X**." }, afterContentAlignY: { ...ALIGN_Y, description: "Define how children in **afterContent** wrapper should be aligned on **axis Y**." }, equalCols: { type: "boolean", group: group$4, valueType: "boolean | Record<string,boolean> | Array<boolean>", description: "Whether should all inner elements have the same `width` / `height`." }, gap: { type: "number", group: group$4, valueType: "number | Record<string,number> | Array<number>", description: "Defines space gap **between** _beforeContent_, _content_ and _afterContent_ if one of _beforeContent_ or _afterContent_ contain _children_ to be rendered." }, beforeContent: { group: group$4, type: "", valueType: "ReactNode", description: "A children to be rendered inside `beforeContent` wrapper." }, afterContent: { group: group$4, type: "", valueType: "ReactNode", description: "A children to be rendered inside `afterContent` wrapper." }, css: { ...CSS, description: "An additional styling prop to enhance the **root** element CSS styles." }, contentCss: { ...CSS, description: "An additional styling prop to enhance the **content** element CSS styles." }, beforeContentCss: { ...CSS, description: "An additional styling prop to enhance the **beforeContent** element CSS styles." }, afterContentCss: { ...CSS, description: "An additional styling prop to enhance the **afterContent** element CSS styles." }, ref: { group: group$4, description: "A React ref", valueType: "ForwardedRef<any>" }, innerRef: { group: group$4, description: "A React ref", valueType: "ForwardedRef<any>" }, dangerouslySetInnerHTML: { group: group$4, type: "text", disable: true, valueType: "any" } }; //#endregion //#region src/controls/list.ts /** * Default Storybook control definitions for List-based components. * Covers data input, item/wrap component props, key extraction, * and disables label/content controls that do not apply to lists. */ const group$3 = "List (@vitus-labs)"; const itemPropsType = `Record<string, any> | (props, meta) => Record<string,any>`; var list_default = { rootElement: { group: group$3, type: "boolean", valueType: "boolean", description: "Whether a **root** element should be rendered or the output should be just a type of React **Fragment**." }, data: { group: group$3, type: "array", valueType: "string[] | number[] | object[]", description: "An array of item values to be passed to item component. Data are being passed to _component_ prop element." }, valueName: { group: group$3, type: "text", valueType: `string`, description: "Is required when **data** consists of **strings** or **numbers** to name value being passed as a prop." }, itemProps: { group: group$3, valueType: itemPropsType, description: "A customizable hook for dynamically render props for each **item** component." }, wrapProps: { group: group$3, valueType: itemPropsType, description: "A customizable hook for dynamically render props for each **wrapComponent** when _wrapComponent_ is passed, otherwise ignored." }, itemKey: { group: group$3, valueType: "string | `(item, i) => number | string`", description: "Prop for defining item key in list. **name** / **value** if default behavior doesn't work out." }, component: { group: group$3, type: "component", valueType: "ComponentType", description: "A component to be rendered within the List per item. Receives props from _data_ array props." }, wrapComponent: { group: group$3, type: "component", valueType: `ComponentType`, description: "A component to be used as a wrapper component for each item component." }, label: { disable: true }, content: { disable: true } }; //#endregion //#region src/controls/overlay.ts const group$2 = "Overlay (Vitus-Labs)"; var overlay_default = { refName: { type: "text", value: "ref", description: "Overlay component access **ref** to directly mutate styles when calculation position to prevent re-renders. It's being used for both `trigger`, and `children` element at the same time. Your components must accept refs with the same naming.", group: group$2 }, triggerRefName: { type: "text", description: "A key name how a **ref** should be passed to trigger component", group: group$2 }, contentRefName: { type: "text", description: "A key name how a **ref** should be passed to content component", group: group$2 }, isOpen: { type: "boolean", value: false, description: "", group: group$2 }, openOn: { type: "select", options: ["click", "hover"], value: "click", description: "", group: group$2 }, closeOn: { type: "select", options: [ "click", "triggerClick", "hover", "manual" ], value: "click", description: "", group: group$2 }, type: { type: "select", options: [ "dropdown", "tooltip", "popover", "modal" ], value: "dropdown", description: "", group: group$2 }, align: { type: "select", options: [ "top", "left", "bottom", "right" ], value: "bottom", description: "", group: group$2 }, alignX: { type: "select", options: [ "left", "center", "right" ], value: "left", description: "", group: group$2 }, alignY: { type: "select", options: [ "top", "center", "bottom" ], value: "bottom", description: "", group: group$2 }, position: { type: "select", options: [ "fixed", "absolute", "relative", "static" ], value: "fixed", description: "", group: group$2 }, offsetX: { type: "number", value: 0, description: "", group: group$2 }, offsetY: { type: "number", value: 0, description: "", group: group$2 }, throttleDelay: { type: "number", value: 200, description: "", group: group$2 }, children: { description: "A content to be rendered when Overlay is open" } }; //#endregion //#region src/controls/rocketstyle.ts /** * Default Storybook control definitions for rocketstyle pseudo-state props. * Provides boolean toggles for hover, active, pressed, and focus states, * as well as descriptions for their corresponding mouse/focus event handlers. */ const group$1 = "Rocketstyle (Vitus-Labs)"; var rocketstyle_default = { hover: { group: group$1, type: "boolean", value: false, description: "Can be manually triggered **hover** event on the element. Behaves as **:hover** state in _CSS_." }, active: { group: group$1, type: "boolean", value: false, description: "Can be manually triggered **active** event on the element. Can be used to define element as `active`, e.g. _links_." }, pressed: { group: group$1, type: "boolean", value: false, description: "Can be manually triggered **pressed** event on the element. Behaves as `:active` state in _CSS_." }, focus: { group: group$1, type: "boolean", value: false, description: "Can be manually triggered **focus** event on the element. Behaves as `:focus` state in _CSS_." }, onMouseEnter: { group: group$1, type: "function", description: "The _onMouseEnter_ function can take a `SyntheticMouseEvent`." }, onMouseLeave: { group: group$1, type: "function", description: "The _onMouseLeave_ function can take a `SyntheticMouseEvent.`" }, onMouseDown: { group: group$1, type: "function", description: "The _onMouseDown_ function can take a `SyntheticMouseEvent`." }, onMouseUp: { group: group$1, type: "function", description: "The _onMouseUp_ function can take a `SyntheticMouseEvent`." }, onFocus: { group: group$1, type: "function", description: "The _onFocus_ function can take a `SyntheticFocusEvent`." }, onBlur: { group: group$1, type: "function", description: "The _onBlur_ function can take a `SyntheticFocusEvent`." } }; //#endregion //#region src/controls/text.ts /** * Default Storybook control definitions for Text-based components. * Provides controls for paragraph mode, HTML tag selection, children/label * content, and CSS extension. */ const group = "Text (Vitus-Labs)"; var text_default = { paragraph: { group, type: "boolean", description: "Changes a behavior of inline text to become **block** text. Also changes HTML **tag** to `p`" }, tag: { group, type: "select", options: HTML_TAGS }, children: { group, type: "", valueType: "ReactNode", description: "React children. Priorities when rendering are **children** → **label**, therefore _children_ has the highest priority." }, label: { group, type: "text", valueType: "ReactNode", description: "A prop which can be used instead of _children_. Priorities when rendering are **children** → **label**, therefore _label_ has lower priority than _children_." }, extendCss: { group, type: "text", description: "An additional styling prop to enhance Text element CSS styles." } }; //#endregion //#region src/utils/controls.ts /** * Utilities for creating, converting, and formatting Storybook controls. * Handles transformation from rocketstories control definitions to the * Storybook argTypes format, including dimension-based select controls * and component-specific default controls. */ /** Normalizes user-supplied control shorthand (string or object) into full ControlConfiguration objects. */ const createControls = (props) => Object.entries(props).reduce((acc, [key, value]) => { if (typeof value === "string") return { ...acc, [key]: { type: value } }; if (typeof value === "object" && value !== null) return { ...acc, [key]: value }; return acc; }, {}); /** Converts rocketstyle dimension metadata into select/multi-select Storybook controls. */ const convertDimensionsToControls = ({ dimensions, multiKeys }) => Object.entries(dimensions).reduce((acc, [key, value]) => { const valueKeys = Object.keys(value); const control = { type: !!multiKeys[key] ? "multi-select" : "select", options: valueKeys, group: "Dimensions [Rocketstyle (Vitus-Labs)]" }; return { ...acc, [key]: control }; }, {}); /** Returns pre-defined controls based on the component's Vitus Labs type (Element, List, Text, Overlay). */ const getDefaultVitusLabsControls = (component) => { const { IS_ROCKETSTYLE, VITUS_LABS__COMPONENT } = component; const IS_ELEMENT = VITUS_LABS__COMPONENT === "@vitus-labs/elements/Element"; const IS_LIST = VITUS_LABS__COMPONENT === "@vitus-labs/elements/List"; const IS_TEXT = VITUS_LABS__COMPONENT === "@vitus-labs/elements/Text"; const IS_OVERLAY = VITUS_LABS__COMPONENT === "@vitus-labs/elements/Overlay"; return { ...IS_ELEMENT || IS_LIST ? element_default$1 : {}, ...IS_LIST ? list_default : {}, ...IS_TEXT ? text_default : {}, ...IS_OVERLAY ? overlay_default : {}, ...IS_ROCKETSTYLE ? rocketstyle_default : {} }; }; const makeStorybookControls = (obj, props) => Object.entries(obj).reduce((acc, [key, control]) => { const defaultValue = typeof props[key] !== "function" ? props[key] : void 0; if (control.disable) acc[key] = { table: { disable: control.disable } }; else acc[key] = { control: { type: control.type ?? "text" }, description: control.description, options: control.options, table: { defaultValue: { summary: defaultValue || control.value }, disable: control.disable, category: control.group, type: { summary: control.valueType } } }; return acc; }, {}); const disableControl = (name) => ({ [name]: { table: { disable: true } } }); const disableDimensionControls = (dimensions, dimensionName) => { const result = dimensionName ? disableControl(dimensionName) : {}; return Object.values(dimensions).reduce((acc, value) => { Object.keys(value).forEach((item) => { acc = { ...acc, ...disableControl(item) }; }); return acc; }, result); }; //#endregion //#region src/internal/StoryHoc.tsx const story = (WrappedComponent) => ({ component, attrs, controls }) => { const storybookControls = makeStorybookControls(createControls(controls), attrs); const Enhanced = WrappedComponent(component); Enhanced.args = attrs; Enhanced.argTypes = storybookControls; return Enhanced; }; //#endregion //#region src/stories/base/renderList.tsx /** * Renders a list story for a regular non-rocketstyle component. * Accepts list configuration (data, itemKey, etc.) and wraps the component * through StoryHoc, rendering it inside a Vitus Labs List element. */ const LooseList$1 = List; var renderList_default$1 = (list) => story((component) => (props) => /* @__PURE__ */ jsx(LooseList$1, { rootElement: false, ...list, itemProps: props, component })); //#endregion //#region src/stories/base/renderMain.tsx /** * Renders the main (default) story for a regular non-rocketstyle component. * Wraps the component through StoryHoc to attach Storybook controls, then * renders it with createElement using the current args. */ var renderMain_default$1 = story((component) => (props) => /* @__PURE__ */ jsx(Fragment, { children: createElement(component, props) })); //#endregion //#region src/stories/base/renderRender.tsx var renderRender_default$1 = (render) => story(() => render); //#endregion //#region src/components/base/element.ts /** * Base rocketstyle element used internally by rocketstories for * layout wrappers and story containers. Applies a default Arial * font family via the theme and renders unistyle-based CSS. */ var element_default = rocketstyle()({ component: Element, name: "element" }).theme({ fontFamily: "Arial" }).styles((css) => css` ${({ $rocketstyle }) => { return css` ${styles({ theme: $rocketstyle, css, rootSize: 16 })}; `; }}; `); //#endregion //#region src/components/base/Heading.ts /** * Heading component built on the base element. Renders as an h1 tag * and provides two size variants: level1 (20px) and level2 (16px). * Used for labeling dimension items and pseudo-state groups in stories. */ var Heading_default = element_default.attrs({ tag: "h1", block: true }).sizes({ level1: { fontSize: 20 }, level2: { marginTop: 0, fontSize: 16 } }); //#endregion //#region src/components/NotFound.tsx /** * Empty placeholder component displayed when a requested dimension * has no values or the story cannot be rendered. */ const Wrapper = config.styled("div")` display: flex; font-size: 32px; `; const component$1 = () => /* @__PURE__ */ jsx(Wrapper, { children: "Nothing here" }); component$1.displayName = "@vitus-labs/rocketstories/Empty"; //#endregion //#region src/utils/code.ts const parseProps = (props) => Object.entries(props).reduce((acc, [key, value]) => { if (value === null) return acc; const valueType = typeof value; if ([ "string", "number", "boolean", "bigint" ].includes(valueType)) return { ...acc, [key]: value }; if (Array.isArray(value)) return { ...acc, [key]: value }; if (valueType === "object") { const type = get(value, "type"); const options = get(value, "options"); const defaultValue = get(value, "value"); if (type && options && defaultValue) return { ...acc, [key]: defaultValue || options }; return { ...acc, [key]: value }; } return acc; }, {}); const stringifyArray = (props) => { let result = "["; const arrayLength = props.length; result += props.reduce((acc, value, i) => { if (Array.isArray(value)) acc += `${stringifyArray(value)}`; else if (typeof value === "object" && value !== null) acc += `${stringifyObject(value)}`; else if (["number", "string"].includes(typeof value)) acc += `"${value}"`; else acc += `${value}`; if (arrayLength !== i + 1) acc += `, `; return acc; }, ""); result += "]"; return result; }; const stringifyObject = (props) => { let result = "{ "; const propsArray = Object.entries(props); const arrayLength = propsArray.length; result += propsArray.reduce((acc, [key, value], i) => { if (Array.isArray(value)) acc += `${key}: ${value}`; else if (typeof value === "object" && value !== null) acc += `${key}: ${stringifyObject(value)}`; else if (["string"].includes(typeof value)) acc += `${key}: "${value}"`; else acc += `${key}: ${value}`; if (arrayLength !== i + 1) acc += `, `; return acc; }, ""); result += " }"; return result; }; const stringifyProps = (props) => { const parsedProps = parseProps(props); const arrayProps = Object.entries(parsedProps); const arrayLength = arrayProps.length; return arrayProps.reduce((acc, [key, value], i) => { if (typeof value === "boolean") if (value === true) acc += `${key}`; else acc += `${key}=${value}`; else if (["string", "number"].includes(typeof value) || value === null || value === void 0) acc += `${key}="${value}"`; else if (Array.isArray(value)) acc += `${key}={${stringifyArray(value)}}`; else if (typeof value === "object" && value !== null) acc += `${key}={${stringifyObject(value)}}`; if (arrayLength !== i + 1) acc += " "; return acc; }, ""); }; const parseComponentName = (name) => { const helper = name.split("/"); if (helper.length > 1) return helper[helper.length - 1]; return name; }; const createJSXCode = (name, props) => `<${parseComponentName(name)} ${stringifyProps(props)} />`; const createJSXCodeArray = (name, props, dimensionName, dimensions, useBooleans, isMultiKey) => { if (!dimensions) return `// nothing here`; let result = ""; const finalProps = { ...props }; delete finalProps[dimensionName]; result += Object.keys(dimensions).reduce((acc, key) => { acc += createJSXCode(name, { [dimensionName]: isMultiKey ? [key] : key, ...finalProps }); acc += `\n`; return acc; }, ""); if (useBooleans) { result += `\n\n`; result += `// Or alternatively use boolean ${dimensionName} props (${Object.keys(dimensions).toString()})`; result += `\n`; result += Object.keys(dimensions).reduce((acc, key) => { acc += createJSXCode(name, { [key]: true, ...finalProps }); acc += `\n`; return acc; }, ""); } return result; }; const addBooleanCodeComment = (values) => { let result = `\n\n`; result += `// Or alternatively use boolean props (e.g. ${values})`; result += `\n`; return result; }; const generateMainJSXCode = ({ name, props, dimensions, booleanDimensions }) => { let result = createJSXCode(name, { ...dimensions, ...props }); if (booleanDimensions) { result += addBooleanCodeComment(Object.keys(booleanDimensions)); result += createJSXCode(name, { ...booleanDimensions, ...props }); } return result; }; //#endregion //#region src/stories/rocketstories/renderDimension/context.tsx /** * React context for the dimension story renderer. Provides the wrapped * rocketstyle component reference to nested Item and PseudoList components * so they can render it without prop-drilling. */ const context = createContext({}); const useContext$1 = () => useContext(context); const ContextProvider = context.Provider; const Provider = ({ children, ...props }) => /* @__PURE__ */ jsx(ContextProvider, { value: props, children }); //#endregion //#region src/stories/rocketstories/renderDimension/components/Item.tsx /** * Renders a single dimension value item within a dimension story. * Retrieves the component from context and renders it with the given * props, optionally prefixed by a level2 Heading showing the item title. */ const Item = ({ title, ...props }) => { const { component } = useContext$1(); return /* @__PURE__ */ jsxs("div", { children: [title && /* @__PURE__ */ jsx(Heading_default, { level2: true, label: title }), createElement(component, props)] }); }; //#endregion //#region src/stories/rocketstories/renderDimension/components/PseudoList.tsx const pseudo = [ "base", "hover", "pressed", "active" ]; const component = ({ itemProps }) => /* @__PURE__ */ jsx(Fragment, { children: pseudo.map((item) => { const pseudoProps = { [item]: true }; return /* @__PURE__ */ jsx(Item, { title: item, ...itemProps, ...pseudoProps }, item); }) }); //#endregion //#region src/stories/rocketstories/renderDimension/index.tsx /** * Renders a dimension story for a rocketstyle component. Iterates over all * values of the specified dimension and renders each as an Item (or a * PseudoList when pseudo-state visualization is enabled). Generates * corresponding JSX code snippets and Storybook controls. */ const renderDimension = (dimension, { name, component: component$2, attrs = {}, controls, storyOptions = {}, ignore = [], theme = {} }) => { const statics = component$2.getStaticDimensions(theme); const defaultAttrs = component$2.getDefaultAttrs(attrs, theme, "light"); const { dimensions, useBooleans, multiKeys } = statics; const finalAttrs = { ...defaultAttrs, ...attrs }; const currentDimension = dimensions[dimension]; const isMultiKey = !!multiKeys[dimension]; if (isEmpty(currentDimension)) return component$1; const createdControls = createControls(controls); const dimensionControls = convertDimensionsToControls(statics); const storybookControls = makeStorybookControls({ ...getDefaultVitusLabsControls(component$2), ...createdControls, ...dimensionControls }, defaultAttrs); const hasPseudo = storyOptions.pseudo === true; let story = `This story renders all _options_ of the **${dimension}** dimension. `; if (hasPseudo) story += "Including `pseudo` states."; const revertedDirection = storyOptions.direction === "rows" ? "inline" : "rows"; const hasStoryOptions = !isEmpty(storyOptions); const WrapElement = hasStoryOptions ? Element : Fragment$1; const wrapperProps = hasStoryOptions ? { block: true, contentDirection: hasPseudo ? revertedDirection : storyOptions.direction, contentAlignX: storyOptions.alignX, contentAlignY: storyOptions.alignY, style: { gap: storyOptions.gap } } : {}; const innerGap = storyOptions.gap ? storyOptions.gap / 2 : 0; const Enhanced = (props) => /* @__PURE__ */ jsx(WrapElement, { ...wrapperProps, children: Object.keys(currentDimension).map((item) => { const shouldBeIgnored = ignore.includes(item); const key = `${dimension}-${item}`; const storyProps = { "data-story": key, contentDirection: revertedDirection, contentAlignX: storyOptions.alignX, contentAlignY: storyOptions.alignY, style: { gap: innerGap } }; if (shouldBeIgnored) return null; if (storyOptions.pseudo === true) return /* @__PURE__ */ jsxs(WrapElement, { contentDirection: "rows", contentAlignY: "top", children: [/* @__PURE__ */ jsx(Heading_default, { level1: true, label: item.charAt(0).toUpperCase() + item.slice(1) }), /* @__PURE__ */ jsx(WrapElement, { ...storyProps, contentDirection: storyOptions.direction, contentAlignY: "top", children: /* @__PURE__ */ jsx(Provider, { component: component$2, children: /* @__PURE__ */ jsx(component, { itemProps: { ...props, [dimension]: isMultiKey ? [item] : item } }) }) })] }, key); return /* @__PURE__ */ jsx(WrapElement, { ...storyProps, children: /* @__PURE__ */ jsx(Provider, { component: component$2, children: /* @__PURE__ */ jsx(Item, { ...props, [dimension]: isMultiKey ? [item] : item }) }) }, key); }) }); Enhanced.args = finalAttrs; Enhanced.argTypes = { ...storybookControls, ...disableDimensionControls(dimensions, dimension) }; Enhanced.parameters = { docs: { description: { story }, source: { code: createJSXCodeArray(name, pick(finalAttrs, Object.keys(attrs)), dimension, currentDimension, useBooleans, isMultiKey) } } }; return Enhanced; }; //#endregion //#region src/utils/dimensions.ts const extractDefaultBooleanProps = ({ dimensions, multiKeys, useBooleans }) => { if (!useBooleans) return null; return Object.entries(dimensions).reduce((acc, [key, value]) => { if (!multiKeys[key]) { const propName = Object.keys(value)[0]; return { ...acc, [propName]: true }; } return acc; }, {}); }; //#endregion //#region src/internal/RocketStoryHoc.tsx /** * Higher-order component factory for rocketstyle components. * Extracts dimension metadata and default attributes from the component's * static configuration, auto-generates Storybook controls for dimensions * and known Vitus Labs props, and attaches args/argTypes/parameters to the story. */ const rocketStory = (WrappedComponent) => ({ name, component, attrs, controls, theme = {} }) => { const statics = component.getStaticDimensions(theme); const defaultAttrs = component.getDefaultAttrs(attrs, theme, "light"); const { dimensions, useBooleans, multiKeys } = statics; const finalAttrs = { ...defaultAttrs, ...attrs }; const createdControls = createControls(controls); const dimensionControls = convertDimensionsToControls(statics); const vitusLabsControls = getDefaultVitusLabsControls(component); const storybookControls = makeStorybookControls({ ...dimensionControls, ...vitusLabsControls, ...createdControls }, defaultAttrs); const story = `This story is a showcase of a _${name}_ component`; const Enhanced = WrappedComponent(component); Enhanced.args = finalAttrs; Enhanced.argTypes = { ...storybookControls, ...disableDimensionControls(dimensions) }; Enhanced.parameters = { docs: { description: { story }, source: { code: generateMainJSXCode({ name, dimensions: {}, props: pick(finalAttrs, Object.keys(attrs)), booleanDimensions: extractDefaultBooleanProps({ dimensions, multiKeys, useBooleans }) }) } } }; return Enhanced; }; //#endregion //#region src/stories/rocketstories/renderList.tsx /** * Renders a list story for a rocketstyle component. * Accepts list configuration and wraps the component through RocketStoryHoc, * rendering it inside a Vitus Labs List element with auto-generated controls. */ const LooseList = List; var renderList_default = (list) => rocketStory((component) => (props) => /* @__PURE__ */ jsx(LooseList, { rootElement: false, itemProps: props, ...list, component })); //#endregion //#region src/stories/rocketstories/renderMain.tsx /** * Renders the main (default) story for a rocketstyle component. * Wraps the component through RocketStoryHoc to auto-generate dimension * controls and Vitus Labs-specific argTypes, then renders it with createElement. */ var renderMain_default = rocketStory((component) => (props) => /* @__PURE__ */ jsx(Fragment, { children: createElement(component, props) })); //#endregion //#region src/stories/rocketstories/renderRender.tsx var renderRender_default = (render) => rocketStory(() => render); //#endregion //#region src/rocketstories.tsx /** * Clones the current configuration, merges in new options, and returns a * fresh IRocketStories instance for immutable chaining. * * Component-swap reset: when `options.component` differs from the current * `defaultOptions.component`, the prior `attrs` are dropped — they were * tailored to the previous component's prop shape, and applying them to a * different component silently leaks invalid props onto the rendered * output. Story-level config (`storyOptions`, `controls`, `decorators`) * is preserved because it's about how stories render, not about the * component's prop shape. Mirrors the same fix in `@vitus-labs/rocketstyle`'s * `cloneAndEnhance` (PR #200). * * Callers who want to preserve attrs across a component swap must * re-chain explicitly: * * stories.replaceComponent(NewComp).attrs(sharedAttrs) */ const cloneAndEnhance = (defaultOptions, options) => { const componentChanged = options.component != null && options.component !== defaultOptions.component; const result = { ...defaultOptions, name: defaultOptions.name || options.name, prefix: options.prefix || defaultOptions.prefix, component: options.component || defaultOptions.component, attrs: componentChanged ? { ...options.attrs } : { ...defaultOptions.attrs, ...options.attrs }, storyOptions: { ...defaultOptions.storyOptions, ...options.storyOptions }, controls: { ...defaultOptions.controls, ...options.controls }, decorators: [...defaultOptions.decorators || [], ...options.decorators || []] }; const finalName = result.name || result.component.displayName || get(result.component, "name"); const finalStoryName = result.prefix ? `${result.prefix}/${finalName}` : finalName; return createRocketStories({ ...result, name: finalStoryName }); }; const createRocketStories = (options) => { const isRocket = isRocketComponent(options.component); return { CONFIG: options, main: () => isRocket ? renderMain_default({ ...options, component: options.component }) : renderMain_default$1(options), dimension: (dimension, params = {}) => { if (!isRocket) return null; const { ignore = [] } = params; return renderDimension(dimension, { ...options, component: options.component, ignore }); }, render: (renderer) => isRocket ? renderRender_default(renderer)({ ...options, component: options.component }) : renderRender_default$1(renderer)({ ...options, component: options.component }), list: (params) => isRocket ? renderList_default(params)({ ...options, component: options.component }) : renderList_default$1(params)({ ...options, component: options.component }), init: { component: options.component, title: options.name, decorators: options.decorators }, storyOptions: (storyOptions) => cloneAndEnhance(options, { storyOptions }), controls: (controls) => cloneAndEnhance(options, { controls }), config: ({ component, storyOptions, prefix, name, decorators }) => cloneAndEnhance(options, { component, storyOptions, prefix, name, decorators }), attrs: (attrs) => cloneAndEnhance(options, { attrs }), replaceComponent: (component) => cloneAndEnhance(options, { component }), decorators: (decorators) => cloneAndEnhance(options, { decorators }) }; }; //#endregion //#region src/utils/theme.ts let theme = {}; const getTheme = () => theme; const setTheme = (value) => { theme = value; }; //#endregion //#region src/init.ts /** @see {@link Init} */ const init = ({ decorators = [], storyOptions = {}, theme, ...rest }) => { if (theme) setTheme(theme); return (component) => rocketstories(component, { decorators, storyOptions, theme, ...rest }); }; /** @see {@link Rocketstories} */ const rocketstories = (component, options = {}) => { const { decorators = [], storyOptions = {}, theme } = options; return createRocketStories({ component, name: component.displayName || component.name, attrs: {}, storyOptions: { gap: 16, direction: "rows", alignY: "top", alignX: "left", ...storyOptions }, decorators, controls: {}, theme: theme ?? getTheme() }); }; //#endregion //#region src/index.ts var src_default = init; //#endregion export { src_default as default, init, rocketstories }; //# sourceMappingURL=index.js.map