@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,388 lines (1,357 loc) • 65.8 kB
JavaScript
import { isEmpty, config, pick, omit, compose, render, context as context$2, merge, set, get, HTML_TAGS } from '@vitus-labs/core';
import React, { createContext, forwardRef, useMemo, useRef, useImperativeHandle, useContext as useContext$1, useState, useCallback, createElement, Fragment } from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { Element, List } from '@vitus-labs/elements';
import { styles } from '@vitus-labs/unistyle';
const PSEUDO_KEYS = ['hover', 'active', 'focus', 'pressed'];
const PSEUDO_META_KEYS = ['disabled', 'readOnly'];
const THEME_MODES = {
light: true,
dark: true,
};
const THEME_MODES_INVERSED = {
dark: 'light',
light: 'dark',
};
const CONFIG_KEYS = [
'provider',
'consumer',
'DEBUG',
'name',
'component',
'inversed',
'passProps',
'styled',
];
const STYLING_KEYS = ['theme', 'styles'];
const STATIC_KEYS = [...STYLING_KEYS, 'compose'];
const ALL_RESERVED_KEYS = [
...Object.keys(THEME_MODES),
...CONFIG_KEYS,
...STATIC_KEYS,
'attrs',
];
const context$1 = createContext({});
const useLocalContext = (consumer) => {
const ctx = consumer ? useContext$1(context$1) : {};
const result = consumer ? consumer((callback) => callback(ctx)) : {};
return { pseudo: {}, ...result };
};
const LocalProvider = context$1.Provider;
const usePseudoState = ({ onBlur, onFocus, onMouseDown, onMouseEnter, onMouseLeave, onMouseUp, }) => {
const [hover, setHover] = useState(false);
const [focus, setFocus] = useState(false);
const [pressed, setPressed] = useState(false);
const handleOnMouseEnter = useCallback((e) => {
setHover(true);
if (onMouseEnter)
onMouseEnter(e);
}, [onMouseEnter]);
const handleOnMouseLeave = useCallback((e) => {
setHover(false);
setPressed(false);
if (onMouseLeave)
onMouseLeave(e);
}, [onMouseLeave]);
const handleOnMouseDown = useCallback((e) => {
setPressed(true);
if (onMouseDown)
onMouseDown(e);
}, [onMouseDown]);
const handleOnMouseUp = useCallback((e) => {
setPressed(false);
if (onMouseUp)
onMouseUp(e);
}, [onMouseUp]);
const handleOnFocus = useCallback((e) => {
setFocus(true);
if (onFocus)
onFocus(e);
}, [onFocus]);
const handleOnBlur = useCallback((e) => {
setFocus(false);
if (onBlur)
onBlur(e);
}, [onBlur]);
return {
state: {
hover,
focus,
pressed,
},
events: {
onMouseEnter: handleOnMouseEnter,
onMouseLeave: handleOnMouseLeave,
onMouseDown: handleOnMouseDown,
onMouseUp: handleOnMouseUp,
onFocus: handleOnFocus,
onBlur: handleOnBlur,
},
};
};
const useRocketstyleRef = ({ $rocketstyleRef, ref }) => {
const internalRef = useRef(null);
useImperativeHandle($rocketstyleRef, () => internalRef.current);
useImperativeHandle(ref, () => internalRef.current);
return internalRef;
};
const useThemeAttrs = ({ inversed }) => {
const { theme = {}, mode: ctxMode = 'light', isDark: ctxDark, } = useContext$1(context$2) || {};
const mode = inversed ? THEME_MODES_INVERSED[ctxMode] : ctxMode;
const isDark = inversed ? !ctxDark : ctxDark;
const isLight = !isDark;
return { theme, mode, isDark, isLight };
};
const RocketStyleProviderComponent = (WrappedComponent) => forwardRef(({ onMouseEnter, onMouseLeave, onMouseUp, onMouseDown, onFocus, onBlur, $rocketstate, ...props }, ref) => {
// pseudo hook to detect states hover / pressed / focus
const pseudo = usePseudoState({
onMouseEnter,
onMouseLeave,
onMouseUp,
onMouseDown,
onFocus,
onBlur,
});
const updatedState = useMemo(() => ({
...$rocketstate,
pseudo: { ...$rocketstate.pseudo, ...pseudo.state },
}), [$rocketstate, pseudo]);
return (React.createElement(LocalProvider, { value: updatedState },
React.createElement(WrappedComponent, { ...props, ...pseudo.events, ref: ref, "$rocketstate": updatedState })));
});
class ThemeManager {
baseTheme = new WeakMap();
dimensionsThemes = new WeakMap();
modeBaseTheme = { light: new WeakMap(), dark: new WeakMap() };
modeDimensionTheme = { light: new WeakMap(), dark: new WeakMap() };
}
/* eslint-disable no-param-reassign */
const removeUndefinedProps = (props) => Object.keys(props).reduce((acc, key) => {
const currentValue = props[key];
if (currentValue !== undefined)
return { ...acc, [key]: currentValue };
return acc;
}, {});
const pickStyledAttrs = (props, keywords) => Object.keys(props).reduce((acc, key) => {
if (keywords[key] && props[key])
acc[key] = props[key];
return acc;
}, {});
const calculateChainOptions = (options) => (args) => {
const result = {};
if (isEmpty(options))
return result;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return options.reduce((acc, item) => Object.assign(acc, item(...args)), {});
};
const calculateStylingAttrs = ({ useBooleans, multiKeys }) => ({ props, dimensions }) => {
const result = {};
// (1) find dimension keys values & initialize
// object with possible options
Object.keys(dimensions).forEach((item) => {
const pickedProp = props[item];
const valueTypes = ['number', 'string'];
// if the property is multi key, allow assign array as well
if (multiKeys && multiKeys[item] && Array.isArray(pickedProp)) {
result[item] = pickedProp;
}
// assign when it's only a string or number otherwise it's considered
// as invalid param
else if (valueTypes.includes(typeof pickedProp)) {
result[item] = pickedProp;
}
else {
result[item] = undefined;
}
});
// (2) if booleans are being used let's find the rest
if (useBooleans) {
const propsKeys = Object.keys(props).reverse();
Object.entries(result).forEach(([key, value]) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const isMultiKey = multiKeys[key];
// when value in result is not assigned yet
if (!value) {
let newDimensionValue;
const keywords = Object.keys(dimensions[key]);
if (isMultiKey) {
newDimensionValue = propsKeys.filter((key) => keywords.includes(key));
}
else {
// reverse props to guarantee the last one will have
// a priority over previous ones
newDimensionValue = propsKeys.find((key) => {
if (keywords.includes(key) && props[key])
return key;
return false;
});
}
result[key] = newDimensionValue;
}
});
}
return result;
};
/* eslint-disable no-underscore-dangle */
const rocketStyleHOC = ({ inversed, attrs, priorityAttrs }) => {
// --------------------------------------------------
// .attrs(...)
// first we need to calculate final props which are
// being returned by using `attr` chaining method
// --------------------------------------------------
const calculateAttrs = calculateChainOptions(attrs);
const calculatePriorityAttrs = calculateChainOptions(priorityAttrs);
const Enhanced = (WrappedComponent) => forwardRef((props, ref) => {
const { theme, mode, isDark, isLight } = useThemeAttrs({
inversed,
});
const callbackParams = [theme, { render, mode, isDark, isLight }];
// --------------------------------------------------
// remove undefined props not to override potential default props
// only props with value (e.g. `null`) should override default props
// --------------------------------------------------
const filteredProps = removeUndefinedProps(props);
const prioritizedAttrs = calculatePriorityAttrs([
filteredProps,
...callbackParams,
]);
const finalAttrs = calculateAttrs([
{
...prioritizedAttrs,
...filteredProps,
},
...callbackParams,
]);
return (React.createElement(WrappedComponent, { "$rocketstyleRef": ref, ...prioritizedAttrs, ...finalAttrs, ...filteredProps }));
});
return Enhanced;
};
const createStaticsChainingEnhancers = ({ context, dimensionKeys, func, options, }) => {
const keys = [...dimensionKeys, ...STATIC_KEYS];
keys.forEach((item) => {
// eslint-disable-next-line no-param-reassign
context[item] = (props) => func(options, { [item]: props });
});
};
const createStaticsEnhancers = ({ context, options, }) => {
if (!isEmpty(options)) {
Object.assign(context, options);
}
};
/* eslint-disable import/prefer-default-export */
const removeNullableValues = (obj) => Object.entries(obj)
.filter(([, v]) => v != null && v !== false)
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
// --------------------------------------------------------
// Remove All Empty Values
// --------------------------------------------------------
// type RemoveAllEmptyValues = (obj: Record<string, any>) => Record<string, any>
// export const removeAllEmptyValues: RemoveAllEmptyValues = (obj) =>
// Object.entries(obj)
// .filter(([, v]) => v != null)
// .reduce(
// (acc, [k, v]) => ({
// ...acc,
// [k]: typeof v === 'object' ? removeAllEmptyValues(v) : v,
// }),
// {}
// )
const isValidKey = (value) => value !== undefined && value !== null && value !== false;
const isMultiKey = (value) => {
if (typeof value === 'object' && value !== null)
return [true, get(value, 'propName')];
return [false, value];
};
const getDimensionsMap = ({ themes, useBooleans }) => {
const result = {
keysMap: {},
keywords: {},
};
if (isEmpty(themes))
return result;
return Object.entries(themes).reduce((accumulator, [key, value]) => {
const { keysMap, keywords } = accumulator;
keywords[key] = true;
Object.entries(value).forEach(([itemKey, itemValue]) => {
if (!isValidKey(itemValue))
return;
if (useBooleans) {
keywords[itemKey] = true;
}
set(keysMap, [key, itemKey], true);
});
return accumulator;
}, result);
};
const getKeys = (obj) => Object.keys(obj);
const getValues = (obj) => Object.values(obj);
const getDimensionsValues = (obj) => getValues(obj).map((item) => {
if (typeof item === 'object') {
return item.propName;
}
return item;
});
const getMultipleDimensions = (obj) => getValues(obj).reduce((accumulator, value) => {
if (typeof value === 'object') {
// eslint-disable-next-line no-param-reassign
if (value.multi === true)
accumulator[value.propName] = true;
}
return accumulator;
}, {});
/* eslint-disable no-param-reassign */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// --------------------------------------------------------
// Theme Mode Callback
// --------------------------------------------------------
const themeModeCallback = (light, dark) => (mode) => {
if (!mode || mode === 'light')
return light;
return dark;
};
const isModeCallback = (value) => typeof value === 'function' &&
//@ts-ignore
value.toString() === themeModeCallback().toString();
const getThemeFromChain = (options, theme) => {
const result = {};
if (!options || isEmpty(options))
return result;
return options.reduce((acc, item) => merge(acc, item(theme, themeModeCallback, config.css)), result);
};
const getDimensionThemes = (theme, options) => {
const result = {};
if (isEmpty(options.dimensions))
return result;
return Object.entries(options.dimensions).reduce((acc, [key, value]) => {
const [, dimension] = isMultiKey(value);
const helper = options[key];
if (Array.isArray(helper) && helper.length > 0) {
const finalDimensionThemes = getThemeFromChain(helper, theme);
// eslint-disable-next-line no-param-reassign
acc[dimension] = removeNullableValues(finalDimensionThemes);
}
return acc;
}, result);
};
const getTheme$1 = ({ rocketstate, themes, baseTheme }) => {
// generate final theme which will be passed to styled component
let finalTheme = { ...baseTheme };
Object.entries(rocketstate).forEach(([key, value]) => {
const keyTheme = themes[key];
if (Array.isArray(value)) {
value.forEach((item) => {
finalTheme = merge({}, finalTheme, keyTheme[item]);
});
}
else {
finalTheme = merge({}, finalTheme, keyTheme[value]);
}
});
return finalTheme;
};
const getThemeByMode = (object, mode) => Object.keys(object).reduce((acc, key) => {
const value = object[key];
if (typeof value === 'object' && value !== null) {
acc[key] = getThemeByMode(value, mode);
}
else if (isModeCallback(value)) {
acc[key] = value(mode);
}
else {
acc[key] = value;
}
return acc;
}, {});
const chainOptions = (opts, defaultOpts = []) => {
const result = [...defaultOpts];
if (typeof opts === 'function')
result.push(opts);
else if (typeof opts === 'object')
result.push(() => opts);
return result;
};
const chainOrOptions = (keys, opts, defaultOpts) => keys.reduce((acc, item) => ({ ...acc, [item]: opts[item] || defaultOpts[item] }), {});
const chainReservedKeyOptions = (keys, opts, defaultOpts) => keys.reduce((acc, item) => ({
...acc,
[item]: chainOptions(opts[item], defaultOpts[item]),
}), {});
const calculateHocsFuncs = (options = {}) => Object.values(options)
.filter((item) => typeof item === 'function')
.reverse();
/* eslint-disable import/prefer-default-export */
const calculateStyles = (styles) => {
if (!styles)
return [];
return styles.map((item) => item(config.css));
};
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-underscore-dangle */
const cloneAndEnhance = (defaultOpts, opts) =>
// @ts-ignore
rocketComponent({
...defaultOpts,
attrs: chainOptions(opts.attrs, defaultOpts.attrs),
filterAttrs: [
...(defaultOpts.filterAttrs ?? []),
...(opts.filterAttrs ?? []),
],
priorityAttrs: chainOptions(opts.priorityAttrs, defaultOpts.priorityAttrs),
statics: { ...defaultOpts.statics, ...opts.statics },
compose: { ...defaultOpts.compose, ...opts.compose },
...chainOrOptions(CONFIG_KEYS, opts, defaultOpts),
...chainReservedKeyOptions([...defaultOpts.dimensionKeys, ...STYLING_KEYS], opts, defaultOpts),
});
// --------------------------------------------------------
// styleComponent
// helper function which allows function chaining
// always returns a valid React component with static functions
// assigned, so it can be even rendered as a valid component
// or styles can be extended via its statics
// --------------------------------------------------------
// @ts-ignore
const rocketComponent = (options) => {
const { component, styles, DEBUG } = options;
const { styled } = config;
const _calculateStylingAttrs = calculateStylingAttrs({
multiKeys: options.multiKeys,
useBooleans: options.useBooleans,
});
const componentName = options.name ?? options.component.displayName ?? options.component.name;
// create styled component with all options.styles if available
const STYLED_COMPONENT = component.IS_ROCKETSTYLE ?? options.styled !== true
? component
: styled(component) `
${calculateStyles(styles)};
`;
// --------------------------------------------------------
// COMPONENT - Final component to be rendered
// --------------------------------------------------------
const RenderComponent = options.provider
? RocketStyleProviderComponent(STYLED_COMPONENT)
: STYLED_COMPONENT;
// --------------------------------------------------------
// THEME - Cached & Calculated theme(s)
// --------------------------------------------------------
const ThemeManager$1 = new ThemeManager();
// --------------------------------------------------------
// COMPOSE - high-order components
// --------------------------------------------------------
const hocsFuncs = [
rocketStyleHOC(options),
...calculateHocsFuncs(options.compose),
];
// --------------------------------------------------------
// ENHANCED COMPONENT (returned component)
// --------------------------------------------------------
// .attrs() chaining option is calculated in HOC and passed as props already
// @ts-ignore
// eslint-disable-next-line react/display-name
const EnhancedComponent = forwardRef(({ $rocketstyleRef, // it's forwarded from HOC which is always on top of all hocs
...props }, ref) => {
// --------------------------------------------------
// handle refs
// (1) one is passed from inner HOC - $rocketstyleRef
// (2) second one is used to be used directly (e.g. inside hocs)
// --------------------------------------------------
const internalRef = useRocketstyleRef({ $rocketstyleRef, ref });
// --------------------------------------------------
// hover - focus - pressed state passed via context from parent component
// --------------------------------------------------
const localCtx = useLocalContext(options.consumer);
// --------------------------------------------------
// general theme and theme mode dark / light passed in context
// --------------------------------------------------
const { theme, mode } = useThemeAttrs(options);
// --------------------------------------------------
// calculate themes for all defined styling dimensions
// .theme(...) + defined dimensions like .states(...), .sizes(...), etc.
// --------------------------------------------------
// --------------------------------------------------
// BASE / DEFAULT THEME Object
// --------------------------------------------------
const baseTheme = useMemo(() => {
const helper = ThemeManager$1.baseTheme;
if (!helper.has(theme)) {
helper.set(theme, getThemeFromChain(options.theme, theme));
}
return helper.get(theme);
},
// recalculate this only when theme mode changes dark / light
[theme]);
// --------------------------------------------------
// DIMENSION(S) THEMES Object
// --------------------------------------------------
const themes = useMemo(() => {
const helper = ThemeManager$1.dimensionsThemes;
if (!helper.has(theme)) {
helper.set(theme, getDimensionThemes(theme, options));
}
return helper.get(theme);
},
// recalculate this only when theme object changes
[theme]);
// --------------------------------------------------
// BASE / DEFAULT MODE THEME Object
// --------------------------------------------------
const currentModeBaseTheme = useMemo(() => {
const helper = ThemeManager$1.modeBaseTheme[mode];
if (!helper.has(baseTheme)) {
helper.set(baseTheme, getThemeByMode(baseTheme, mode));
}
return helper.get(baseTheme);
},
// recalculate this only when theme mode changes dark / light
[mode, baseTheme]);
// --------------------------------------------------
// DIMENSION(S) MODE THEMES Object
// --------------------------------------------------
const currentModeThemes = useMemo(() => {
const helper = ThemeManager$1.modeDimensionTheme[mode];
if (!helper.has(themes)) {
helper.set(themes, getThemeByMode(themes, mode));
}
return helper.get(themes);
},
// recalculate this only when theme mode changes dark / light
[mode, themes]);
// --------------------------------------------------
// calculate reserved Keys defined in dimensions as styling keys
// there is no need to calculate this each time - keys are based on
// dimensions definitions
// --------------------------------------------------
const { keysMap: dimensions, keywords: reservedPropNames } = useMemo(() => getDimensionsMap({
themes,
useBooleans: options.useBooleans,
}), [themes]);
const RESERVED_STYLING_PROPS_KEYS = useMemo(() => Object.keys(reservedPropNames), [reservedPropNames]);
// --------------------------------------------------
// get final props which are (latest has the highest priority):
// (1) merged styling from context,
// (2) `attrs` chaining method, and from
// (3) passing them directly to component
// --------------------------------------------------
const { pseudo, ...mergeProps } = {
...localCtx,
...props,
};
// --------------------------------------------------
// pseudo rocket state
// calculate final component pseudo state including pseudo state
// from props and override by pseudo props from context
// --------------------------------------------------
const pseudoRocketstate = {
...pseudo,
...pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS]),
};
// --------------------------------------------------
// rocketstate
// calculate final component state including pseudo state
// passed as $rocketstate prop
// --------------------------------------------------
const rocketstate = _calculateStylingAttrs({
props: pickStyledAttrs(mergeProps, reservedPropNames),
dimensions,
});
const finalRocketstate = { ...rocketstate, pseudo: pseudoRocketstate };
// --------------------------------------------------
// rocketstyle
// calculated (based on styling props) final theme which will be passed
// to our styled component
// passed as $rocketstyle prop
// --------------------------------------------------
const rocketstyle = getTheme$1({
rocketstate,
themes: currentModeThemes,
baseTheme: currentModeBaseTheme,
});
// --------------------------------------------------
// final props
// final props passed to WrappedComponent
// excluding: styling props
// including: $rocketstyle, $rocketstate
// --------------------------------------------------
const finalProps = {
// this removes styling state from props and passes its state
// under rocketstate key only
...omit(mergeProps, [
...RESERVED_STYLING_PROPS_KEYS,
...PSEUDO_KEYS,
...options.filterAttrs,
]),
// if enforced to pass styling props, we pass them directly
...(options.passProps ? pick(mergeProps, options.passProps) : {}),
ref: ref ?? $rocketstyleRef ? internalRef : undefined,
// state props passed to styled component only, therefore the `$` symbol
$rocketstyle: rocketstyle,
$rocketstate: finalRocketstate,
};
if (DEBUG && process.env.NODE_ENV !== 'production') {
console.log('[Rocketstyle] Debug mode enabled');
console.log(`component ${componentName}`);
console.log(finalProps);
}
// all the development stuff injected
if (process.env.NODE_ENV !== 'production') {
finalProps['data-rocketstyle'] = componentName;
}
return React.createElement(RenderComponent, { ...finalProps });
});
// ------------------------------------------------------
// This will hoist and generate dynamically next static methods
// for all dimensions available in configuration
// ------------------------------------------------------
const RocketComponent = compose(...hocsFuncs)(EnhancedComponent);
RocketComponent.IS_ROCKETSTYLE = true;
RocketComponent.displayName = componentName;
hoistNonReactStatics(RocketComponent, options.component);
// ------------------------------------------------------
// enhance for chaining methods
// ------------------------------------------------------
createStaticsChainingEnhancers({
context: RocketComponent,
dimensionKeys: options.dimensionKeys,
func: cloneAndEnhance,
options,
});
// ------------------------------------------------------
RocketComponent.IS_ROCKETSTYLE = true;
RocketComponent.displayName = componentName;
RocketComponent.meta = {};
// ------------------------------------------------------
// ------------------------------------------------------
// enhance for statics
// ------------------------------------------------------
createStaticsEnhancers({
context: RocketComponent.meta,
options: options.statics,
});
// @ts-ignore
RocketComponent.attrs = (attrs, { priority, filter } = {}) => {
const result = {};
if (filter) {
result.filterAttrs = filter;
}
if (priority) {
result.priorityAttrs = attrs;
return cloneAndEnhance(options, result);
}
result.attrs = attrs;
return cloneAndEnhance(options, result);
};
// @ts-ignore
RocketComponent.config = (opts = {}) => {
const result = pick(opts, CONFIG_KEYS);
// @ts-ignore
return cloneAndEnhance(options, result);
};
RocketComponent.statics = (opts) =>
// @ts-ignore
cloneAndEnhance(options, { statics: opts });
RocketComponent.getStaticDimensions = (theme) => {
const themes = getDimensionThemes(theme, options);
const { keysMap, keywords } = getDimensionsMap({
themes,
useBooleans: options.useBooleans,
});
return {
dimensions: keysMap,
keywords,
useBooleans: options.useBooleans,
multiKeys: options.multiKeys,
};
};
RocketComponent.getDefaultAttrs = (props, theme, mode) => calculateChainOptions(options.attrs)([
props,
theme,
{
render,
mode,
isDark: mode === 'light',
isLight: mode === 'dark',
},
]);
return RocketComponent;
};
const DEFAULT_DIMENSIONS = {
states: 'state',
sizes: 'size',
variants: 'variant',
multiple: {
propName: 'multiple',
multi: true,
},
};
// @ts-nocheck
const rocketstyle$1 = ({ dimensions = DEFAULT_DIMENSIONS, useBooleans = true } = {}) => ({ name, component }) => {
// --------------------------------------------------------
// handle ERRORS in development mode
// --------------------------------------------------------
if (process.env.NODE_ENV !== 'production') {
const errors = {};
if (!component) {
errors.component = 'Parameter `component` is missing in params!';
}
if (!name) {
errors.name = 'Parameter `name` is missing in params!';
}
if (isEmpty(dimensions)) {
errors.dimensions = 'Parameter `dimensions` is missing in params!';
}
else {
const definedDimensions = getKeys(dimensions);
const invalidDimension = ALL_RESERVED_KEYS.some((item) => definedDimensions.includes(item));
if (invalidDimension) {
errors.invalidDimensions = `Some of your \`dimensions\` is invalid and uses reserved static keys which are
${DEFAULT_DIMENSIONS.toString()}`;
}
}
if (!isEmpty(errors)) {
throw Error(JSON.stringify(errors));
}
}
return rocketComponent({
name,
component,
useBooleans,
dimensions,
dimensionKeys: getKeys(dimensions),
dimensionValues: getDimensionsValues(dimensions),
multiKeys: getMultipleDimensions(dimensions),
styled: true,
});
};
const isRocketComponent = (component) => {
if (component &&
typeof component === 'object' &&
component !== null &&
Object.prototype.hasOwnProperty.call(component, 'IS_ROCKETSTYLE')) {
return true;
}
return false;
};
const Wrapper = config.styled.div `
display: flex;
font-size: 32px;
`;
const component$2 = () => React.createElement(Wrapper, null, "Nothing here");
component$2.displayName = '@vitus-labs/rocketstories/Empty';
var element$1 = rocketstyle$1()({
component: Element,
name: 'element',
})
.theme({
fontFamily: 'Arial',
})
.styles((css) => css `
${({ $rocketstyle }) => {
const baseTheme = styles({ theme: $rocketstyle, css, rootSize: 16 });
return css `
${baseTheme};
`;
}};
`);
var Heading = element$1.attrs({ tag: 'h1', block: true }).sizes({
level1: {
fontSize: 20,
},
level2: {
marginTop: 0,
fontSize: 16,
},
});
/* eslint-disable no-underscore-dangle */
const getTheme = () => window.__VITUS_LABS_STORIES__.decorators.theme;
/* eslint-disable no-param-reassign */
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 has custom knobs configuration
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)) {
// TODO: parse arrays
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 not last item, add comma and space
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)) {
// TODO: parse arrays
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 === undefined) {
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) {
const keys = Object.keys(booleanDimensions);
result += addBooleanCodeComment(keys);
result += createJSXCode(name, { ...booleanDimensions, ...props });
}
return result;
};
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 = {
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: undefined,
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.',
},
// vertical: {
// type: 'boolean',
// group,
// valueType: 'boolean | Record<string,boolean> | Array<boolean>',
// description:
// 'Define whether element should render horizontally or vertically.',
// },
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',
},
};
const group$3 = 'List (@vitus-labs)';
const itemPropsType = `Record<string, any> | (props, meta) => Record<string,any>`;
var list = {
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,
},
};
const group$2 = 'Overlay (Vitus-Labs)';
var overlay = {
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',
},
};
const group$1 = 'Rocketstyle (Vitus-Labs)';
var rocketstyle = {
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`.',
},
};
const group = 'Text (Vitus-Labs)';
var text = {
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.',
},
};
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;
}, {});
const convertDimensionsToControls = ({ dimensions, multiKeys, }) => Object.entries(dimensions).reduce((acc, [key, value]) => {
const valueKeys = Object.keys(value);
const isMultiKey = !!multiKeys[key];
const control = {
type: isMultiKey ? 'multi-select' : 'select',
options: valueKeys,
group: 'Dimensions [Rocketstyle (Vitus-Labs)]',
};
return { ...acc, [key]: control };
}, {});
const getDefaultVitusLabsControls = (component) => {
const { IS_ROCKETSTYLE, VITUS_LABS__COMPONENT } = component;
cons