New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@classytic/formkit

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@classytic/formkit - npm Package Compare versions

Comparing version
1.0.2
to
1.0.3
+56
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.3] - 2024-12-04
### Changed
- **Complete TypeScript Migration**: Rewrote entire codebase with strict TypeScript
- **Improved Type Definitions**: Added comprehensive type exports for better DX
- **Modern Build System**: Updated tsup configuration for better tree-shaking
- **Enhanced Types**: Added `FieldOption`, `FieldOptionGroup`, `DefineField` utility types
- **Better Error Messages**: Improved development warnings with actionable messages
### Added
- New type exports: `FieldOption`, `FieldOptionGroup`, `DefineField`, `SchemaFieldNames`, `InferSchemaValues`
- Subcomponent exports: `SectionRenderer`, `GridRenderer`, `FieldWrapper`
- Hook exports: `useFieldComponent`, `useLayoutComponent`
- Support for `gap` property in sections
- Support for `collapsible` and `defaultCollapsed` section properties
- `FormElement` type for consistent JSX return types
- `ClassValue` re-export from utils
### Fixed
- Package exports configuration for proper ESM/CJS interop
- TypeScript strict mode compatibility
- Proper peer dependency declarations
## [1.0.2] - 2024-12-03
### Added
- Initial public release
- FormGenerator component
- FormSystemProvider context
- Schema-driven form generation
- Variant support for components
- Conditional field rendering
## [1.0.1] - 2024-12-02
### Fixed
- Package configuration fixes
## [1.0.0] - 2024-12-01
### Added
- Initial release
+122
-58

@@ -9,4 +9,4 @@ 'use strict';

// src/FormGenerator.tsx
var FormSystemContext = react.createContext(null);
FormSystemContext.displayName = "FormSystemContext";
function FormSystemProvider({

@@ -19,4 +19,4 @@ components,

() => ({
components: components || {},
layouts: layouts || {}
components: components ?? {},
layouts: layouts ?? {}
}),

@@ -30,3 +30,5 @@ [components, layouts]

if (!context) {
throw new Error("useFormSystem must be used within a FormSystemProvider");
throw new Error(
"[FormKit] useFormSystem must be used within a FormSystemProvider. Make sure to wrap your form components with <FormSystemProvider>."
);
}

@@ -37,32 +39,48 @@ return context;

const { components } = useFormSystem();
if (variant && typeof components[variant] === "object" && components[variant]) {
if (variant && typeof components[variant] === "object" && components[variant] !== null) {
const variantComponents = components[variant];
if (variantComponents[type]) {
return variantComponents[type];
const variantComponent = variantComponents[type];
if (variantComponent) {
return variantComponent;
}
}
const Component = components[type] || components["default"] || components["text"];
if (!Component) {
if (process.env.NODE_ENV === "development") {
console.warn(`FormKit: No component found for type "${type}" (variant: ${variant})`);
return () => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "red", padding: 4, border: "1px dashed red" }, children: [
"Missing: ",
type
] });
}
return () => null;
const typeComponent = components[type];
if (typeComponent && typeof typeComponent === "function") {
return typeComponent;
}
return Component;
const defaultComponent = components["default"];
if (defaultComponent && typeof defaultComponent === "function") {
return defaultComponent;
}
const textComponent = components["text"];
if (textComponent && typeof textComponent === "function") {
return textComponent;
}
return NullComponent;
}
function useLayoutComponent(type, variant) {
const { layouts } = useFormSystem();
if (variant && typeof layouts[variant] === "object" && layouts[variant]) {
if (variant && typeof layouts[variant] === "object" && layouts[variant] !== null) {
const variantLayouts = layouts[variant];
if (variantLayouts[type]) {
return variantLayouts[type];
const variantLayout = variantLayouts[type];
if (variantLayout) {
return variantLayout;
}
}
return layouts[type] || layouts["default"] || DefaultLayout;
const typeLayout = layouts[type];
if (typeLayout && typeof typeLayout === "function") {
return typeLayout;
}
const defaultLayout = layouts["default"];
if (defaultLayout && typeof defaultLayout === "function") {
return defaultLayout;
}
return DefaultLayout;
}
var DefaultLayout = ({ children, className }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className, children });
function DefaultLayout({ children, className }) {
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children });
}
function NullComponent() {
return null;
}
function cn(...inputs) {

@@ -75,17 +93,31 @@ return tailwindMerge.twMerge(clsx.clsx(inputs));

disabled = false,
variant
variant,
className
}) {
const formContext = reactHookForm.useFormContext();
const activeControl = control || formContext?.control;
if (!schema?.sections) return null;
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("form-generator-root", variant && `form-variant-${variant}`), children: schema.sections.map((section, idx) => /* @__PURE__ */ jsxRuntime.jsx(
SectionRenderer,
const activeControl = control ?? formContext?.control;
if (!schema?.sections || schema.sections.length === 0) {
return null;
}
return /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
section,
control: activeControl,
disabled,
variant
},
section.id || idx
)) });
className: cn(
"formkit-root",
variant && `formkit-variant-${variant}`,
className
),
"data-formkit-root": "",
children: schema.sections.map((section, index) => /* @__PURE__ */ jsxRuntime.jsx(
SectionRenderer,
{
section,
control: activeControl,
disabled,
variant
},
section.id ?? `section-${index}`
))
}
);
}

