Gathering detailed insights and metrics for @ffsm/factory
Gathering detailed insights and metrics for @ffsm/factory
Gathering detailed insights and metrics for @ffsm/factory
Gathering detailed insights and metrics for @ffsm/factory
npm install @ffsm/factory
Typescript
Module System
TypeScript (96.92%)
CSS (2.52%)
JavaScript (0.4%)
Shell (0.17%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
64 Commits
1 Branches
1 Contributors
Updated on May 27, 2025
Latest Version
0.0.2
Package Id
@ffsm/factory@0.0.2
Unpacked Size
72.13 kB
Size
18.23 kB
File Count
16
Published on
May 12, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
2
6
@ffsm/factory
is a powerful component factory for React that simplifies the creation of reusable components with built-in support for composition patterns, conditional rendering, and prop management.
Note: Additional examples and illustrations will be added in upcoming updates.
@ffsm/factory
is a powerful component creation system for React that streamlines how you build, compose,
and manage reusable UI components. Unlike traditional component libraries, factory doesn't impose design opinions
but instead focuses on providing a flexible and type-safe foundation for creating your own component ecosystem.
At its core, @ffsm/factory
solves common challenges in component development:
The library comes in two versions:
@ffsm/factory
): Core functionality for component creation, styling, and prop management@ffsm/factory/compositor
): Extended version with advanced UI patterns like conditional rendering, slots, and empty statesInstalling @ffsm/factory
is straightforward with your preferred package manager:
1# Using npm 2npm install @ffsm/factory 3 4# Using yarn 5yarn add @ffsm/factory 6 7# Using pnpm 8pnpm add @ffsm/factory
Basic vs. Compositor Installation
@ffsm/factory
comes in two versions:
1import { factory } from '@ffsm/factory';
1# Install both packages for compositor features 2npm install @ffsm/factory @ffsm/compositor
1import { factory } from '@ffsm/factory/compositor';
TypeScript Support
@ffsm/factory
includes built-in TypeScript types - no additional packages required.
Peer Dependencies
Optional Integrations
Choose the installation that matches your requirements - start with the basic package for simple component creation, or include the compositor package for advanced UI patterns.
@ffsm/factory
provides a comprehensive set of features to streamline React component development:
The core factory functionality focuses on simplified component creation:
1import { factory } from '@ffsm/factory'; 2 3// Create a simple button component 4const Button = factory<{}>('Button', { 5 className: 'btn', 6 as: 'button', 7}); 8 9// Use it in your app 10function App() { 11 return ( 12 <Button className="btn-primary" onClick={handleClick}> 13 Click Me 14 </Button> 15 ); 16}
When used with the compositor package, additional UI patterns are available:
1import { factory } from '@ffsm/factory/compositor'; 2 3// Component that only renders for authenticated users 4const ProtectedContent = factory<{}>('ProtectedContent', { 5 condition: (props) => props.isAuthenticated, 6 conditionFallback: <LoginPrompt />, 7}); 8 9// Usage 10<ProtectedContent isAuthenticated={user?.authenticated}> 11 <UserDashboard /> 12</ProtectedContent>;
Factory components automatically handle:
All these features are designed to work together seamlessly, providing a robust foundation for building React component libraries or design systems.
Getting started with @ffsm/factory
is simple and straightforward. The library provides an intuitive API
for creating reusable React components with minimal boilerplate:
1import { factory } from '@ffsm/factory'; 2 3// Create a simple button component 4const Button = factory<{}>('Button', { 5 className: 'btn', 6 as: 'button', 7}); 8 9// Use it in your application 10function App() { 11 return ( 12 <Button className="btn-primary" onClick={() => alert('Clicked!')}> 13 Click Me 14 </Button> 15 ); 16}
The factory()
function takes three parameters:
1// Static props initialization 2const Card = factory<{}>('Card', { 3 className: 'card', 4 as: 'div', 5}); 6 7// Dynamic props based on component props 8const Button = factory<{ variant?: 'primary' | 'secondary' }>( 9 'Button', 10 (props) => ({ 11 className: `btn ${props.variant ? `btn-${props.variant}` : ''}`, 12 as: 'button', 13 }) 14);
Factory components behave like standard React components:
1// Props are merged with initial props 2<Button className="mt-4" onClick={handleClick}> 3 Submit 4</Button>; 5 6// Props can be spread 7const buttonProps = { className: 'large', disabled: true }; 8<Button {...buttonProps}>Cancel</Button>; 9 10// Ref forwarding works automatically 11const buttonRef = useRef < HTMLButtonElement > null; 12<Button ref={buttonRef}>Focus Me</Button>;
You can specify the element type using the as
prop:
1// Create a component with default element type 2const Text = factory<{}>('Text', { 3 className: 'text', 4}); 5 6// Override element type when using the component 7<Text as="h1" className="text-xl">Heading</Text> 8<Text as="p" className="text-sm">Paragraph</Text> 9<Text as="span" className="text-xs">Small text</Text>
Factory components are fully typed with TypeScript, providing excellent autocomplete and type checking:
1// Define component-specific props 2type ButtonProps = { 3 variant?: 'primary' | 'secondary' | 'outline'; 4 size?: 'sm' | 'md' | 'lg'; 5}; 6 7// Create type-safe component 8const Button = factory<ButtonProps>('Button', (props) => ({ 9 className: `btn btn-${props.variant || 'primary'} btn-${props.size || 'md'}`, 10 as: 'button', 11})); 12 13// Type checking and autocomplete work as expected 14<Button 15 variant="primary" // ✓ Autocomplete for 'primary', 'secondary', 'outline' 16 size="lg" // ✓ Autocomplete for 'sm', 'md', 'lg' 17 onClick={() => {}} // ✓ Standard button props available 18/>;
These examples demonstrate the foundation of using @ffsm/factory
for component creation.
The subsequent sections will explore more advanced patterns and features.
The component factory is the heart of @ffsm/factory
, providing a flexible and type-safe way to
create reusable React components. This section explores its capabilities, from basic usage to advanced
configuration options.
Creating Components with Factory
The factory()
function streamlines the process of creating React components by handling boilerplate
code and providing a consistent API:
1import { factory } from '@ffsm/factory'; 2 3// Create a simple button component 4const Button = factory<{}>('Button', { 5 className: 'btn', 6 as: 'button', 7}); 8 9// Create a card component 10const Card = factory<{}>('Card', { 11 className: 'card', 12});
When you create a component with factory()
, it:
One of the most powerful features of the factory is dynamic prop initialization, which allows you to define props based on the component's runtime props:
1import { factory } from '@ffsm/factory'; 2 3// Button component with variant support 4const Button = factory<{ 5 variant?: 'primary' | 'secondary' | 'outline'; 6 size?: 'sm' | 'md' | 'lg'; 7}>('Button', (props) => ({ 8 className: ` 9 btn 10 ${props.variant ? `btn-${props.variant}` : 'btn-primary'} 11 ${props.size ? `btn-${props.size}` : 'btn-md'} 12 `, 13 as: 'button', 14})); 15 16// Usage 17<Button variant="secondary" size="lg"> 18 Large Secondary Button 19</Button>;
This approach enables:
The third parameter to factory()
lets you configure how the component handles props:
1import { factory } from '@ffsm/factory'; 2 3// Create a link component with external link handling 4const Link = factory<{ 5 isExternal?: boolean; 6}>( 7 'Link', 8 { 9 as: 'a', 10 className: 'link', 11 }, 12 { 13 // Don't forward custom props to the DOM 14 excludeProps: ['isExternal'], 15 16 // Custom template function to add security attributes to external links 17 template: (Component, props, initProps) => ( 18 <Component 19 {...props} 20 target={initProps.isExternal ? '_blank' : undefined} 21 rel={initProps.isExternal ? 'noopener noreferrer' : undefined} 22 /> 23 ), 24 } 25); 26 27// Usage 28<Link href="https://example.com" isExternal> 29 External Link 30</Link>;
Available Options
The factory accepts several configuration options to customize behavior:
Custom Templates
The template option gives you complete control over how your component renders:
1// Create a form field with label and error handling 2const FormField = factory<{ 3 label?: string; 4 error?: string; 5}>( 6 'FormField', 7 { 8 as: 'input', 9 className: 'form-control', 10 }, 11 { 12 excludeProps: ['label', 'error'], 13 template: (Component, props, initProps) => ( 14 <div className="form-group"> 15 {initProps.label && ( 16 <label className="form-label">{initProps.label}</label> 17 )} 18 <Component {...props} /> 19 {initProps.error && <div className="form-error">{initProps.error}</div>} 20 </div> 21 ), 22 } 23); 24 25// Usage 26<FormField 27 label="Email Address" 28 type="email" 29 placeholder="your@email.com" 30 error={errors.email} 31/>;
Templates are useful for:
By leveraging the configuration options, you can create highly customized and reusable components while maintaining clean, developer-friendly APIs.
The Compositor integration extends @ffsm/factory
with advanced UI patterns leveraging
the @ffsm/compositor
package. This powerful combination allows you to create components
with built-in support for conditional rendering, slot-based composition, and empty state handling.
To use the compositor features, you need to install both packages:
1# Using npm 2npm install @ffsm/factory @ffsm/compositor 3 4# Using yarn 5yarn add @ffsm/factory @ffsm/compositor 6 7# Using pnpm 8pnpm add @ffsm/factory @ffsm/compositor
Import factory from the compositor subpath to access all the enhanced features:
1// Import the compositor-enabled factory 2import { factory } from '@ffsm/factory/compositor'; 3 4// Create components with advanced composition patterns 5const Card = factory<{}>('Card', { 6 className: 'card', 7 emptyFallback: <p>No content available</p>, 8});
The slot-based composition pattern allows you to render children into designated "slots" within your component, similar to how web components handle content projection:
1import { factory } from '@ffsm/factory/compositor'; 2 3// Create a dialog with header and content slots 4const Dialog = factory<{}>('Dialog', { 5 className: 'dialog', 6 asSlot: true, 7 children: ({ children }) => ( 8 <div className="dialog-container"> 9 <div className="dialog-header"></div> 10 <div className="dialog-content">{children}</div> 11 </div> 12 ), 13}); 14 15// Usage with content projected into the slot 16<Dialog>This content will be rendered inside the dialog-content div</Dialog>;
Conditional rendering lets you show or hide components based on specific conditions:
1import { factory } from '@ffsm/factory/compositor'; 2 3// Component that only renders for authenticated users 4const ProtectedContent = factory<{ 5 isAuthenticated: boolean; 6}>('ProtectedContent', (props) => ({ 7 condition: props.isAuthenticated, 8 conditionFallback: <LoginPrompt />, 9})); 10 11// Component with async conditions (e.g., permission check) 12const AdminPanel = factory<{ 13 userId: string; 14}>('AdminPanel', (props) => ({ 15 // Can return a Promise for async checks 16 condition: async () => { 17 const permissions = await fetchUserPermissions(props.userId); 18 return permissions.includes('admin'); 19 }, 20 conditionFallback: <AccessDenied />, 21})); 22 23// Usage 24<ProtectedContent isAuthenticated={Boolean(user)}> 25 <UserDashboard /> 26</ProtectedContent>;
Empty state handling provides fallback content when children are empty or non-existent:
1import { factory } from '@ffsm/factory/compositor'; 2 3// List with empty state 4const UserList = factory<{}>('UserList', { 5 as: 'ul', 6 className: 'user-list', 7 emptyFallback: <p className="empty-message">No users found</p>, 8}); 9 10// Usage with conditional content 11<UserList> 12 {users.length > 0 13 ? users.map((user) => <li key={user.id}>{user.name}</li>) 14 : null} 15</UserList>;
You can also use the asNode
option to only render a component when it has children:
1import { factory } from '@ffsm/factory/compositor'; 2 3// Section that only renders when it has content 4const Section = factory<{}>('Section', { 5 className: 'section', 6 asNode: true, 7}); 8 9// Won't render anything 10<Section /> 11 12// Will render normally 13<Section> 14 <h2>Section Title</h2> 15 <p>Content here</p> 16</Section>
The real power of compositor integration comes from combining these patterns:
1import { factory } from '@ffsm/factory/compositor'; 2 3// Data panel component with loading, empty, and error states 4const DataPanel = factory<{ 5 isLoading: boolean; 6 error?: string; 7}>('DataPanel', (props) => ({ 8 // Only render when not loading 9 condition: !props.isLoading, 10 conditionFallback: <LoadingSpinner />, 11 12 // Show error message when there's an error 13 asSlot: Boolean(props.error), 14 children: props.error ? ( 15 <div className="error-container">{props.error}</div> 16 ) : undefined, 17 18 // When no error but no content, show empty state 19 emptyFallback: <EmptyState message="No data available" />, 20})); 21 22// Usage in a data-fetching scenario 23<DataPanel isLoading={loading} error={error}> 24 {data && 25 data.items.map((item) => ( 26 <div key={item.id} className="data-item"> 27 {item.name} 28 </div> 29 ))} 30</DataPanel>;
The compositor integration eliminates the need for repetitive conditional rendering patterns, empty state checks, and nested component structures, resulting in cleaner and more maintainable code.
By leveraging these patterns, you can create sophisticated UI components with minimal code while maintaining a clean, declarative API that's easy for other developers to use.
@ffsm/factory
works exceptionally well with Tailwind CSS, allowing you to create reusable
components with consistent styling while maintaining the utility-first approach.
Using factory with Tailwind CSS gives you the best of both worlds: the simplicity of Tailwind's utility classes and the reusability of component abstractions:
1import { factory, clsx } from '@ffsm/factory'; 2 3// Create a reusable button component with Tailwind classes 4export const Button = factory<{ 5 variant?: 'primary' | 'secondary' | 'outline'; 6 size?: 'sm' | 'md' | 'lg'; 7}>('Button', (props) => ({ 8 as: 'button', 9 className: clsx( 10 // Base styles 11 'font-medium rounded transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2', 12 13 // Size variations 14 props.size === 'sm' && 'px-3 py-1.5 text-sm', 15 props.size === 'lg' && 'px-5 py-2.5 text-lg', 16 (!props.size || props.size === 'md') && 'px-4 py-2 text-base', 17 18 // Variant styles 19 props.variant === 'primary' && 20 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500', 21 props.variant === 'secondary' && 22 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500', 23 props.variant === 'outline' && 24 'border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-500', 25 (!props.variant || props.variant === 'primary') && 26 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500' 27 ), 28})); 29 30// Usage 31<Button variant="secondary" size="lg" onClick={handleClick}> 32 Save Changes 33</Button>;
You can create complete UI systems by composing factory components with Tailwind classes:
1// Card components 2export const Card = factory<{}>('Card', { 3 className: 'bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden', 4}); 5 6export const CardHeader = factory<{}>('CardHeader', { 7 className: 'px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700', 8}); 9 10export const CardBody = factory<{}>('CardBody', { 11 className: 'px-4 py-5 sm:p-6', 12}); 13 14export const CardFooter = factory<{}>('CardFooter', { 15 className: 'px-4 py-4 sm:px-6 border-t border-gray-200 dark:border-gray-700', 16}); 17 18// Form components 19export const FormGroup = factory<{}>('FormGroup', { 20 className: 'mb-4', 21}); 22 23export const Label = factory<{ required?: boolean }>('Label', (props) => ({ 24 as: 'label', 25 className: 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1', 26 children: props.children, 27})); 28 29export const Input = factory<{ 30 error?: boolean; 31}>('Input', (props) => ({ 32 as: 'input', 33 className: clsx( 34 'block w-full rounded-md shadow-sm sm:text-sm', 35 props.error 36 ? 'border-red-300 text-red-900 placeholder-red-300 focus:border-red-500 focus:ring-red-500' 37 : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500' 38 ), 39}));
Using factory with Tailwind provides several advantages:
With Tailwind Plugins
Factory components work seamlessly with Tailwind plugins like @tailwindcss/forms
:
1export const Select = factory<{ 2 error?: boolean; 3}>('Select', (props) => ({ 4 as: 'select', 5 className: clsx( 6 'block w-full rounded-md shadow-sm sm:text-sm', 7 props.error 8 ? 'border-red-300 text-red-900 focus:border-red-500 focus:ring-red-500' 9 : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500' 10 ), 11}));
Advanced Tailwind Integration
For more complex scenarios, you can leverage dynamic props with Tailwind:
1import { factory, clsx } from '@ffsm/factory'; 2 3// Text component with color, size and weight variants 4export const Text = factory<{ 5 color?: 'primary' | 'secondary' | 'error' | 'success'; 6 size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl'; 7 weight?: 'normal' | 'medium' | 'semibold' | 'bold'; 8}>('Text', (props) => ({ 9 className: clsx( 10 // Size mapping 11 props.size === 'xs' && 'text-xs', 12 props.size === 'sm' && 'text-sm', 13 props.size === 'base' && 'text-base', 14 props.size === 'lg' && 'text-lg', 15 props.size === 'xl' && 'text-xl', 16 props.size === '2xl' && 'text-2xl', 17 (!props.size || props.size === 'base') && 'text-base', 18 19 // Weight mapping 20 props.weight === 'normal' && 'font-normal', 21 props.weight === 'medium' && 'font-medium', 22 props.weight === 'semibold' && 'font-semibold', 23 props.weight === 'bold' && 'font-bold', 24 (!props.weight || props.weight === 'normal') && 'font-normal', 25 26 // Color mapping 27 props.color === 'primary' && 'text-blue-600 dark:text-blue-400', 28 props.color === 'secondary' && 'text-gray-600 dark:text-gray-400', 29 props.color === 'error' && 'text-red-600 dark:text-red-400', 30 props.color === 'success' && 'text-green-600 dark:text-green-400', 31 (!props.color || props.color === 'primary') && 32 'text-gray-900 dark:text-white' 33 ), 34})); 35 36// Usage 37<Text size="lg" color="error" weight="bold"> 38 Something went wrong! 39</Text>;
This approach gives you all the benefits of Tailwind's utility-first approach while providing the abstraction and reusability of a component library.
The core factory function for creating components with standardized patterns and prop handling.
1// Basic factory 2function factory< 3 AdditionalProps extends Record<string, any>, 4 Element extends ElementType = 'div', 5>( 6 displayName: string, 7 init?: InitialProps<Element, AdditionalProps>, 8 options?: FactoryOptions<Element, AdditionalProps> 9): ForwardRefExoticComponent< 10 PropsWithoutRef<FactoryProps<Element, AdditionalProps>> & 11 RefAttributes<Element> 12>; 13 14// Compositor-enabled factory 15// From '@ffsm/factory/compositor' 16function factory< 17 AdditionalProps extends Record<string, any>, 18 Element extends ElementType = 'div', 19>( 20 displayName: string, 21 init?: InitialProps<Element, AdditionalProps>, 22 options?: CompositorFactoryOptions<Element, AdditionalProps> 23): ForwardRefExoticComponent< 24 PropsWithoutRef<FactoryProps<Element, AdditionalProps>> & 25 RefAttributes<Element> 26>;
Parameters
displayName
(string): The display name for the component in React DevToolsinit
(optional): Initial props or function that returns props based on component propsoptions
(optional): Configuration options for the factory componentReturns
A forward ref React component with the specified props and features.
The second parameter (init) of the factory function can be either an object or a function that returns an object. It accepts the following properties:
Prop | Type | Description |
---|---|---|
as | ElementType | The base element type to render (div, button, etc.) |
className | string , (props) => string | Static or dynamic class name |
style | CSSProperties , (props) => CSSProperties | Static or dynamic styles |
children | ReactNode , (props) => ReactNode | Static content or render function |
...props | any | Any additional props to pass to the component |
Usage Examples
Static initialization:
1const Card = factory<{}>('Card', { 2 className: 'card p-4 rounded shadow', 3 as: 'div', 4});
Dynamic initialization:
1const Button = factory<{ variant?: 'primary' | 'secondary' }>( 2 'Button', 3 (props) => ({ 4 className: `btn ${props.variant ? `btn-${props.variant}` : 'btn-primary'}`, 5 as: 'button', 6 }) 7);
The third parameter (options
) allows you to configure advanced behavior of your component:
Basic Factory Options
Option | Type | Description |
---|---|---|
excludeProps | Array<string> | Props to exclude from being forwarded to DOM |
shouldForwardProp | (key: keyof Props) => boolean | Custom function to determine if a prop should be forwarded |
template | (Component, props, initProps) => ReactNode | Custom rendering template for the component |
Compositor Factory Options (available in '@ffsm/factory/compositor')
Option | Type | Description |
---|---|---|
asSlot | boolean | Enable slot-based composition |
asNode | boolean | Render only when children exist |
asNodeFalsy | boolean | Use strict falsy checking for asNode |
emptyFallback | ReactNode | Content to show when children are empty |
condition | unknown , (props) => unknown | Condition for conditional rendering |
conditionFallback | ReactNode | Content to show when condition is falsy |
conditionFalsy | boolean | Use strict falsy checking for condition |
Usage Examples
Excluding custom props:
1const Link = factory<{ isExternal?: boolean }>( 2 'Link', 3 { 4 as: 'a', 5 className: 'link', 6 }, 7 { 8 excludeProps: ['isExternal'], 9 } 10);
Custom template:
1const FormField = factory<{ label?: string; error?: string }>( 2 'FormField', 3 { 4 as: 'input', 5 className: 'form-input', 6 }, 7 { 8 excludeProps: ['label', 'error'], 9 template: (Component, props, initProps) => ( 10 <div className="form-group"> 11 {initProps.label && <label>{initProps.label}</label>} 12 <Component {...props} /> 13 {initProps.error && <div className="error">{initProps.error}</div>} 14 </div> 15 ), 16 } 17);
Compositor features:
1import { factory } from '@ffsm/factory/compositor'; 2 3const ProtectedContent = factory<{ isAdmin: boolean }>( 4 'ProtectedContent', 5 { 6 className: 'protected-content', 7 }, 8 { 9 condition: (props) => props.isAdmin, 10 conditionFallback: <AccessDenied />, 11 } 12);
The package exports several utility types for working with factory components:
Type | Description |
---|---|
FactoryProps<Element, AdditionalProps> | Combined props type for factory components |
InitialProps<Element, AdditionalProps> | Type for initial props or props factory function |
FactoryOptions<Element, AdditionalProps> | Type for factory options (basic version) |
MaybeFn<Result, Props> | Type that can be either a value or a function returning it |
ObjectProps | Generic object properties type |
Utility for conditionally joining class names:
1import { clsx } from '@ffsm/factory'; 2 3const className = clsx( 4 'base-class', 5 condition && 'conditional-class', 6 { 'object-key': booleanValue }, 7 ['array', 'of', 'classes'] 8);
The @ffsm/factory
package includes a comprehensive type system that provides full TypeScript support.
This section explains the key types and how to use them effectively in your components.
FactoryProps<Element, AdditionalProps>
This is the main props type for factory-created components. It combines:
1// Example: Creating a button with custom props 2type ButtonProps = { 3 variant: 'primary' | 'secondary'; 4 size: 'sm' | 'md' | 'lg'; 5}; 6 7const Button = factory<ButtonProps, 'button'>('Button', { 8 className: 'btn', 9 as: 'button', 10}); 11 12// Usage with type checking: 13<Button 14 variant="primary" // ✓ Type checked 15 size="sm" // ✓ Type checked 16 onClick={() => {}} // ✓ Inherited from 'button' element 17 invalid={true} // ✗ Type error 18/>;
InitialProps<Element, AdditionalProps>
This flexible type represents initial props configuration and can be either:
1const Card = factory<{}>('Card', { 2 className: 'card', 3 as: 'div', 4});
1const Button = factory<ButtonProps>('Button', (props) => ({ 2 className: `btn btn-${props.variant} btn-${props.size}`, 3 as: 'button', 4}));
Custom Component Props
Define custom props with full type safety:
1interface TabProps { 2 active?: boolean; 3 label: string; 4 index: number; 5} 6 7const Tab = factory<TabProps>('Tab', (props) => ({ 8 className: props.active ? 'tab active' : 'tab', 9 'aria-selected': props.active, 10 role: 'tab', 11 as: 'div', 12})); 13 14// Usage with TypeScript validation 15<Tab 16 label="Settings" // Required 17 index={2} // Required 18 active={true} // Optional 19/>;
Element Type Customization
Specify a different base element type:
1const NavLink = factory<{}, 'a'>('NavLink', { 2 className: 'nav-link', 3 as: 'a', 4}); 5 6// Usage with anchor-specific props 7<NavLink href="/about" target="_blank"> 8 About 9</NavLink>;
Type Inference Best Practices
When creating a factory component without additional props, always specify an empty object explicitly:
1// Correct approach - explicitly specify empty object 2export const FormWrapper = factory<{}>('FormWrapper', { 3 className: 'w-full', 4}); 5 6// Not recommended - may cause type inference issues 7export const FormContainer = factory('FormContainer', { 8 className: 'flex w-full border border-white/20 rounded-lg', 9});
Conditional Props
Create components with conditional prop requirements:
1// Better approach using union types 2type DialogProps = 3 | { title: string; size?: 'sm' | 'md' | 'lg'; closable: true; onClose: () => void } 4 | { title: string; size?: 'sm' | 'md' | 'lg'; closable?: false; onClose?: never }; 5 6const Dialog = factory<DialogProps>('Dialog', { 7 className: 'dialog', 8}); 9 10// Usage: 11// Valid - onClose is required when closable is true 12<Dialog title="Settings" closable={true} onClose={() => setOpen(false)} /> 13 14// Valid - onClose is not allowed when closable is false 15<Dialog title="Info" closable={false} /> 16 17// Invalid - missing onClose 18<Dialog title="Error" closable={true} /> // TypeScript error 19 20// Invalid - has onClose when closable is false 21<Dialog title="Warning" closable={false} onClose={() => {}} /> // TypeScript error
Component Composition Types
Build complex component hierarchies with composed types:
1type CardProps = { 2 title: string; 3 elevated?: boolean; 4}; 5 6type CardImageProps = CardProps & { 7 imageSrc: string; 8 imageAlt?: string; 9}; 10 11const Card = factory<CardProps>('Card', { 12 className: 'card', 13}); 14 15const CardWithImage = factory<CardImageProps>('CardWithImage', (props) => ({ 16 className: `card ${props.elevated ? 'elevated' : ''}`, 17 children: ( 18 <> 19 <img src={props.imageSrc} alt={props.imageAlt || props.title} /> 20 <h3>{props.title}</h3> 21 </> 22 ), 23}));
The package exports several utility types for advanced use cases:
MaybeFn<Result, Props>
: Type for values that can be either direct or function-returnedObjectProps
: Generic object properties typeFactoryProps<Element, AdditionalProps>
: Combined props for factory componentsFactoryOptions<Element, AdditionalProps>
: Options for factory configurationImport these types directly from the package:
1import { MaybeFn, ObjectProps, FactoryProps } from '@ffsm/factory'; 2 3// Create a type for a component that needs dynamic styling 4type DynamicComponent<P = {}> = React.FC< 5 P & { 6 styling: MaybeFn<string, P>; 7 } 8>;
By leveraging these type utilities, you can create type-safe component APIs that provide excellent developer experience with autocompletion and type checking.
@ffsm/factory
includes several utility functions that help with common component development tasks.
These utilities can be imported directly from the package and used both within factory components
and in your application code.
The package includes a lightweight version of the clsx utility for conditional class name composition, allowing you to combine class names dynamically:
1import { clsx } from '@ffsm/factory'; 2 3// Basic usage 4const className = clsx( 5 'base-class', 6 condition && 'conditional-class', 7 isActive ? 'active' : 'inactive', 8 { hidden: isHidden, visible: !isHidden } 9); 10 11// Within a factory component 12const Button = factory<{ 13 variant?: 'primary' | 'secondary'; 14 size?: 'sm' | 'md' | 'lg'; 15 disabled?: boolean; 16}>('Button', (props) => ({ 17 as: 'button', 18 className: clsx( 19 'btn', 20 props.variant && `btn-${props.variant}`, 21 props.size && `btn-${props.size}`, 22 props.disabled && 'btn-disabled' 23 ), 24}));
The clsx
utility supports:
'btn'
{ 'btn-primary': isPrimary }
['btn', isActive && 'btn-active']
condition && [subCondition && 'class']
false && 'hidden'
results in nothing being addedThis eliminates the need for an additional package for class name management in your projects.
While styled-components
focuses on styling with CSS-in-JS, @ffsm/factory
provides a more comprehensive
approach to component creation with built-in composition patterns, conditional rendering, and prop management.
Yes, @ffsm/factory works with any React-based UI library. You can wrap components from Material UI, Chakra UI,
or any other library using factory()
.
Yes, @ffsm/factory
is compatible with React Server Components, but be aware that some dynamic features
might need client-side hydration.
The regular factory (import { factory } from '@ffsm/factory'
) provides core functionality for component creation,
prop forwarding, and templates. The compositor factory (import { factory } from '@ffsm/factory/compositor'
) adds
support for advanced UI patterns like conditional rendering, slot-based composition, and empty state handling through
integration with @ffsm/compositor
.
Use the basic factory when you only need component creation and prop management. Use the compositor factory when you need advanced UI patterns like conditional rendering, slots, or empty state handling. The basic factory has fewer dependencies and a smaller bundle size.
The compositor integration adds a small runtime overhead, but it's negligible for most applications. The benefits
of cleaner code and declarative patterns usually outweigh the minimal performance cost. For performance-critical
scenarios with large lists, consider memoizing components with React.memo
.
Yes, when using the compositor features, you need to install both packages:
1npm install @ffsm/factory @ffsm/compositor
The basic factory doesn't require the compositor package.
While there are many component libraries and styling solutions available, @ffsm/factory
offers unique advantages
that set it apart:
Traditional UI libraries like Material UI, Chakra UI, or Ant Design provide pre-built components with specific
design systems. @ffsm/factory
takes a different approach:
Unlike CSS-in-JS libraries like styled-components or emotion, @ffsm/factory
:
Headless UI libraries like Radix UI or Headless UI provide unstyled components, but @ffsm/factory
:
@ffsm/factory
is ideal for teams building custom design systems, developers who need flexibility beyond existing UI libraries,
and projects where component consistency and maintainability are priorities.
@ffsm/factory
is designed with performance in mind, but there are some considerations to ensure optimal performance in your applications:
1const OptimizedCard = React.memo(factory<CardProps>('Card', {...}));
1// Dynamic import of compositor-enabled components 2const AdminPanel = lazy(() => import('./AdminPanel'));
@ffsm/factory
is designed to work across a wide range of React environments:
@ffsm/compositor
: The companion package that provides the advanced composition utilities used by factory's compositor integration. Install this package to use the advanced features like slots, conditional rendering, and empty state handling.These packages are designed to work together to provide a complete solution for building React applications, but each can be used independently according to your specific needs.
No vulnerabilities found.
No security vulnerabilities found.