@ffsm/factory
Version:
A powerful component factory for React that simplifies creation of reusable components with built-in support for composition patterns, conditional rendering, prop management, and TypeScript integration.
90 lines (89 loc) • 3.35 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, } from 'react';
import { extractProps, filterProps } from './utils';
/**
* Creates a reusable factory component with customizable rendering.
* Factory components support dynamic props, type-safe forwarding,
* and customizable rendering through template functions.
*
* @template AdditionalProps - Additional props type that the factory component accepts
* @template Element - The base element type, defaults to 'div'
* @param {string} displayName - Display name for the component in React DevTools
* @param {FactoryInitialProps<Element, AdditionalProps>} [init] - Initial props or props factory function
* @param {FactoryOptions<ElementType, AdditionalProps>} [options={}] - Additional options for the factory
* @returns {ForwardRefExoticComponent<PropsWithoutRef<FactoryProps<Element, AdditionalProps>> & RefAttributes<Factory<Element>>>}
* A forward ref component with the specified features
*
* @example
* // Basic usage - create a Button component
* const Button = factory('Button', {
* className: 'btn'
* });
*
* @example
* // With dynamic initialization
* const Card = factory('Card', (props) => ({
* className: clsx('card', props.variant && `card-${props.variant}`)
* }));
*
* @example
* // With custom template for form field layout
* const FormInput = factory(
* 'FormInput',
* {
* as: 'input',
* className: 'form-control'
* },
* {
* template: (Component, props, initProps) => (
* <div className="form-group">
* {props.label && <label>{props.label}</label>}
* <Component {...props} />
* {props.error && <div className="error">{props.error}</div>}
* </div>
* )
* }
* );
*
* @example
* // Filtering props with options
* const Link = factory(
* 'Link',
* { as: 'a' },
* {
* excludeProps: ['isExternal'],
* template: (Component, props, initProps) => (
* <Component
* {...props}
* target={initProps.isExternal ? '_blank' : undefined}
* rel={initProps.isExternal ? 'noopener noreferrer' : undefined}
* />
* )
* }
* );
*/
export function factory(displayName, init, options) {
if (options === void 0) { options = {}; }
var template = options.template || (function (Component, props) { return _jsx(Component, __assign({}, props)); });
var FactoryElement = forwardRef(function FactoryElement(props, ref) {
var solvedInit = typeof init === 'function'
? init(props)
: init;
var _a = extractProps(solvedInit, props), Component = _a.Component, children = _a.children, merged = _a.merged, initProps = _a.initProps;
var filtered = filterProps(merged, options);
return template(Component, __assign(__assign({}, filtered), { ref: ref, children: children }), initProps);
});
FactoryElement.displayName = displayName;
return FactoryElement;
}