@uniform-ts/core 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ UniForm takes a Zod schema and automatically renders a fully customizable form.
14
14
  - **Per-field `onChange` in `fields` prop** — react to individual field changes inline, with typed values and full form control methods
15
15
  - **Per-field custom components** — pass any `React.ComponentType<FieldProps>` directly as `meta.component` (inline, no registry) or register under a custom string key; direct components bypass the registry _and_ the default `ArrayField`/`ObjectField` routing, allowing fully custom multi-value widgets for `array`-typed fields
16
16
  - **Layout hooks** — `classNames`, `fieldWrapper`, `layout.formWrapper`, `layout.sectionWrapper`, `layout.submitButton`
17
- - **Section grouping** — group fields into named sections via `meta.section`
17
+ - **Section grouping** — group fields into named sections via `meta.section`; style or swap individual section wrappers via `layout.sections`
18
18
  - **Conditional fields** — show/hide fields based on form values with `meta.condition`; hidden fields automatically reset to their default value
19
19
  - **Field ordering** — control render order with `meta.order`
20
20
  - **`createAutoForm()` factory** — bake in your design system defaults once, use everywhere
@@ -178,7 +178,7 @@ const MyAutoForm = createAutoForm({
178
178
  | -------------- | ---------------------------------------- | ------------------------------------------------ |
179
179
  | `components` | `ComponentRegistry` | Deep merge (instance overrides specific keys) |
180
180
  | `fieldWrapper` | `React.ComponentType<FieldWrapperProps>` | Instance replaces factory |
181
- | `layout` | `LayoutSlots` | Shallow merge |
181
+ | `layout` | `LayoutSlots` | Shallow merge (except `sections` — deep-merged) |
182
182
  | `classNames` | `FormClassNames` | Shallow merge |
183
183
  | `disabled` | `boolean` | OR logic (either `true` → disabled) |
184
184
  | `coercions` | `CoercionMap` | Shallow merge |
@@ -307,11 +307,29 @@ type LayoutSlots = {
307
307
  sectionWrapper?: React.ComponentType<{
308
308
  children: React.ReactNode
309
309
  title: string
310
+ className?: string
310
311
  }>
311
312
  submitButton?: React.ComponentType<{ isSubmitting: boolean }>
312
313
  arrayRowLayout?: React.ComponentType<ArrayRowLayoutProps>
313
314
  /** Shown while async `defaultValues` are resolving. Default: `<p>Loading…</p>` */
314
315
  loadingFallback?: React.ReactNode
316
+ /** Per-section styling / component overrides keyed by section title. */
317
+ sections?: Record<string, SectionConfig>
318
+ }
319
+ ```
320
+
321
+ #### `SectionConfig`
322
+
323
+ ```ts
324
+ type SectionConfig = {
325
+ /** CSS class name forwarded to the section wrapper. */
326
+ className?: string
327
+ /** Replace the section wrapper component for this section only. */
328
+ component?: React.ComponentType<{
329
+ children: React.ReactNode
330
+ title: string
331
+ className?: string
332
+ }>
315
333
  }
316
334
  ```
317
335
 
@@ -633,8 +651,8 @@ The `span` value is set as `--field-span` CSS custom property on each field wrap
633
651
  city: { section: 'Address', order: 4 },
634
652
  }}
635
653
  layout={{
636
- sectionWrapper: ({ children, title }) => (
637
- <fieldset>
654
+ sectionWrapper: ({ children, title, className }) => (
655
+ <fieldset className={className}>
638
656
  <legend>{title}</legend>
639
657
  {children}
640
658
  </fieldset>
@@ -643,6 +661,23 @@ The `span` value is set as `--field-span` CSS custom property on each field wrap
643
661
  />
644
662
  ```
645
663
 
664
+ Use `layout.sections` to style or swap the wrapper for individual sections:
665
+
666
+ ```tsx
667
+ <AutoForm
668
+ form={myForm}
669
+ onSubmit={handleSubmit}
670
+ layout={{
671
+ sections: {
672
+ Personal: { className: 'bg-blue-50 p-4 rounded' },
673
+ Address: { component: AddressCard }, // completely different component
674
+ },
675
+ }}
676
+ />
677
+ ```
678
+
679
+ `className` is forwarded as a prop to the active wrapper (global `sectionWrapper` or the per-section `component`). Factory-level and instance-level `sections` are merged — instance wins on conflicts.
680
+
646
681
  ### Conditional Fields
647
682
 
648
683
  Show a field only when another field has a specific value. Conditional fields are fully lifecycle-managed:
package/dist/index.d.mts CHANGED
@@ -347,6 +347,20 @@ type ArrayRowLayoutProps = {
347
347
  /** Total number of rows currently in the array. */
348
348
  rowCount: number;
349
349
  };
350
+ /**
351
+ * Per-section styling overrides forwarded to the `sectionWrapper` component.
352
+ * Keys are section titles; values control how that section wrapper is styled.
353
+ */
354
+ type SectionConfig = {
355
+ /** CSS class name(s) applied to the section wrapper. */
356
+ className?: string;
357
+ /** Replaces the section wrapper component entirely for this section. */
358
+ component?: React.ComponentType<{
359
+ children: React.ReactNode;
360
+ title: string;
361
+ className?: string;
362
+ }>;
363
+ };
350
364
  /**
351
365
  * Optional layout slot overrides for top-level structural components of the
352
366
  * form. Provide only the slots you want to replace; omitted slots fall back
@@ -361,6 +375,7 @@ type LayoutSlots = {
361
375
  sectionWrapper?: React.ComponentType<{
362
376
  children: React.ReactNode;
363
377
  title: string;
378
+ className?: string;
364
379
  }>;
365
380
  /** Custom submit button component. */
366
381
  submitButton?: React.ComponentType<{
@@ -374,6 +389,11 @@ type LayoutSlots = {
374
389
  * Defaults to a simple `<p>Loading…</p>` when not provided.
375
390
  */
376
391
  loadingFallback?: React.ReactNode;
392
+ /**
393
+ * Per-section config keyed by section title.
394
+ * Forwarded to the `sectionWrapper` component as a `className` prop.
395
+ */
396
+ sections?: Record<string, SectionConfig>;
377
397
  };
378
398
  /**
379
399
  * The resolved version of `LayoutSlots` used internally, where all slots are
@@ -386,6 +406,7 @@ type ResolvedLayoutSlots = {
386
406
  sectionWrapper: React.ComponentType<{
387
407
  children: React.ReactNode;
388
408
  title: string;
409
+ className?: string;
389
410
  }>;
390
411
  submitButton: React.ComponentType<{
391
412
  isSubmitting: boolean;
package/dist/index.d.ts CHANGED
@@ -347,6 +347,20 @@ type ArrayRowLayoutProps = {
347
347
  /** Total number of rows currently in the array. */
348
348
  rowCount: number;
349
349
  };
350
+ /**
351
+ * Per-section styling overrides forwarded to the `sectionWrapper` component.
352
+ * Keys are section titles; values control how that section wrapper is styled.
353
+ */
354
+ type SectionConfig = {
355
+ /** CSS class name(s) applied to the section wrapper. */
356
+ className?: string;
357
+ /** Replaces the section wrapper component entirely for this section. */
358
+ component?: React.ComponentType<{
359
+ children: React.ReactNode;
360
+ title: string;
361
+ className?: string;
362
+ }>;
363
+ };
350
364
  /**
351
365
  * Optional layout slot overrides for top-level structural components of the
352
366
  * form. Provide only the slots you want to replace; omitted slots fall back
@@ -361,6 +375,7 @@ type LayoutSlots = {
361
375
  sectionWrapper?: React.ComponentType<{
362
376
  children: React.ReactNode;
363
377
  title: string;
378
+ className?: string;
364
379
  }>;
365
380
  /** Custom submit button component. */
366
381
  submitButton?: React.ComponentType<{
@@ -374,6 +389,11 @@ type LayoutSlots = {
374
389
  * Defaults to a simple `<p>Loading…</p>` when not provided.
375
390
  */
376
391
  loadingFallback?: React.ReactNode;
392
+ /**
393
+ * Per-section config keyed by section title.
394
+ * Forwarded to the `sectionWrapper` component as a `className` prop.
395
+ */
396
+ sections?: Record<string, SectionConfig>;
377
397
  };
378
398
  /**
379
399
  * The resolved version of `LayoutSlots` used internally, where all slots are
@@ -386,6 +406,7 @@ type ResolvedLayoutSlots = {
386
406
  sectionWrapper: React.ComponentType<{
387
407
  children: React.ReactNode;
388
408
  title: string;
409
+ className?: string;
389
410
  }>;
390
411
  submitButton: React.ComponentType<{
391
412
  isSubmitting: boolean;
package/dist/index.js CHANGED
@@ -398,9 +398,10 @@ function DefaultFormWrapper({ children }) {
398
398
  }
399
399
  function DefaultSectionWrapper({
400
400
  children,
401
- title
401
+ title,
402
+ className
402
403
  }) {
403
- return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
404
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className, children: [
404
405
  /* @__PURE__ */ jsxRuntime.jsx("legend", { children: title }),
405
406
  children
406
407
  ] });
