UNPKG

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

129 lines (128 loc) 5.76 kB
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); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { jsx as _jsx } from "react/jsx-runtime"; import { factory as factoryBase } from './factory'; import { cloneProps, getAndDeleteProp } from './utils'; import { Empty } from '@ffsm/compositor/empty'; import { Condition } from '@ffsm/compositor/condition'; import { AsSlot } from '@ffsm/compositor/as-slot'; import { AsNode } from '@ffsm/compositor/as-node'; function getCompositorOptions(options) { var clonedOptions = cloneProps(options); var asSlot = getAndDeleteProp(clonedOptions, 'asSlot'); var asNode = getAndDeleteProp(clonedOptions, 'asNode', false); var asNodeFalsy = getAndDeleteProp(clonedOptions, 'asNodeFalsy', false); var emptyFallback = getAndDeleteProp(clonedOptions, 'emptyFallback', null); var condition = getAndDeleteProp(clonedOptions, 'condition', undefined); var conditionFallback = getAndDeleteProp(clonedOptions, 'conditionFallback', null); var conditionFalsy = getAndDeleteProp(clonedOptions, 'conditionFalsy', false); return { asSlot: asSlot, asNode: asNode, asNodeFalsy: asNodeFalsy, emptyFallback: emptyFallback, condition: condition, conditionFallback: conditionFallback, conditionFalsy: conditionFalsy, }; } /** * Creates a factory component with integrated Compositor features * * This factory function combines the standard factory component creation with * automatic integration of @ffsm/compositor components. While the basic factory * focuses on creating reusable UI components with dynamic props and forwarding, * this compositor-enabled version adds support for advanced UI patterns: * * - Conditional rendering via Condition component * - Empty state handling via Empty component * - Children-based conditional rendering via AsNode component * - Content projection via AsSlot component * * @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 {InitialProps<Element, AdditionalProps>} [init] - Initial props or props factory function * @param {FactoryOptions<Element, AdditionalProps>} [options={}] - Factory options including compositor features * @returns {ForwardRefExoticComponent<PropsWithoutRef<FactoryProps<Element, AdditionalProps>> & RefAttributes<Factory<Element>>>} * A forward ref component with compositor integration * * @example * // Create a card with empty state * const Card = factory('Card', * { className: 'card p-4' }, * { emptyFallback: <p>No content available</p> } * ); * * @example * // Create a component that only renders when authenticated * const ProtectedArea = factory('ProtectedArea', * { className: 'protected-area' }, * { * condition: (props) => props.isAuthenticated, * conditionFallback: <LoginPrompt /> * } * ); * * @example * // Create a component with slot-based composition * const Dialog = factory('Dialog', * { className: 'dialog' }, * { * asSlot: true, * outlet: <div className="dialog-content" /> * } * ); * * @example * // Create a component that only renders when it has children * const Section = factory('Section', * { className: 'section' }, * { asNode: true } * ); */ export function factory(displayName, init, options) { if (options === void 0) { options = {}; } var _a = getCompositorOptions(options), emptyFallback = _a.emptyFallback, condition = _a.condition, conditionFalsy = _a.conditionFalsy, conditionFallback = _a.conditionFallback, asSlot = _a.asSlot, asNode = _a.asNode, asNodeFalsy = _a.asNodeFalsy; function template(Component, props, initProps) { var children = props.children, rest = __rest(props, ["children"]); var content = children; if (emptyFallback) { content = _jsx(Empty, { fallback: emptyFallback, children: content }); } if (condition !== undefined) { content = (_jsx(Condition, { when: condition, falsy: conditionFalsy, fallback: conditionFallback, children: content })); } var outlet = getAndDeleteProp(initProps, 'outlet', null); if (asSlot) { content = (_jsx(AsSlot, { outlet: outlet, outletProps: initProps, children: content })); } var templateOption = (options === null || options === void 0 ? void 0 : options.template) || (function (Comp, props) { return _jsx(Comp, __assign({}, props)); }); var render = templateOption(Component, __assign(__assign({}, rest), { children: content }), initProps); if (asNode) { return (_jsx(AsNode, { of: content, falsy: asNodeFalsy, children: render })); } return render; } return factoryBase(displayName, init, __assign(__assign({}, options), { template: template })); }