@@ -98,3 +130,3 @@ function SectionRenderer({

}) {
const activeVariant = section.variant || variant;
const activeVariant = section.variant ?? variant;
const SectionLayout = useLayoutComponent("section", activeVariant);

@@ -112,11 +144,20 @@ if (section.condition && !section.condition(control)) {

className: section.className,
children: section.render ? section.render({ control, disabled, section }) : /* @__PURE__ */ jsxRuntime.jsx(
GridRenderer,
{
fields: section.fields,
cols: section.cols,
control,
disabled,
variant: activeVariant
}
collapsible: section.collapsible,
defaultCollapsed: section.defaultCollapsed,
children: section.render ? (
// Custom render function
section.render({ control, disabled, section })
) : (
// Standard grid rendering
/* @__PURE__ */ jsxRuntime.jsx(
GridRenderer,
{
fields: section.fields,
cols: section.cols,
gap: section.gap,
control,
disabled,
variant: activeVariant
}
)
)

@@ -129,2 +170,3 @@ }

cols = 1,
gap,
control,

@@ -135,4 +177,6 @@ disabled,

const GridLayout = useLayoutComponent("grid", variant);
if (!fields || fields.length === 0) return null;
return /* @__PURE__ */ jsxRuntime.jsx(GridLayout, { cols, children: fields.map((field, idx) => /* @__PURE__ */ jsxRuntime.jsx(
if (!fields || fields.length === 0) {
return null;
}
return /* @__PURE__ */ jsxRuntime.jsx(GridLayout, { cols, gap, children: fields.map((field, index) => /* @__PURE__ */ jsxRuntime.jsx(
FieldWrapper,

@@ -145,3 +189,3 @@ {

},
field.name || idx
field.name || `field-${index}`
)) });

@@ -159,22 +203,42 @@ }

}
const activeVariant = field.variant || variant;
const activeVariant = field.variant ?? variant;
const FieldComponent = useFieldComponent(field.type, activeVariant);
if (!FieldComponent) return null;
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: field.fullWidth ? "col-span-full" : "", children: /* @__PURE__ */ jsxRuntime.jsx(
FieldComponent,
if (!FieldComponent) {
return null;
}
const isDisabled = disabled || field.disabled;
return /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
field,
control,
disabled: disabled || field.disabled,
variant: activeVariant,
...field
className: cn(
"formkit-field",
field.fullWidth && "col-span-full",
field.className
),
"data-formkit-field": field.name,
"data-field-type": field.type,
children: /* @__PURE__ */ jsxRuntime.jsx(
FieldComponent,
{
...field,
field,
control,
disabled: isDisabled,
variant: activeVariant
}
)
}
) });
);
}
exports.FieldWrapper = FieldWrapper;
exports.FormGenerator = FormGenerator;
exports.FormSystemProvider = FormSystemProvider;
exports.GridRenderer = GridRenderer;
exports.SectionRenderer = SectionRenderer;
exports.cn = cn;
exports.useFieldComponent = useFieldComponent;
exports.useFormSystem = useFormSystem;
exports.useLayoutComponent = useLayoutComponent;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["createContext","useMemo","jsx","useContext","jsxs","twMerge","clsx","useFormContext","useWatch"],"mappings":"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","file":"index.cjs","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n} from \"./types\";\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Provides the component registry to the form system.\r\n * This is the root provider that enables FormGenerator to work.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): JSX.Element {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components || {},\r\n layouts: layouts || {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n/**\r\n * Hook to access the form system context\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\"useFormSystem must be used within a FormSystemProvider\");\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Helper to resolve a component for a specific field type and variant\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant]) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n if (variantComponents[type]) {\r\n return variantComponents[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard component\r\n const Component =\r\n (components[type] as FieldComponent) ||\r\n (components[\"default\"] as FieldComponent) ||\r\n (components[\"text\"] as FieldComponent);\r\n\r\n if (!Component) {\r\n // Library-safe fallback: Don't crash, just warn and render nothing or a placeholder\r\n if (process.env.NODE_ENV === \"development\") {\r\n console.warn(`FormKit: No component found for type \"${type}\" (variant: ${variant})`);\r\n return () => (\r\n <div style={{ color: \"red\", padding: 4, border: \"1px dashed red\" }}>\r\n Missing: {type}\r\n </div>\r\n );\r\n }\r\n return () => null;\r\n }\r\n\r\n return Component;\r\n}\r\n\r\n/**\r\n * Helper to resolve a layout component\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(type: LayoutType, variant?: Variant): LayoutComponent {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant]) {\r\n const variantLayouts = layouts[variant] as Record<string, LayoutComponent>;\r\n if (variantLayouts[type]) {\r\n return variantLayouts[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard layout\r\n return (\r\n (layouts[type] as LayoutComponent) ||\r\n (layouts[\"default\"] as LayoutComponent) ||\r\n DefaultLayout\r\n );\r\n}\r\n\r\n/**\r\n * Default layout component - simple div wrapper\r\n */\r\nconst DefaultLayout: LayoutComponent = ({ children, className }: any) => (\r\n <div className={className}>{children}</div>\r\n);\r\n","import { type ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\n/**\r\n * Utility function to merge Tailwind CSS classes\r\n * Combines clsx and tailwind-merge for conflict-free class merging\r\n *\r\n * @param inputs - Class values to merge\r\n * @returns Merged class string\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type { FormGeneratorProps, Section, BaseField, Variant } from \"./types\";\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema, using components injected via FormSystemProvider.\r\n * Supports conditional fields, dynamic layouts, and variants.\r\n *\r\n * @template TFieldValues - Form field values type\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n}: FormGeneratorProps<TFieldValues>): JSX.Element | null {\r\n // Use provided control or fall back to context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control || formContext?.control;\r\n\r\n if (!schema?.sections) return null;\r\n\r\n return (\r\n <div className={cn(\"form-generator-root\", variant && `form-variant-${variant}`)}>\r\n {schema.sections.map((section, idx) => (\r\n <SectionRenderer\r\n key={section.id || idx}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Section Renderer Props\r\n */\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): JSX.Element | null {\r\n // Allow section to override variant, or use global variant\r\n const activeVariant = section.variant || variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check condition if present\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n >\r\n {section.render ? (\r\n section.render({ control, disabled, section })\r\n ) : (\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Grid Renderer Props\r\n */\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField[];\r\n cols?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): JSX.Element | null {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) return null;\r\n\r\n return (\r\n <GridLayout cols={cols}>\r\n {fields.map((field, idx) => (\r\n <FieldWrapper\r\n key={field.name || idx}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Field Wrapper Props\r\n */\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single field wrapper\r\n * Handles conditional rendering and field visibility\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): JSX.Element | null {\r\n // Watch values for conditional rendering\r\n const formValues = useWatch({ control });\r\n\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Allow field to override variant\r\n const activeVariant = field.variant || variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) return null;\r\n\r\n return (\r\n <div className={field.fullWidth ? \"col-span-full\" : \"\"}>\r\n <FieldComponent\r\n field={field}\r\n control={control as any}\r\n disabled={disabled || field.disabled}\r\n variant={activeVariant}\r\n // Pass all field props to the component\r\n {...field}\r\n />\r\n </div>\r\n );\r\n}\r\n"]}
{"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["createContext","useMemo","jsx","useContext","twMerge","clsx","useFormContext","useWatch"],"mappings":"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","file":"index.cjs","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n SectionLayoutProps,\r\n GridLayoutProps,\r\n DefaultLayoutProps,\r\n FormElement,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// Context\r\n// ============================================================================\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n// Display name for React DevTools\r\nFormSystemContext.displayName = \"FormSystemContext\";\r\n\r\n// ============================================================================\r\n// Provider\r\n// ============================================================================\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Root provider that enables the form system. Provides component and layout\r\n * registries to FormGenerator and its descendants.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * // Variant-specific components\r\n * compact: {\r\n * text: CompactTextInput,\r\n * },\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): FormElement {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components ?? {},\r\n layouts: layouts ?? {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n// ============================================================================\r\n// Hooks\r\n// ============================================================================\r\n\r\n/**\r\n * Hook to access the form system context.\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\r\n \"[FormKit] useFormSystem must be used within a FormSystemProvider. \" +\r\n \"Make sure to wrap your form components with <FormSystemProvider>.\"\r\n );\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Hook to get a field component by type and optional variant.\r\n *\r\n * Resolution order:\r\n * 1. Variant-specific component: `components[variant][type]`\r\n * 2. Type-specific component: `components[type]`\r\n * 3. Default component: `components[\"default\"]`\r\n * 4. Text fallback: `components[\"text\"]`\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant] !== null) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n const variantComponent = variantComponents[type];\r\n if (variantComponent) {\r\n return variantComponent;\r\n }\r\n }\r\n\r\n // 2. Try type-specific component\r\n const typeComponent = components[type] as FieldComponent | undefined;\r\n if (typeComponent && typeof typeComponent === \"function\") {\r\n return typeComponent;\r\n }\r\n\r\n // 3. Try default component\r\n const defaultComponent = components[\"default\"] as FieldComponent | undefined;\r\n if (defaultComponent && typeof defaultComponent === \"function\") {\r\n return defaultComponent;\r\n }\r\n\r\n // 4. Try text fallback\r\n const textComponent = components[\"text\"] as FieldComponent | undefined;\r\n if (textComponent && typeof textComponent === \"function\") {\r\n return textComponent;\r\n }\r\n\r\n // 5. Development warning and placeholder\r\n if (process.env.NODE_ENV !== \"production\") {\r\n console.warn(\r\n `[FormKit] No component found for type \"${type}\"${variant ? ` (variant: \"${variant}\")` : \"\"}. ` +\r\n \"Register a component for this type in your FormSystemProvider.\"\r\n );\r\n return MissingFieldComponent;\r\n }\r\n\r\n // Production: silent fallback\r\n return NullComponent;\r\n}\r\n\r\n/**\r\n * Hook to get a layout component by type and optional variant.\r\n *\r\n * Resolution order:\r\n * 1. Variant-specific layout: `layouts[variant][type]`\r\n * 2. Type-specific layout: `layouts[type]`\r\n * 3. Default layout: `layouts[\"default\"]`\r\n * 4. Built-in default layout\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(\r\n type: LayoutType,\r\n variant?: Variant\r\n): LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps> {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant] !== null) {\r\n const variantLayouts = layouts[variant] as Record<\r\n string,\r\n LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n >;\r\n const variantLayout = variantLayouts[type];\r\n if (variantLayout) {\r\n return variantLayout;\r\n }\r\n }\r\n\r\n // 2. Try type-specific layout\r\n const typeLayout = layouts[type] as\r\n | LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n | undefined;\r\n if (typeLayout && typeof typeLayout === \"function\") {\r\n return typeLayout;\r\n }\r\n\r\n // 3. Try default layout\r\n const defaultLayout = layouts[\"default\"] as\r\n | LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n | undefined;\r\n if (defaultLayout && typeof defaultLayout === \"function\") {\r\n return defaultLayout;\r\n }\r\n\r\n // 4. Built-in default layout\r\n return DefaultLayout;\r\n}\r\n\r\n// ============================================================================\r\n// Fallback Components\r\n// ============================================================================\r\n\r\n/**\r\n * Default layout component - simple div wrapper.\r\n * Used when no layout is registered.\r\n */\r\nfunction DefaultLayout({ children, className }: DefaultLayoutProps): FormElement {\r\n return <div className={className}>{children}</div>;\r\n}\r\n\r\n/**\r\n * Null component for production fallback.\r\n */\r\nfunction NullComponent(): FormElement {\r\n return null;\r\n}\r\n\r\n/**\r\n * Development placeholder for missing field components.\r\n */\r\nfunction MissingFieldComponent({ field }: { field: { type: string; name: string } }): FormElement {\r\n return (\r\n <div\r\n style={{\r\n color: \"#dc2626\",\r\n padding: \"8px 12px\",\r\n border: \"1px dashed #dc2626\",\r\n borderRadius: \"4px\",\r\n fontSize: \"12px\",\r\n fontFamily: \"monospace\",\r\n backgroundColor: \"#fef2f2\",\r\n }}\r\n >\r\n Missing component: <strong>{field.type}</strong> (field: {field.name})\r\n </div>\r\n );\r\n}\r\n","import { clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\nimport type { ClassValue } from \"clsx\";\r\n\r\n/**\r\n * Utility function to merge CSS classes with Tailwind CSS conflict resolution.\r\n *\r\n * Combines `clsx` for conditional class handling with `tailwind-merge`\r\n * for proper Tailwind CSS class conflict resolution.\r\n *\r\n * @param inputs - Class values to merge (strings, arrays, objects, or conditionals)\r\n * @returns Merged and deduplicated class string\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage\r\n * cn(\"px-2 py-1\", \"px-4\") // \"py-1 px-4\"\r\n *\r\n * // Conditional classes\r\n * cn(\"base\", isActive && \"active\", { \"disabled\": isDisabled })\r\n *\r\n * // Arrays\r\n * cn([\"flex\", \"items-center\"], \"gap-2\")\r\n * ```\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n\r\n/**\r\n * Re-export ClassValue type for consumers who need it.\r\n */\r\nexport type { ClassValue };\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type {\r\n FormGeneratorProps,\r\n Section,\r\n BaseField,\r\n Variant,\r\n FormElement,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// FormGenerator Component\r\n// ============================================================================\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema definition, using components registered\r\n * via FormSystemProvider. Supports conditional fields, dynamic layouts,\r\n * and component variants.\r\n *\r\n * @template TFieldValues - Form field values type for type safety\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * interface FormData {\r\n * firstName: string;\r\n * email: string;\r\n * }\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm<FormData>();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n className,\r\n}: FormGeneratorProps<TFieldValues>): FormElement {\r\n // Use provided control or fall back to FormProvider context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control ?? formContext?.control;\r\n\r\n // Early return if no schema\r\n if (!schema?.sections || schema.sections.length === 0) {\r\n return null;\r\n }\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"formkit-root\",\r\n variant && `formkit-variant-${variant}`,\r\n className\r\n )}\r\n data-formkit-root=\"\"\r\n >\r\n {schema.sections.map((section, index) => (\r\n <SectionRenderer\r\n key={section.id ?? `section-${index}`}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Section Renderer\r\n// ============================================================================\r\n\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section<TFieldValues>;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields.\r\n * Handles conditional rendering and variant resolution.\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): FormElement {\r\n // Section can override variant\r\n const activeVariant = section.variant ?? variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check section condition\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n collapsible={section.collapsible}\r\n defaultCollapsed={section.defaultCollapsed}\r\n >\r\n {section.render ? (\r\n // Custom render function\r\n section.render({ control, disabled, section })\r\n ) : (\r\n // Standard grid rendering\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n gap={section.gap}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Grid Renderer\r\n// ============================================================================\r\n\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField<TFieldValues>[];\r\n cols?: number;\r\n gap?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout.\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n gap,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): FormElement {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) {\r\n return null;\r\n }\r\n\r\n return (\r\n <GridLayout cols={cols} gap={gap}>\r\n {fields.map((field, index) => (\r\n <FieldWrapper\r\n key={field.name || `field-${index}`}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Field Wrapper\r\n// ============================================================================\r\n\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField<TFieldValues>;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Wraps individual fields with conditional rendering logic.\r\n * Handles field visibility and variant resolution.\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): FormElement {\r\n // Watch form values for conditional rendering\r\n const formValues = useWatch({ control }) as TFieldValues;\r\n\r\n // Check field condition\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Field can override variant\r\n const activeVariant = field.variant ?? variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) {\r\n return null;\r\n }\r\n\r\n // Merge disabled states\r\n const isDisabled = disabled || field.disabled;\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"formkit-field\",\r\n field.fullWidth && \"col-span-full\",\r\n field.className\r\n )}\r\n data-formkit-field={field.name}\r\n data-field-type={field.type}\r\n >\r\n <FieldComponent\r\n {...(field as BaseField<FieldValues>)}\r\n field={field as BaseField<FieldValues>}\r\n control={control as Control<FieldValues>}\r\n disabled={isDisabled}\r\n variant={activeVariant}\r\n />\r\n </div>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Named Exports for Subcomponents (for advanced use cases)\r\n// ============================================================================\r\n\r\nexport { SectionRenderer, GridRenderer, FieldWrapper };\r\n"]}

@@ -1,24 +0,53 @@

import { FieldValues, Control } from 'react-hook-form';
import { ReactNode, ComponentType } from 'react';
import { FieldValues, Path, Control } from 'react-hook-form';
import { ReactNode, JSX, ComponentType } from 'react';
import { ClassValue } from 'clsx';
export { ClassValue } from 'clsx';
/**
* Field type identifier
* Field type identifier.
* Can be built-in types or custom string identifiers.
*/
type FieldType = string;
type FieldType = "text" | "email" | "password" | "number" | "tel" | "url" | "textarea" | "select" | "checkbox" | "radio" | "switch" | "date" | "time" | "datetime" | "file" | "hidden" | "custom" | (string & {});
/**
* Layout type identifier
* Layout type identifier.
*/
type LayoutType = "section" | "grid" | "default" | string;
type LayoutType = "section" | "grid" | "default" | (string & {});
/**
* Variant identifier for component styling
* Variant identifier for component styling.
*/
type Variant = string | undefined;
type Variant = "default" | "compact" | "inline" | (string & {}) | undefined;
/**
* Base field configuration
* Single option for select/radio/checkbox fields.
*/
interface BaseField {
/** Field name (maps to form field) */
name: string;
/** Field type (text, select, checkbox, etc.) */
interface FieldOption<TValue = string> {
/** Display label */
label: string;
/** Option value */
value: TValue;
/** Whether option is disabled */
disabled?: boolean;
/** Optional description */
description?: string;
/** Optional icon */
icon?: ReactNode;
}
/**
* Option group for select fields.
*/
interface FieldOptionGroup<TValue = string> {
/** Group label */
label: string;
/** Group options */
options: FieldOption<TValue>[];
/** Whether group is disabled */
disabled?: boolean;
}
/**
* Base field configuration shared by all field types.
* @template TFieldValues - Form field values type for type-safe field names
*/
interface BaseField<TFieldValues extends FieldValues = FieldValues> {
/** Field name (must be a valid path in form values) */
name: Path<TFieldValues> | (string & {});
/** Field type identifier */
type: FieldType;

@@ -29,2 +58,4 @@ /** Field label */

placeholder?: string;
/** Helper text shown below the field */
helperText?: string;
/** Whether field is disabled */

@@ -34,23 +65,54 @@ disabled?: boolean;

required?: boolean;
/** Whether field is read-only */
readOnly?: boolean;
/** Field variant */
variant?: Variant;
/** Whether field should span full width */
/** Whether field should span full width in grid */
fullWidth?: boolean;
/** Custom CSS class name */
className?: string;
/** Conditional rendering function */
condition?: (formValues: FieldValues) => boolean;
/**
* Conditional rendering function.
* Return true to show the field, false to hide.
*/
condition?: (formValues: TFieldValues) => boolean;
/** Default value */
defaultValue?: any;
/** Additional field-specific props */
[key: string]: any;
defaultValue?: unknown;
/** Options for select/radio/checkbox fields */
options?: (FieldOption | FieldOptionGroup)[];
/** Minimum value (for number/date fields) */
min?: number | string;
/** Maximum value (for number/date fields) */
max?: number | string;
/** Step value (for number fields) */
step?: number;
/** Pattern for validation (regex string) */
pattern?: string;
/** Minimum length */
minLength?: number;
/** Maximum length */
maxLength?: number;
/** Number of rows (for textarea) */
rows?: number;
/** Multiple selection (for select/file) */
multiple?: boolean;
/** Accepted file types (for file input) */
accept?: string;
/** Auto-complete attribute */
autoComplete?: string;
/** Auto-focus on mount */
autoFocus?: boolean;
/** Additional field-specific props (for custom components) */
[key: string]: unknown;
}
/**
* Field component props
* Props passed to field components.
*
* IMPORTANT: Components receive both:
* Components receive both:
* 1. A `field` object with the complete field configuration
* 2. All field properties spread at the top level (via ...field)
* 2. All field properties spread at the top level
*
* Example:
* @template TFieldValues - Form field values type
*
* @example
* ```tsx

@@ -74,17 +136,23 @@ * // Schema

*/
interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> extends BaseField {
interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> extends BaseField<TFieldValues> {
/** Field configuration object (contains all field props) */
field: BaseField;
/** React Hook Form control (for Controller integration) */
control?: Control<TFieldValues> | Control<any>;
field: BaseField<TFieldValues>;
/** React Hook Form control for Controller integration */
control: Control<TFieldValues>;
/** Whether field is globally disabled */
disabled?: boolean;
/** Component variant (e.g., "compact", "default") */
/** Component variant */
variant?: Variant;
}
/**
* Section configuration
* Field component type.
* A React component that renders a form field.
*/
interface Section {
/** Section ID (optional) */
type FieldComponent<TFieldValues extends FieldValues = FieldValues> = ComponentType<FieldComponentProps<TFieldValues>>;
/**
* Section configuration for grouping fields.
* @template TFieldValues - Form field values type
*/
interface Section<TFieldValues extends FieldValues = FieldValues> {
/** Unique section identifier */
id?: string;

@@ -97,12 +165,13 @@ /** Section title */

icon?: ReactNode;
/** Section fields */
fields?: BaseField[];
/** Number of columns in grid */
cols?: number;
/** Custom render function */
render?: (props: {
control?: Control<any>;
disabled?: boolean;
section: Section;
}) => ReactNode;
/** Fields in this section */
fields?: BaseField<TFieldValues>[];
/** Number of columns in grid layout */
cols?: 1 | 2 | 3 | 4 | 5 | 6 | number;
/** Gap between grid items (Tailwind spacing scale) */
gap?: number;
/**
* Custom render function for complete control.
* When provided, fields array is ignored.
*/
render?: (props: SectionRenderProps<TFieldValues>) => ReactNode;
/** Section variant */

@@ -112,14 +181,22 @@ variant?: Variant;

className?: string;
/** Conditional rendering function */
condition?: (control?: Control<any>) => boolean;
/**
* Conditional rendering function.
* Return true to show the section, false to hide.
*/
condition?: (control?: Control<TFieldValues>) => boolean;
/** Whether section is collapsible */
collapsible?: boolean;
/** Default collapsed state */
defaultCollapsed?: boolean;
}
/**
* Form schema configuration
* Props passed to section render function.
*/
interface FormSchema {
/** Form sections */
sections: Section[];
interface SectionRenderProps<TFieldValues extends FieldValues = FieldValues> {
control?: Control<TFieldValues>;
disabled?: boolean;
section: Section<TFieldValues>;
}
/**
* Section layout component props
* Section layout component props.
*/

@@ -137,2 +214,6 @@ interface SectionLayoutProps {

className?: string;
/** Whether section is collapsible */
collapsible?: boolean;
/** Default collapsed state */
defaultCollapsed?: boolean;
/** Children content */

@@ -142,3 +223,3 @@ children: ReactNode;

/**
* Grid layout component props
* Grid layout component props.
*/

@@ -148,2 +229,6 @@ interface GridLayoutProps {

cols?: number;
/** Gap between items */
gap?: number;
/** Custom CSS class name */
className?: string;
/** Children content */

@@ -153,27 +238,54 @@ children: ReactNode;

/**
* Layout component props union
* Default layout component props.
*/
type LayoutComponentProps = SectionLayoutProps | GridLayoutProps;
interface DefaultLayoutProps {
/** Custom CSS class name */
className?: string;
/** Children content */
children: ReactNode;
}
/**
* Field component type
* Union of all layout component props.
*/
type FieldComponent<TFieldValues extends FieldValues = FieldValues> = ComponentType<FieldComponentProps<TFieldValues>>;
type LayoutComponentProps = SectionLayoutProps | GridLayoutProps | DefaultLayoutProps;
/**
* Layout component type
* Layout component type.
*/
type LayoutComponent = ComponentType<any>;
type LayoutComponent<TProps extends LayoutComponentProps = LayoutComponentProps> = ComponentType<TProps>;
/**
* Component registry mapping field types to components
* Complete form schema configuration.
* @template TFieldValues - Form field values type for type-safe schemas
*/
interface FormSchema<TFieldValues extends FieldValues = FieldValues> {
/** Form sections */
sections: Section<TFieldValues>[];
}
/**
* Component registry mapping field types to components.
* Supports variant-specific components via nested objects.
*
* @example
* ```tsx
* const components: ComponentRegistry = {
* text: TextInput,
* select: SelectInput,
* // Variant-specific
* compact: {
* text: CompactTextInput,
* },
* };
* ```
*/
interface ComponentRegistry {
[key: string]: FieldComponent | ComponentRegistry;
[key: string]: FieldComponent<FieldValues> | ComponentRegistry;
}
/**
* Layout registry mapping layout types to components
* Layout registry mapping layout types to components.
* Supports variant-specific layouts via nested objects.
*/
interface LayoutRegistry {
[key: string]: LayoutComponent | LayoutRegistry;
[key: string]: LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps> | LayoutRegistry;
}
/**
* Form system context value
* Form system context value.
*/

@@ -187,3 +299,3 @@ interface FormSystemContextValue {

/**
* Form system provider props
* Form system provider props.
*/

@@ -199,14 +311,35 @@ interface FormSystemProviderProps {

/**
* Form generator props
* FormGenerator component props.
* @template TFieldValues - Form field values type
*/
interface FormGeneratorProps<TFieldValues extends FieldValues = FieldValues> {
/** Form schema */
schema: FormSchema;
/** React Hook Form control */
/** Form schema defining sections and fields */
schema: FormSchema<TFieldValues>;
/** React Hook Form control object */
control?: Control<TFieldValues>;
/** Global disabled state */
/** Global disabled state for all fields */
disabled?: boolean;
/** Global variant */
/** Global variant for all components */
variant?: Variant;
/** Additional CSS class for the root element */
className?: string;
}
/**
* Extract field names from a schema.
*/
type SchemaFieldNames<TSchema extends FormSchema> = TSchema extends FormSchema<infer T> ? keyof T : never;
/**
* Infer field values type from a schema.
*/
type InferSchemaValues<TSchema extends FormSchema> = TSchema extends FormSchema<infer T> ? T : FieldValues;
/**
* Helper type for creating type-safe field definitions.
*/
type DefineField<TFieldValues extends FieldValues, TType extends FieldType = FieldType> = BaseField<TFieldValues> & {
type: TType;
};
/**
* JSX Element return type for components.
*/
type FormElement = JSX.Element | null;

@@ -216,6 +349,7 @@ /**

*
* Renders a form based on a schema, using components injected via FormSystemProvider.
* Supports conditional fields, dynamic layouts, and variants.
* Renders a form based on a schema definition, using components registered
* via FormSystemProvider. Supports conditional fields, dynamic layouts,
* and component variants.
*
* @template TFieldValues - Form field values type
* @template TFieldValues - Form field values type for type safety
*

@@ -227,4 +361,9 @@ * @example

*
* interface FormData {
* firstName: string;
* email: string;
* }
*
* function MyForm() {
* const { control } = useForm();
* const { control } = useForm<FormData>();
*

@@ -247,3 +386,37 @@ * const schema = {

*/
declare function FormGenerator<TFieldValues extends FieldValues = FieldValues>({ schema, control, disabled, variant, }: FormGeneratorProps<TFieldValues>): JSX.Element | null;
declare function FormGenerator<TFieldValues extends FieldValues = FieldValues>({ schema, control, disabled, variant, className, }: FormGeneratorProps<TFieldValues>): FormElement;
interface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {
section: Section<TFieldValues>;
control?: Control<TFieldValues>;
disabled?: boolean;
variant?: Variant;
}
/**
* Renders a single section with its fields.
* Handles conditional rendering and variant resolution.
*/
declare function SectionRenderer<TFieldValues extends FieldValues = FieldValues>({ section, control, disabled, variant, }: SectionRendererProps<TFieldValues>): FormElement;
interface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {
fields?: BaseField<TFieldValues>[];
cols?: number;
gap?: number;
control?: Control<TFieldValues>;
disabled?: boolean;
variant?: Variant;
}
/**
* Renders a grid of fields with specified column layout.
*/
declare function GridRenderer<TFieldValues extends FieldValues = FieldValues>({ fields, cols, gap, control, disabled, variant, }: GridRendererProps<TFieldValues>): FormElement;
interface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {
field: BaseField<TFieldValues>;
control?: Control<TFieldValues>;
disabled?: boolean;
variant?: Variant;
}
/**
* Wraps individual fields with conditional rendering logic.
* Handles field visibility and variant resolution.
*/
declare function FieldWrapper<TFieldValues extends FieldValues = FieldValues>({ field, control, disabled, variant, }: FieldWrapperProps<TFieldValues>): FormElement;

@@ -253,4 +426,4 @@ /**

*
* Provides the component registry to the form system.
* This is the root provider that enables FormGenerator to work.
* Root provider that enables the form system. Provides component and layout
* registries to FormGenerator and its descendants.
*

@@ -264,2 +437,6 @@ * @example

* select: SelectInput,
* // Variant-specific components
* compact: {
* text: CompactTextInput,
* },
* };

@@ -281,5 +458,5 @@ *

*/
declare function FormSystemProvider({ components, layouts, children, }: FormSystemProviderProps): JSX.Element;
declare function FormSystemProvider({ components, layouts, children, }: FormSystemProviderProps): FormElement;
/**
* Hook to access the form system context
* Hook to access the form system context.
*

@@ -295,12 +472,58 @@ * @throws {Error} If used outside FormSystemProvider

declare function useFormSystem(): FormSystemContextValue;
/**
* Hook to get a field component by type and optional variant.
*
* Resolution order:
* 1. Variant-specific component: `components[variant][type]`
* 2. Type-specific component: `components[type]`
* 3. Default component: `components["default"]`
* 4. Text fallback: `components["text"]`
*
* @param type - Field type identifier
* @param variant - Optional variant name
* @returns Field component or fallback
*
* @internal
*/
declare function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent;
/**
* Hook to get a layout component by type and optional variant.
*
* Resolution order:
* 1. Variant-specific layout: `layouts[variant][type]`
* 2. Type-specific layout: `layouts[type]`
* 3. Default layout: `layouts["default"]`
* 4. Built-in default layout
*
* @param type - Layout type identifier
* @param variant - Optional variant name
* @returns Layout component or fallback
*
* @internal
*/
declare function useLayoutComponent(type: LayoutType, variant?: Variant): LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>;
/**
* Utility function to merge Tailwind CSS classes
* Combines clsx and tailwind-merge for conflict-free class merging
* Utility function to merge CSS classes with Tailwind CSS conflict resolution.
*
* @param inputs - Class values to merge
* @returns Merged class string
* Combines `clsx` for conditional class handling with `tailwind-merge`
* for proper Tailwind CSS class conflict resolution.
*
* @param inputs - Class values to merge (strings, arrays, objects, or conditionals)
* @returns Merged and deduplicated class string
*
* @example
* ```tsx
* // Basic usage
* cn("px-2 py-1", "px-4") // "py-1 px-4"
*
* // Conditional classes
* cn("base", isActive && "active", { "disabled": isDisabled })
*
* // Arrays
* cn(["flex", "items-center"], "gap-2")
* ```
*/
declare function cn(...inputs: ClassValue[]): string;
export { type BaseField, type ComponentRegistry, type FieldComponent, type FieldComponentProps, type FieldType, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type Section, type SectionLayoutProps, type Variant, cn, useFormSystem };
export { type BaseField, type ComponentRegistry, type DefaultLayoutProps, type DefineField, type FieldComponent, type FieldComponentProps, type FieldOption, type FieldOptionGroup, type FieldType, FieldWrapper, type FormElement, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, GridRenderer, type InferSchemaValues, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type SchemaFieldNames, type Section, type SectionLayoutProps, type SectionRenderProps, SectionRenderer, type Variant, cn, useFieldComponent, useFormSystem, useLayoutComponent };

@@ -1,24 +0,53 @@

import { FieldValues, Control } from 'react-hook-form';
import { ReactNode, ComponentType } from 'react';
import { FieldValues, Path, Control } from 'react-hook-form';
import { ReactNode, JSX, ComponentType } from 'react';
import { ClassValue } from 'clsx';
export { ClassValue } from 'clsx';
/**
* Field type identifier
* Field type identifier.
* Can be built-in types or custom string identifiers.
*/
type FieldType = string;
type FieldType = "text" | "email" | "password" | "number" | "tel" | "url" | "textarea" | "select" | "checkbox" | "radio" | "switch" | "date" | "time" | "datetime" | "file" | "hidden" | "custom" | (string & {});
/**
* Layout type identifier
* Layout type identifier.
*/
type LayoutType = "section" | "grid" | "default" | string;
type LayoutType = "section" | "grid" | "default" | (string & {});
/**
* Variant identifier for component styling
* Variant identifier for component styling.
*/
type Variant = string | undefined;
type Variant = "default" | "compact" | "inline" | (string & {}) | undefined;
/**
* Base field configuration
* Single option for select/radio/checkbox fields.
*/
interface BaseField {
/** Field name (maps to form field) */
name: string;
/** Field type (text, select, checkbox, etc.) */
interface FieldOption<TValue = string> {
/** Display label */
label: string;
/** Option value */
value: TValue;
/** Whether option is disabled */
disabled?: boolean;
/** Optional description */
description?: string;
/** Optional icon */
icon?: ReactNode;
}
/**
* Option group for select fields.
*/
interface FieldOptionGroup<TValue = string> {
/** Group label */
label: string;
/** Group options */
options: FieldOption<TValue>[];
/** Whether group is disabled */
disabled?: boolean;
}
/**
* Base field configuration shared by all field types.
* @template TFieldValues - Form field values type for type-safe field names
*/
interface BaseField<TFieldValues extends FieldValues = FieldValues> {
/** Field name (must be a valid path in form values) */
name: Path<TFieldValues> | (string & {});
/** Field type identifier */
type: FieldType;

@@ -29,2 +58,4 @@ /** Field label */

placeholder?: string;
/** Helper text shown below the field */
helperText?: string;
/** Whether field is disabled */

@@ -34,23 +65,54 @@ disabled?: boolean;

required?: boolean;
/** Whether field is read-only */
readOnly?: boolean;
/** Field variant */
variant?: Variant;
/** Whether field should span full width */
/** Whether field should span full width in grid */
fullWidth?: boolean;
/** Custom CSS class name */
className?: string;
/** Conditional rendering function */
condition?: (formValues: FieldValues) => boolean;
/**
* Conditional rendering function.
* Return true to show the field, false to hide.
*/
condition?: (formValues: TFieldValues) => boolean;
/** Default value */
defaultValue?: any;
/** Additional field-specific props */
[key: string]: any;
defaultValue?: unknown;
/** Options for select/radio/checkbox fields */
options?: (FieldOption | FieldOptionGroup)[];
/** Minimum value (for number/date fields) */
min?: number | string;
/** Maximum value (for number/date fields) */
max?: number | string;
/** Step value (for number fields) */
step?: number;
/** Pattern for validation (regex string) */
pattern?: string;
/** Minimum length */
minLength?: number;
/** Maximum length */
maxLength?: number;
/** Number of rows (for textarea) */
rows?: number;
/** Multiple selection (for select/file) */
multiple?: boolean;
/** Accepted file types (for file input) */
accept?: string;
/** Auto-complete attribute */
autoComplete?: string;
/** Auto-focus on mount */
autoFocus?: boolean;
/** Additional field-specific props (for custom components) */
[key: string]: unknown;
}
/**
* Field component props
* Props passed to field components.
*
* IMPORTANT: Components receive both:
* Components receive both:
* 1. A `field` object with the complete field configuration
* 2. All field properties spread at the top level (via ...field)
* 2. All field properties spread at the top level
*
* Example:
* @template TFieldValues - Form field values type
*
* @example
* ```tsx

@@ -74,17 +136,23 @@ * // Schema

*/
interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> extends BaseField {
interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> extends BaseField<TFieldValues> {
/** Field configuration object (contains all field props) */
field: BaseField;
/** React Hook Form control (for Controller integration) */
control?: Control<TFieldValues> | Control<any>;
field: BaseField<TFieldValues>;
/** React Hook Form control for Controller integration */
control: Control<TFieldValues>;
/** Whether field is globally disabled */
disabled?: boolean;
/** Component variant (e.g., "compact", "default") */
/** Component variant */
variant?: Variant;
}
/**
* Section configuration
* Field component type.
* A React component that renders a form field.
*/
interface Section {
/** Section ID (optional) */
type FieldComponent<TFieldValues extends FieldValues = FieldValues> = ComponentType<FieldComponentProps<TFieldValues>>;
/**
* Section configuration for grouping fields.
* @template TFieldValues - Form field values type
*/
interface Section<TFieldValues extends FieldValues = FieldValues> {
/** Unique section identifier */
id?: string;

@@ -97,12 +165,13 @@ /** Section title */

icon?: ReactNode;
/** Section fields */
fields?: BaseField[];
/** Number of columns in grid */
cols?: number;
/** Custom render function */
render?: (props: {
control?: Control<any>;
disabled?: boolean;
section: Section;
}) => ReactNode;
/** Fields in this section */
fields?: BaseField<TFieldValues>[];
/** Number of columns in grid layout */
cols?: 1 | 2 | 3 | 4 | 5 | 6 | number;
/** Gap between grid items (Tailwind spacing scale) */
gap?: number;
/**
* Custom render function for complete control.
* When provided, fields array is ignored.
*/
render?: (props: SectionRenderProps<TFieldValues>) => ReactNode;
/** Section variant */

@@ -112,14 +181,22 @@ variant?: Variant;

className?: string;
/** Conditional rendering function */
condition?: (control?: Control<any>) => boolean;
/**
* Conditional rendering function.
* Return true to show the section, false to hide.
*/
condition?: (control?: Control<TFieldValues>) => boolean;
/** Whether section is collapsible */
collapsible?: boolean;
/** Default collapsed state */
defaultCollapsed?: boolean;
}
/**
* Form schema configuration
* Props passed to section render function.
*/
interface FormSchema {
/** Form sections */
sections: Section[];
interface SectionRenderProps<TFieldValues extends FieldValues = FieldValues> {
control?: Control<TFieldValues>;
disabled?: boolean;
section: Section<TFieldValues>;
}
/**
* Section layout component props
* Section layout component props.
*/

@@ -137,2 +214,6 @@ interface SectionLayoutProps {

className?: string;
/** Whether section is collapsible */
collapsible?: boolean;
/** Default collapsed state */
defaultCollapsed?: boolean;
/** Children content */

@@ -142,3 +223,3 @@ children: ReactNode;

/**
* Grid layout component props
* Grid layout component props.
*/

@@ -148,2 +229,6 @@ interface GridLayoutProps {

cols?: number;
/** Gap between items */
gap?: number;
/** Custom CSS class name */
className?: string;
/** Children content */

@@ -153,27 +238,54 @@ children: ReactNode;

/**
* Layout component props union
* Default layout component props.
*/
type LayoutComponentProps = SectionLayoutProps | GridLayoutProps;
interface DefaultLayoutProps {
/** Custom CSS class name */
className?: string;
/** Children content */
children: ReactNode;
}
/**
* Field component type
* Union of all layout component props.
*/
type FieldComponent<TFieldValues extends FieldValues = FieldValues> = ComponentType<FieldComponentProps<TFieldValues>>;
type LayoutComponentProps = SectionLayoutProps | GridLayoutProps | DefaultLayoutProps;
/**
* Layout component type
* Layout component type.
*/
type LayoutComponent = ComponentType<any>;
type LayoutComponent<TProps extends LayoutComponentProps = LayoutComponentProps> = ComponentType<TProps>;
/**
* Component registry mapping field types to components
* Complete form schema configuration.
* @template TFieldValues - Form field values type for type-safe schemas
*/
interface FormSchema<TFieldValues extends FieldValues = FieldValues> {
/** Form sections */
sections: Section<TFieldValues>[];
}
/**
* Component registry mapping field types to components.
* Supports variant-specific components via nested objects.
*
* @example
* ```tsx
* const components: ComponentRegistry = {
* text: TextInput,
* select: SelectInput,
* // Variant-specific
* compact: {
* text: CompactTextInput,
* },
* };
* ```
*/
interface ComponentRegistry {
[key: string]: FieldComponent | ComponentRegistry;
[key: string]: FieldComponent<FieldValues> | ComponentRegistry;
}
/**
* Layout registry mapping layout types to components
* Layout registry mapping layout types to components.
* Supports variant-specific layouts via nested objects.
*/
interface LayoutRegistry {
[key: string]: LayoutComponent | LayoutRegistry;
[key: string]: LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps> | LayoutRegistry;
}
/**
* Form system context value
* Form system context value.
*/

@@ -187,3 +299,3 @@ interface FormSystemContextValue {

/**
* Form system provider props
* Form system provider props.
*/

@@ -199,14 +311,35 @@ interface FormSystemProviderProps {

/**
* Form generator props
* FormGenerator component props.
* @template TFieldValues - Form field values type
*/
interface FormGeneratorProps<TFieldValues extends FieldValues = FieldValues> {
/** Form schema */
schema: FormSchema;
/** React Hook Form control */
/** Form schema defining sections and fields */
schema: FormSchema<TFieldValues>;
/** React Hook Form control object */
control?: Control<TFieldValues>;
/** Global disabled state */
/** Global disabled state for all fields */
disabled?: boolean;
/** Global variant */
/** Global variant for all components */
variant?: Variant;
/** Additional CSS class for the root element */
className?: string;
}
/**
* Extract field names from a schema.
*/
type SchemaFieldNames<TSchema extends FormSchema> = TSchema extends FormSchema<infer T> ? keyof T : never;
/**
* Infer field values type from a schema.
*/
type InferSchemaValues<TSchema extends FormSchema> = TSchema extends FormSchema<infer T> ? T : FieldValues;
/**
* Helper type for creating type-safe field definitions.
*/
type DefineField<TFieldValues extends FieldValues, TType extends FieldType = FieldType> = BaseField<TFieldValues> & {
type: TType;
};
/**
* JSX Element return type for components.
*/
type FormElement = JSX.Element | null;

@@ -216,6 +349,7 @@ /**

*
* Renders a form based on a schema, using components injected via FormSystemProvider.
* Supports conditional fields, dynamic layouts, and variants.
* Renders a form based on a schema definition, using components registered
* via FormSystemProvider. Supports conditional fields, dynamic layouts,
* and component variants.
*
* @template TFieldValues - Form field values type
* @template TFieldValues - Form field values type for type safety
*

@@ -227,4 +361,9 @@ * @example

*
* interface FormData {
* firstName: string;
* email: string;
* }
*
* function MyForm() {
* const { control } = useForm();
* const { control } = useForm<FormData>();
*

@@ -247,3 +386,37 @@ * const schema = {

*/
declare function FormGenerator<TFieldValues extends FieldValues = FieldValues>({ schema, control, disabled, variant, }: FormGeneratorProps<TFieldValues>): JSX.Element | null;
declare function FormGenerator<TFieldValues extends FieldValues = FieldValues>({ schema, control, disabled, variant, className, }: FormGeneratorProps<TFieldValues>): FormElement;
interface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {
section: Section<TFieldValues>;
control?: Control<TFieldValues>;
disabled?: boolean;
variant?: Variant;
}
/**
* Renders a single section with its fields.
* Handles conditional rendering and variant resolution.
*/
declare function SectionRenderer<TFieldValues extends FieldValues = FieldValues>({ section, control, disabled, variant, }: SectionRendererProps<TFieldValues>): FormElement;
interface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {
fields?: BaseField<TFieldValues>[];
cols?: number;
gap?: number;
control?: Control<TFieldValues>;
disabled?: boolean;
variant?: Variant;
}
/**
* Renders a grid of fields with specified column layout.
*/
declare function GridRenderer<TFieldValues extends FieldValues = FieldValues>({ fields, cols, gap, control, disabled, variant, }: GridRendererProps<TFieldValues>): FormElement;
interface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {
field: BaseField<TFieldValues>;
control?: Control<TFieldValues>;
disabled?: boolean;
variant?: Variant;
}
/**
* Wraps individual fields with conditional rendering logic.
* Handles field visibility and variant resolution.
*/
declare function FieldWrapper<TFieldValues extends FieldValues = FieldValues>({ field, control, disabled, variant, }: FieldWrapperProps<TFieldValues>): FormElement;

@@ -253,4 +426,4 @@ /**

*
* Provides the component registry to the form system.
* This is the root provider that enables FormGenerator to work.
* Root provider that enables the form system. Provides component and layout
* registries to FormGenerator and its descendants.
*

@@ -264,2 +437,6 @@ * @example

* select: SelectInput,
* // Variant-specific components
* compact: {
* text: CompactTextInput,
* },
* };

@@ -281,5 +458,5 @@ *

*/
declare function FormSystemProvider({ components, layouts, children, }: FormSystemProviderProps): JSX.Element;
declare function FormSystemProvider({ components, layouts, children, }: FormSystemProviderProps): FormElement;
/**
* Hook to access the form system context
* Hook to access the form system context.
*

@@ -295,12 +472,58 @@ * @throws {Error} If used outside FormSystemProvider

declare function useFormSystem(): FormSystemContextValue;
/**
* Hook to get a field component by type and optional variant.
*
* Resolution order:
* 1. Variant-specific component: `components[variant][type]`
* 2. Type-specific component: `components[type]`
* 3. Default component: `components["default"]`
* 4. Text fallback: `components["text"]`
*
* @param type - Field type identifier
* @param variant - Optional variant name
* @returns Field component or fallback
*
* @internal
*/
declare function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent;
/**
* Hook to get a layout component by type and optional variant.
*
* Resolution order:
* 1. Variant-specific layout: `layouts[variant][type]`
* 2. Type-specific layout: `layouts[type]`
* 3. Default layout: `layouts["default"]`
* 4. Built-in default layout
*
* @param type - Layout type identifier
* @param variant - Optional variant name
* @returns Layout component or fallback
*
* @internal
*/
declare function useLayoutComponent(type: LayoutType, variant?: Variant): LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>;
/**
* Utility function to merge Tailwind CSS classes
* Combines clsx and tailwind-merge for conflict-free class merging
* Utility function to merge CSS classes with Tailwind CSS conflict resolution.
*
* @param inputs - Class values to merge
* @returns Merged class string
* Combines `clsx` for conditional class handling with `tailwind-merge`
* for proper Tailwind CSS class conflict resolution.
*
* @param inputs - Class values to merge (strings, arrays, objects, or conditionals)
* @returns Merged and deduplicated class string
*
* @example
* ```tsx
* // Basic usage
* cn("px-2 py-1", "px-4") // "py-1 px-4"
*
* // Conditional classes
* cn("base", isActive && "active", { "disabled": isDisabled })
*
* // Arrays
* cn(["flex", "items-center"], "gap-2")
* ```
*/
declare function cn(...inputs: ClassValue[]): string;
export { type BaseField, type ComponentRegistry, type FieldComponent, type FieldComponentProps, type FieldType, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type Section, type SectionLayoutProps, type Variant, cn, useFormSystem };
export { type BaseField, type ComponentRegistry, type DefaultLayoutProps, type DefineField, type FieldComponent, type FieldComponentProps, type FieldOption, type FieldOptionGroup, type FieldType, FieldWrapper, type FormElement, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, GridRenderer, type InferSchemaValues, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type SchemaFieldNames, type Section, type SectionLayoutProps, type SectionRenderProps, SectionRenderer, type Variant, cn, useFieldComponent, useFormSystem, useLayoutComponent };
import { useFormContext, useWatch } from 'react-hook-form';
import { createContext, useMemo, useContext } from 'react';
import { jsx, jsxs } from 'react/jsx-runtime';
import { jsx } from 'react/jsx-runtime';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
// src/FormGenerator.tsx
var FormSystemContext = createContext(null);
FormSystemContext.displayName = "FormSystemContext";
function FormSystemProvider({

@@ -16,4 +16,4 @@ components,

() => ({
components: components || {},
layouts: layouts || {}
components: components ?? {},
layouts: layouts ?? {}
}),

@@ -27,3 +27,5 @@ [components, layouts]

if (!context) {
throw new Error("useFormSystem must be used within a FormSystemProvider");
throw new Error(
"[FormKit] useFormSystem must be used within a FormSystemProvider. Make sure to wrap your form components with <FormSystemProvider>."
);
}

@@ -34,32 +36,48 @@ return context;

const { components } = useFormSystem();
if (variant && typeof components[variant] === "object" && components[variant]) {
if (variant && typeof components[variant] === "object" && components[variant] !== null) {
const variantComponents = components[variant];
if (variantComponents[type]) {
return variantComponents[type];
const variantComponent = variantComponents[type];
if (variantComponent) {
return variantComponent;
}
}
const Component = components[type] || components["default"] || components["text"];
if (!Component) {
if (process.env.NODE_ENV === "development") {
console.warn(`FormKit: No component found for type "${type}" (variant: ${variant})`);
return () => /* @__PURE__ */ jsxs("div", { style: { color: "red", padding: 4, border: "1px dashed red" }, children: [
"Missing: ",
type
] });
}
return () => null;
const typeComponent = components[type];
if (typeComponent && typeof typeComponent === "function") {
return typeComponent;
}
return Component;
const defaultComponent = components["default"];
if (defaultComponent && typeof defaultComponent === "function") {
return defaultComponent;
}
const textComponent = components["text"];
if (textComponent && typeof textComponent === "function") {
return textComponent;
}
return NullComponent;
}
function useLayoutComponent(type, variant) {
const { layouts } = useFormSystem();
if (variant && typeof layouts[variant] === "object" && layouts[variant]) {
if (variant && typeof layouts[variant] === "object" && layouts[variant] !== null) {
const variantLayouts = layouts[variant];
if (variantLayouts[type]) {
return variantLayouts[type];
const variantLayout = variantLayouts[type];
if (variantLayout) {
return variantLayout;
}
}
return layouts[type] || layouts["default"] || DefaultLayout;
const typeLayout = layouts[type];
if (typeLayout && typeof typeLayout === "function") {
return typeLayout;
}
const defaultLayout = layouts["default"];
if (defaultLayout && typeof defaultLayout === "function") {
return defaultLayout;
}
return DefaultLayout;
}
var DefaultLayout = ({ children, className }) => /* @__PURE__ */ jsx("div", { className, children });
function DefaultLayout({ children, className }) {
return /* @__PURE__ */ jsx("div", { className, children });
}
function NullComponent() {
return null;
}
function cn(...inputs) {

@@ -72,17 +90,31 @@ return twMerge(clsx(inputs));

disabled = false,
variant
variant,
className
}) {
const formContext = useFormContext();
const activeControl = control || formContext?.control;
if (!schema?.sections) return null;
return /* @__PURE__ */ jsx("div", { className: cn("form-generator-root", variant && `form-variant-${variant}`), children: schema.sections.map((section, idx) => /* @__PURE__ */ jsx(
SectionRenderer,
const activeControl = control ?? formContext?.control;
if (!schema?.sections || schema.sections.length === 0) {
return null;
}
return /* @__PURE__ */ jsx(
"div",
{
section,
control: activeControl,
disabled,
variant
},
section.id || idx
)) });
className: cn(
"formkit-root",
variant && `formkit-variant-${variant}`,
className
),
"data-formkit-root": "",
children: schema.sections.map((section, index) => /* @__PURE__ */ jsx(
SectionRenderer,
{
section,
control: activeControl,
disabled,
variant
},
section.id ?? `section-${index}`
))
}
);
}

@@ -95,3 +127,3 @@ function SectionRenderer({

}) {
const activeVariant = section.variant || variant;
const activeVariant = section.variant ?? variant;
const SectionLayout = useLayoutComponent("section", activeVariant);

@@ -109,11 +141,20 @@ if (section.condition && !section.condition(control)) {

className: section.className,
children: section.render ? section.render({ control, disabled, section }) : /* @__PURE__ */ jsx(
GridRenderer,
{
fields: section.fields,
cols: section.cols,
control,
disabled,
variant: activeVariant
}
collapsible: section.collapsible,
defaultCollapsed: section.defaultCollapsed,
children: section.render ? (
// Custom render function
section.render({ control, disabled, section })
) : (
// Standard grid rendering
/* @__PURE__ */ jsx(
GridRenderer,
{
fields: section.fields,
cols: section.cols,
gap: section.gap,
control,
disabled,
variant: activeVariant
}
)
)

@@ -126,2 +167,3 @@ }

cols = 1,
gap,
control,

@@ -132,4 +174,6 @@ disabled,

const GridLayout = useLayoutComponent("grid", variant);
if (!fields || fields.length === 0) return null;
return /* @__PURE__ */ jsx(GridLayout, { cols, children: fields.map((field, idx) => /* @__PURE__ */ jsx(
if (!fields || fields.length === 0) {
return null;
}
return /* @__PURE__ */ jsx(GridLayout, { cols, gap, children: fields.map((field, index) => /* @__PURE__ */ jsx(
FieldWrapper,

@@ -142,3 +186,3 @@ {

},
field.name || idx
field.name || `field-${index}`
)) });

@@ -156,19 +200,34 @@ }

}
const activeVariant = field.variant || variant;
const activeVariant = field.variant ?? variant;
const FieldComponent = useFieldComponent(field.type, activeVariant);
if (!FieldComponent) return null;
return /* @__PURE__ */ jsx("div", { className: field.fullWidth ? "col-span-full" : "", children: /* @__PURE__ */ jsx(
FieldComponent,
if (!FieldComponent) {
return null;
}
const isDisabled = disabled || field.disabled;
return /* @__PURE__ */ jsx(
"div",
{
field,
control,
disabled: disabled || field.disabled,
variant: activeVariant,
...field
className: cn(
"formkit-field",
field.fullWidth && "col-span-full",
field.className
),
"data-formkit-field": field.name,
"data-field-type": field.type,
children: /* @__PURE__ */ jsx(
FieldComponent,
{
...field,
field,
control,
disabled: isDisabled,
variant: activeVariant
}
)
}
) });
);
}
export { FormGenerator, FormSystemProvider, cn, useFormSystem };
export { FieldWrapper, FormGenerator, FormSystemProvider, GridRenderer, SectionRenderer, cn, useFieldComponent, useFormSystem, useLayoutComponent };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n} from \"./types\";\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Provides the component registry to the form system.\r\n * This is the root provider that enables FormGenerator to work.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): JSX.Element {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components || {},\r\n layouts: layouts || {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n/**\r\n * Hook to access the form system context\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\"useFormSystem must be used within a FormSystemProvider\");\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Helper to resolve a component for a specific field type and variant\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant]) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n if (variantComponents[type]) {\r\n return variantComponents[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard component\r\n const Component =\r\n (components[type] as FieldComponent) ||\r\n (components[\"default\"] as FieldComponent) ||\r\n (components[\"text\"] as FieldComponent);\r\n\r\n if (!Component) {\r\n // Library-safe fallback: Don't crash, just warn and render nothing or a placeholder\r\n if (process.env.NODE_ENV === \"development\") {\r\n console.warn(`FormKit: No component found for type \"${type}\" (variant: ${variant})`);\r\n return () => (\r\n <div style={{ color: \"red\", padding: 4, border: \"1px dashed red\" }}>\r\n Missing: {type}\r\n </div>\r\n );\r\n }\r\n return () => null;\r\n }\r\n\r\n return Component;\r\n}\r\n\r\n/**\r\n * Helper to resolve a layout component\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(type: LayoutType, variant?: Variant): LayoutComponent {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant]) {\r\n const variantLayouts = layouts[variant] as Record<string, LayoutComponent>;\r\n if (variantLayouts[type]) {\r\n return variantLayouts[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard layout\r\n return (\r\n (layouts[type] as LayoutComponent) ||\r\n (layouts[\"default\"] as LayoutComponent) ||\r\n DefaultLayout\r\n );\r\n}\r\n\r\n/**\r\n * Default layout component - simple div wrapper\r\n */\r\nconst DefaultLayout: LayoutComponent = ({ children, className }: any) => (\r\n <div className={className}>{children}</div>\r\n);\r\n","import { type ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\n/**\r\n * Utility function to merge Tailwind CSS classes\r\n * Combines clsx and tailwind-merge for conflict-free class merging\r\n *\r\n * @param inputs - Class values to merge\r\n * @returns Merged class string\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type { FormGeneratorProps, Section, BaseField, Variant } from \"./types\";\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema, using components injected via FormSystemProvider.\r\n * Supports conditional fields, dynamic layouts, and variants.\r\n *\r\n * @template TFieldValues - Form field values type\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n}: FormGeneratorProps<TFieldValues>): JSX.Element | null {\r\n // Use provided control or fall back to context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control || formContext?.control;\r\n\r\n if (!schema?.sections) return null;\r\n\r\n return (\r\n <div className={cn(\"form-generator-root\", variant && `form-variant-${variant}`)}>\r\n {schema.sections.map((section, idx) => (\r\n <SectionRenderer\r\n key={section.id || idx}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Section Renderer Props\r\n */\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): JSX.Element | null {\r\n // Allow section to override variant, or use global variant\r\n const activeVariant = section.variant || variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check condition if present\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n >\r\n {section.render ? (\r\n section.render({ control, disabled, section })\r\n ) : (\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Grid Renderer Props\r\n */\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField[];\r\n cols?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): JSX.Element | null {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) return null;\r\n\r\n return (\r\n <GridLayout cols={cols}>\r\n {fields.map((field, idx) => (\r\n <FieldWrapper\r\n key={field.name || idx}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Field Wrapper Props\r\n */\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single field wrapper\r\n * Handles conditional rendering and field visibility\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): JSX.Element | null {\r\n // Watch values for conditional rendering\r\n const formValues = useWatch({ control });\r\n\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Allow field to override variant\r\n const activeVariant = field.variant || variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) return null;\r\n\r\n return (\r\n <div className={field.fullWidth ? \"col-span-full\" : \"\"}>\r\n <FieldComponent\r\n field={field}\r\n control={control as any}\r\n disabled={disabled || field.disabled}\r\n variant={activeVariant}\r\n // Pass all field props to the component\r\n {...field}\r\n />\r\n </div>\r\n );\r\n}\r\n"]}
{"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n SectionLayoutProps,\r\n GridLayoutProps,\r\n DefaultLayoutProps,\r\n FormElement,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// Context\r\n// ============================================================================\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n// Display name for React DevTools\r\nFormSystemContext.displayName = \"FormSystemContext\";\r\n\r\n// ============================================================================\r\n// Provider\r\n// ============================================================================\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Root provider that enables the form system. Provides component and layout\r\n * registries to FormGenerator and its descendants.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * // Variant-specific components\r\n * compact: {\r\n * text: CompactTextInput,\r\n * },\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): FormElement {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components ?? {},\r\n layouts: layouts ?? {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n// ============================================================================\r\n// Hooks\r\n// ============================================================================\r\n\r\n/**\r\n * Hook to access the form system context.\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\r\n \"[FormKit] useFormSystem must be used within a FormSystemProvider. \" +\r\n \"Make sure to wrap your form components with <FormSystemProvider>.\"\r\n );\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Hook to get a field component by type and optional variant.\r\n *\r\n * Resolution order:\r\n * 1. Variant-specific component: `components[variant][type]`\r\n * 2. Type-specific component: `components[type]`\r\n * 3. Default component: `components[\"default\"]`\r\n * 4. Text fallback: `components[\"text\"]`\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant] !== null) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n const variantComponent = variantComponents[type];\r\n if (variantComponent) {\r\n return variantComponent;\r\n }\r\n }\r\n\r\n // 2. Try type-specific component\r\n const typeComponent = components[type] as FieldComponent | undefined;\r\n if (typeComponent && typeof typeComponent === \"function\") {\r\n return typeComponent;\r\n }\r\n\r\n // 3. Try default component\r\n const defaultComponent = components[\"default\"] as FieldComponent | undefined;\r\n if (defaultComponent && typeof defaultComponent === \"function\") {\r\n return defaultComponent;\r\n }\r\n\r\n // 4. Try text fallback\r\n const textComponent = components[\"text\"] as FieldComponent | undefined;\r\n if (textComponent && typeof textComponent === \"function\") {\r\n return textComponent;\r\n }\r\n\r\n // 5. Development warning and placeholder\r\n if (process.env.NODE_ENV !== \"production\") {\r\n console.warn(\r\n `[FormKit] No component found for type \"${type}\"${variant ? ` (variant: \"${variant}\")` : \"\"}. ` +\r\n \"Register a component for this type in your FormSystemProvider.\"\r\n );\r\n return MissingFieldComponent;\r\n }\r\n\r\n // Production: silent fallback\r\n return NullComponent;\r\n}\r\n\r\n/**\r\n * Hook to get a layout component by type and optional variant.\r\n *\r\n * Resolution order:\r\n * 1. Variant-specific layout: `layouts[variant][type]`\r\n * 2. Type-specific layout: `layouts[type]`\r\n * 3. Default layout: `layouts[\"default\"]`\r\n * 4. Built-in default layout\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(\r\n type: LayoutType,\r\n variant?: Variant\r\n): LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps> {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant] !== null) {\r\n const variantLayouts = layouts[variant] as Record<\r\n string,\r\n LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n >;\r\n const variantLayout = variantLayouts[type];\r\n if (variantLayout) {\r\n return variantLayout;\r\n }\r\n }\r\n\r\n // 2. Try type-specific layout\r\n const typeLayout = layouts[type] as\r\n | LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n | undefined;\r\n if (typeLayout && typeof typeLayout === \"function\") {\r\n return typeLayout;\r\n }\r\n\r\n // 3. Try default layout\r\n const defaultLayout = layouts[\"default\"] as\r\n | LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n | undefined;\r\n if (defaultLayout && typeof defaultLayout === \"function\") {\r\n return defaultLayout;\r\n }\r\n\r\n // 4. Built-in default layout\r\n return DefaultLayout;\r\n}\r\n\r\n// ============================================================================\r\n// Fallback Components\r\n// ============================================================================\r\n\r\n/**\r\n * Default layout component - simple div wrapper.\r\n * Used when no layout is registered.\r\n */\r\nfunction DefaultLayout({ children, className }: DefaultLayoutProps): FormElement {\r\n return <div className={className}>{children}</div>;\r\n}\r\n\r\n/**\r\n * Null component for production fallback.\r\n */\r\nfunction NullComponent(): FormElement {\r\n return null;\r\n}\r\n\r\n/**\r\n * Development placeholder for missing field components.\r\n */\r\nfunction MissingFieldComponent({ field }: { field: { type: string; name: string } }): FormElement {\r\n return (\r\n <div\r\n style={{\r\n color: \"#dc2626\",\r\n padding: \"8px 12px\",\r\n border: \"1px dashed #dc2626\",\r\n borderRadius: \"4px\",\r\n fontSize: \"12px\",\r\n fontFamily: \"monospace\",\r\n backgroundColor: \"#fef2f2\",\r\n }}\r\n >\r\n Missing component: <strong>{field.type}</strong> (field: {field.name})\r\n </div>\r\n );\r\n}\r\n","import { clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\nimport type { ClassValue } from \"clsx\";\r\n\r\n/**\r\n * Utility function to merge CSS classes with Tailwind CSS conflict resolution.\r\n *\r\n * Combines `clsx` for conditional class handling with `tailwind-merge`\r\n * for proper Tailwind CSS class conflict resolution.\r\n *\r\n * @param inputs - Class values to merge (strings, arrays, objects, or conditionals)\r\n * @returns Merged and deduplicated class string\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage\r\n * cn(\"px-2 py-1\", \"px-4\") // \"py-1 px-4\"\r\n *\r\n * // Conditional classes\r\n * cn(\"base\", isActive && \"active\", { \"disabled\": isDisabled })\r\n *\r\n * // Arrays\r\n * cn([\"flex\", \"items-center\"], \"gap-2\")\r\n * ```\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n\r\n/**\r\n * Re-export ClassValue type for consumers who need it.\r\n */\r\nexport type { ClassValue };\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type {\r\n FormGeneratorProps,\r\n Section,\r\n BaseField,\r\n Variant,\r\n FormElement,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// FormGenerator Component\r\n// ============================================================================\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema definition, using components registered\r\n * via FormSystemProvider. Supports conditional fields, dynamic layouts,\r\n * and component variants.\r\n *\r\n * @template TFieldValues - Form field values type for type safety\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * interface FormData {\r\n * firstName: string;\r\n * email: string;\r\n * }\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm<FormData>();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n className,\r\n}: FormGeneratorProps<TFieldValues>): FormElement {\r\n // Use provided control or fall back to FormProvider context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control ?? formContext?.control;\r\n\r\n // Early return if no schema\r\n if (!schema?.sections || schema.sections.length === 0) {\r\n return null;\r\n }\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"formkit-root\",\r\n variant && `formkit-variant-${variant}`,\r\n className\r\n )}\r\n data-formkit-root=\"\"\r\n >\r\n {schema.sections.map((section, index) => (\r\n <SectionRenderer\r\n key={section.id ?? `section-${index}`}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Section Renderer\r\n// ============================================================================\r\n\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section<TFieldValues>;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields.\r\n * Handles conditional rendering and variant resolution.\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): FormElement {\r\n // Section can override variant\r\n const activeVariant = section.variant ?? variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check section condition\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n collapsible={section.collapsible}\r\n defaultCollapsed={section.defaultCollapsed}\r\n >\r\n {section.render ? (\r\n // Custom render function\r\n section.render({ control, disabled, section })\r\n ) : (\r\n // Standard grid rendering\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n gap={section.gap}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Grid Renderer\r\n// ============================================================================\r\n\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField<TFieldValues>[];\r\n cols?: number;\r\n gap?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout.\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n gap,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): FormElement {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) {\r\n return null;\r\n }\r\n\r\n return (\r\n <GridLayout cols={cols} gap={gap}>\r\n {fields.map((field, index) => (\r\n <FieldWrapper\r\n key={field.name || `field-${index}`}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Field Wrapper\r\n// ============================================================================\r\n\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField<TFieldValues>;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Wraps individual fields with conditional rendering logic.\r\n * Handles field visibility and variant resolution.\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): FormElement {\r\n // Watch form values for conditional rendering\r\n const formValues = useWatch({ control }) as TFieldValues;\r\n\r\n // Check field condition\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Field can override variant\r\n const activeVariant = field.variant ?? variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) {\r\n return null;\r\n }\r\n\r\n // Merge disabled states\r\n const isDisabled = disabled || field.disabled;\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"formkit-field\",\r\n field.fullWidth && \"col-span-full\",\r\n field.className\r\n )}\r\n data-formkit-field={field.name}\r\n data-field-type={field.type}\r\n >\r\n <FieldComponent\r\n {...(field as BaseField<FieldValues>)}\r\n field={field as BaseField<FieldValues>}\r\n control={control as Control<FieldValues>}\r\n disabled={isDisabled}\r\n variant={activeVariant}\r\n />\r\n </div>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Named Exports for Subcomponents (for advanced use cases)\r\n// ============================================================================\r\n\r\nexport { SectionRenderer, GridRenderer, FieldWrapper };\r\n"]}
{
"name": "@classytic/formkit",
"version": "1.0.2",
"version": "1.0.3",
"description": "Headless, type-safe form generation engine for React. Schema-driven with full TypeScript support.",
"author": "Classytic",
"license": "MIT",
"type": "module",
"sideEffects": false,
"main": "./dist/index.cjs",

@@ -13,3 +16,13 @@ "module": "./dist/index.js",

"import": "./dist/index.js",
"require": "./dist/index.cjs"
"require": "./dist/index.cjs",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"typesVersions": {
"*": {
"*": [
"./dist/*",
"./dist/index.d.ts"
]
}

@@ -20,3 +33,4 @@ },

"README.md",
"LICENSE"
"LICENSE",
"CHANGELOG.md"
],

@@ -26,6 +40,9 @@ "scripts": {

"dev": "tsup --watch",
"lint": "tsc --noEmit",
"test": "node --test tests/*.test.js",
"test:watch": "node --test --watch tests/*.test.js",
"prepublishOnly": "npm run lint && npm run build",
"typecheck": "tsc --noEmit",
"lint": "eslint src --ext .ts,.tsx",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"clean": "rimraf dist",
"prepublishOnly": "npm run clean && npm run typecheck && npm run build && npm run test",
"version:patch": "npm version patch",

@@ -41,5 +58,7 @@ "version:minor": "npm version minor",

"react",
"react19",
"react-19",
"react-18",
"forms",
"form-generator",
"form-builder",
"headless",

@@ -54,6 +73,5 @@ "schema-driven",

"dynamic-forms",
"conditional-fields"
"conditional-fields",
"form-schema"
],
"author": "Classytic",
"license": "MIT",
"repository": {

@@ -68,6 +86,11 @@ "type": "git",

"peerDependencies": {
"react": ">=18.3.0",
"react-dom": ">=18.3.0",
"react-hook-form": ">=7.50.0"
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"react-hook-form": "^7.50.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
},
"dependencies": {

@@ -78,10 +101,19 @@ "clsx": "^2.1.1",

"devDependencies": {
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.0.0",
"@types/node": "^22.8.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitejs/plugin-react": "^4.3.0",
"eslint": "^9.0.0",
"happy-dom": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.53.2",
"rimraf": "^6.0.0",
"tsup": "^8.3.5",
"typescript": "^5.6.3"
"typescript": "^5.6.3",
"vitest": "^2.0.0"
},

@@ -92,4 +124,5 @@ "engines": {

"publishConfig": {
"access": "public"
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
+346
-73

@@ -7,3 +7,15 @@ # @classytic/formkit

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
## Features
- ðŸŽŊ **Headless** - Bring your own UI components (Shadcn, MUI, Chakra, etc.)
- 📝 **Schema-driven** - Define forms with JSON/TypeScript schemas
- 🔒 **Type-safe** - Full TypeScript support with generics
- ⚡ **React Hook Form** - Built on top of the best form library
- ðŸŽĻ **Variants** - Support for multiple component variants
- 🔀 **Conditional fields** - Show/hide fields based on form values
- ðŸ“ą **Responsive layouts** - Multi-column grid layouts
- ðŸŠķ **Lightweight** - ~6KB minified, tree-shakeable
## Installation

@@ -13,23 +25,24 @@

npm install @classytic/formkit react-hook-form
# or
pnpm add @classytic/formkit react-hook-form
# or
yarn add @classytic/formkit react-hook-form
```
## Quick Start (Shadcn UI)
## Quick Start
**Important:** You must use `Controller` from react-hook-form. Raw Shadcn components won't work.
### 1. Create Field Components
### 1. Install Shadcn Components
Each field component wraps your UI library with `react-hook-form`'s Controller:
```bash
npx shadcn@latest add input label select
```
```tsx
// components/form/form-input.tsx
"use client";
### 2. Create Form Component
```tsx
// components/form-input.tsx
import { Controller } from "react-hook-form";
import type { FieldComponentProps } from "@classytic/formkit";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function FormInput({ control, name, label, placeholder, required, ...props }) {
export function FormInput({ control, name, label, placeholder, required }: FieldComponentProps) {
return (

@@ -41,5 +54,12 @@ <Controller

<div className="space-y-2">
{label && <Label>{label}{required && '*'}</Label>}
<Input {...field} placeholder={placeholder} />
{fieldState.error && <p className="text-sm text-red-500">{fieldState.error.message}</p>}
{label && (
<Label htmlFor={name}>
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</Label>
)}
<Input {...field} id={name} placeholder={placeholder} />
{fieldState.error && (
<p className="text-sm text-red-500">{fieldState.error.message}</p>
)}
</div>

@@ -52,49 +72,95 @@ )}

### 3. Create Adapter
### 2. Create Form Adapter
Register your components and layouts:
```tsx
// lib/form-adapter.tsx
import { FormSystemProvider } from "@classytic/formkit";
import { FormInput } from "@/components/form-input";
"use client";
const components = {
import { FormSystemProvider, type ComponentRegistry, type LayoutRegistry } from "@classytic/formkit";
import { FormInput } from "@/components/form/form-input";
const components: ComponentRegistry = {
text: FormInput,
email: FormInput,
password: FormInput,
// Add more field types...
};
const layouts = {
section: ({ title, children }) => <div><h3>{title}</h3>{children}</div>,
grid: ({ children, cols = 1 }) => <div className={`grid grid-cols-${cols} gap-4`}>{children}</div>,
const layouts: LayoutRegistry = {
section: ({ title, description, children }) => (
<div className="space-y-4">
{title && <h3 className="text-lg font-semibold">{title}</h3>}
{description && <p className="text-muted-foreground">{description}</p>}
{children}
</div>
),
grid: ({ children, cols = 1 }) => (
<div className={`grid grid-cols-${cols} gap-4`}>{children}</div>
),
};
export function FormProvider({ children }) {
return <FormSystemProvider components={components} layouts={layouts}>{children}</FormSystemProvider>;
export function FormProvider({ children }: { children: React.ReactNode }) {
return (
<FormSystemProvider components={components} layouts={layouts}>
{children}
</FormSystemProvider>
);
}
```
### 4. Use in Your App
### 3. Use FormGenerator
```tsx
// app/signup/page.tsx
"use client";
import { useForm } from "react-hook-form";
import { FormGenerator } from "@classytic/formkit";
import { FormProvider } from "./form-adapter";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormGenerator, type FormSchema } from "@classytic/formkit";
import { FormProvider } from "@/lib/form-adapter";
export function MyForm() {
const form = useForm();
// Validation schema
const signupSchema = z.object({
firstName: z.string().min(2),
lastName: z.string().min(2),
email: z.string().email(),
password: z.string().min(8),
});
type SignupData = z.infer<typeof signupSchema>;
// Form schema (type-safe!)
const formSchema: FormSchema<SignupData> = {
sections: [
{
title: "Personal Information",
cols: 2,
fields: [
{ name: "firstName", type: "text", label: "First Name", required: true },
{ name: "lastName", type: "text", label: "Last Name", required: true },
],
},
{
title: "Account",
fields: [
{ name: "email", type: "email", label: "Email", required: true },
{ name: "password", type: "password", label: "Password", required: true },
],
},
],
};
export default function SignupPage() {
const form = useForm<SignupData>({
resolver: zodResolver(signupSchema),
});
return (
<FormProvider>
<form onSubmit={form.handleSubmit(console.log)}>
<FormGenerator
schema={{
sections: [{
fields: [
{ name: "email", type: "email", label: "Email", required: true },
{ name: "password", type: "text", label: "Password" },
]
}]
}}
control={form.control}
/>
<button type="submit">Submit</button>
<form onSubmit={form.handleSubmit(console.log)} className="space-y-8">
<FormGenerator schema={formSchema} control={form.control} />
<button type="submit">Sign Up</button>
</form>

@@ -106,53 +172,259 @@ </FormProvider>

## Complete Examples
## API Reference
See `example/shadcn/` directory for full working examples with:
- FormInput, FormSelect, FormCheckbox
- Validation with Zod
- Conditional fields
- Error handling
### FormGenerator
## Key Points
The main component that renders forms from a schema.
**You MUST use Controller:**
```tsx
import { Controller } from "react-hook-form";
<Controller
name={name}
control={control}
render={({ field, fieldState }) => (
<Input {...field} /> // ← Spread field
)}
<FormGenerator
schema={formSchema} // Required: Form schema
control={form.control} // Required: React Hook Form control
disabled={false} // Optional: Disable all fields
variant="default" // Optional: Global variant
className="my-form" // Optional: Root element class
/>
```
**Component receives these props:**
### FormSchema
```tsx
interface FormSchema<T extends FieldValues = FieldValues> {
sections: Section<T>[];
}
interface Section<T> {
id?: string; // Unique identifier
title?: string; // Section title
description?: string; // Section description
icon?: ReactNode; // Section icon
fields?: BaseField<T>[]; // Fields in this section
cols?: number; // Grid columns (1-6)
gap?: number; // Grid gap
variant?: string; // Section variant
className?: string; // Custom class
collapsible?: boolean; // Make section collapsible
defaultCollapsed?: boolean;
condition?: (control) => boolean; // Conditional rendering
render?: (props) => ReactNode; // Custom render function
}
```
### BaseField
```tsx
interface BaseField<T> {
name: string; // Field name (required)
type: FieldType; // Field type (required)
label?: string; // Field label
placeholder?: string; // Placeholder text
helperText?: string; // Helper text below field
disabled?: boolean; // Disable field
required?: boolean; // Mark as required
readOnly?: boolean; // Read-only field
variant?: string; // Field variant
fullWidth?: boolean; // Span full grid width
className?: string; // Custom class
defaultValue?: unknown; // Default value
// Conditional rendering
condition?: (formValues: T) => boolean;
// For select/radio/checkbox
options?: FieldOption[];
// HTML input attributes
min?: number | string;
max?: number | string;
step?: number;
pattern?: string;
minLength?: number;
maxLength?: number;
rows?: number; // For textarea
multiple?: boolean; // For select/file
accept?: string; // For file input
autoComplete?: string;
autoFocus?: boolean;
// Custom props
[key: string]: unknown;
}
```
### FieldComponentProps
Props passed to your field components:
```tsx
interface FieldComponentProps<T extends FieldValues = FieldValues> extends BaseField<T> {
field: BaseField<T>; // Full field config
control: Control<T>; // React Hook Form control
disabled?: boolean; // Merged disabled state
variant?: string; // Active variant
}
```
### ComponentRegistry
```tsx
const components: ComponentRegistry = {
// Simple mapping
text: FormInput,
select: FormSelect,
// Variant-specific components
compact: {
text: CompactInput,
select: CompactSelect,
},
};
```
### LayoutRegistry
```tsx
const layouts: LayoutRegistry = {
section: SectionLayout,
grid: GridLayout,
// Variant-specific layouts
compact: {
section: CompactSection,
},
};
```
## Advanced Features
### Conditional Fields
```tsx
{
field: {...}, // Config object
control: {...}, // React Hook Form control
name: "email", // Plus all field props spread
label: "Email",
// ...
name: "companyName",
type: "text",
label: "Company Name",
condition: (values) => values.accountType === "business",
}
```
## API
### Variants
### FormGenerator
Apply different styles based on context:
| Prop | Type | Required |
|------|------|----------|
| schema | FormSchema | Yes |
| control | Control | Yes |
| disabled | boolean | No |
| variant | string | No |
```tsx
// Register variant-specific components
const components = {
text: DefaultInput,
compact: {
text: CompactInput,
},
};
### Types
// Use variant in schema
const schema = {
sections: [{
variant: "compact", // All fields use compact variant
fields: [...]
}]
};
// Or per-field
{
name: "notes",
type: "text",
variant: "compact",
}
```
### Custom Section Render
```tsx
import type { FormSchema, FieldComponentProps } from "@classytic/formkit";
{
title: "Payment",
render: ({ control, disabled }) => (
<StripeElements>
<CardElement />
<FormInput name="billingName" control={control} />
</StripeElements>
),
}
```
### Grouped Select Options
```tsx
{
name: "country",
type: "select",
options: [
{
label: "North America",
options: [
{ value: "us", label: "United States" },
{ value: "ca", label: "Canada" },
],
},
{
label: "Europe",
options: [
{ value: "uk", label: "United Kingdom" },
{ value: "de", label: "Germany" },
],
},
],
}
```
## Type Exports
```tsx
import type {
// Core
FormSchema,
FormGeneratorProps,
BaseField,
Section,
// Components
FieldComponentProps,
FieldComponent,
ComponentRegistry,
// Layouts
SectionLayoutProps,
GridLayoutProps,
LayoutComponent,
LayoutRegistry,
// Options
FieldOption,
FieldOptionGroup,
// Utility types
FieldType,
LayoutType,
Variant,
DefineField,
InferSchemaValues,
SchemaFieldNames,
} from "@classytic/formkit";
```
## Examples
See the [`example/shadcn`](./example/shadcn) directory for complete working examples with:
- Form components (Input, Select, Checkbox)
- Full adapter configuration
- Zod validation
- Conditional fields
- Multi-column layouts
- TypeScript integration
## Browser Support
- React 18.0+
- React 19.0+
- All modern browsers
## License

@@ -165,3 +437,4 @@

- [GitHub](https://github.com/classytic/formkit)
- [npm](https://www.npmjs.com/package/@classytic/formkit)
- [Examples](https://github.com/classytic/formkit/tree/main/example/shadcn)
- [Issues](https://github.com/classytic/formkit/issues)