@@ -1525,7 +1526,17 @@ function AutoForm(props) {
1525
1526
  if (section.title === null) {
1526
1527
  return /* @__PURE__ */ jsxRuntime.jsx(React3__namespace.Fragment, { children: renderedFields }, "__ungrouped");
1527
1528
  }
1528
- return /* @__PURE__ */ jsxRuntime.jsx(SectionWrapper, { title: section.title, children: renderedFields }, section.title);
1529
+ const sectionConfig = layout?.sections?.[section.title];
1530
+ const PerSectionWrapper = sectionConfig?.component ?? SectionWrapper;
1531
+ return /* @__PURE__ */ jsxRuntime.jsx(
1532
+ PerSectionWrapper,
1533
+ {
1534
+ title: section.title,
1535
+ className: sectionConfig?.className,
1536
+ children: renderedFields
1537
+ },
1538
+ section.title
1539
+ );
1529
1540
  }),
1530
1541
  /* @__PURE__ */ jsxRuntime.jsx(
1531
1542
  SubmitButton,
@@ -1545,7 +1556,11 @@ function createAutoForm(config) {
1545
1556
  [props.components]
1546
1557
  );
1547
1558
  const mergedLayout = React3__namespace.useMemo(
1548
- () => ({ ...config.layout, ...props.layout }),
1559
+ () => ({
1560
+ ...config.layout,
1561
+ ...props.layout,
1562
+ sections: config.layout?.sections || props.layout?.sections ? { ...config.layout?.sections, ...props.layout?.sections } : void 0
1563
+ }),
1549
1564
  [props.layout]
1550
1565
  );
1551
1566
  const mergedClassNames = React3__namespace.useMemo(