@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
JavaScript
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