Skip to content

Commit f6a721f

Browse files
committed
refactor: builder component props and tyes
1 parent 310adc2 commit f6a721f

38 files changed

+1282
-297
lines changed

.eslintrc.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
{
2-
"extends": ["next/core-web-vitals", "next/typescript"]
2+
"extends": ["next/core-web-vitals", "next/typescript"],
3+
"rules": {
4+
"@typescript-eslint/no-explicit-any": "off",
5+
"@typescript-eslint/no-unused-vars":"off",
6+
"@typescript-eslint/no-empty-object-type": "off"
7+
}
38
}
49

README.md

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ To use the UI Builder, you need to provide a component registry and a form compo
5757

5858
```tsx
5959
import UIBuilder from "@/components/ui/ui-builder";
60-
import { ComponentRegistry } from "@/lib/ui-builder/store/editor-store";
61-
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions"; // Example registry
62-
import { ThemePanel } from "@/components/ui/ui-builder/internal/theme-panel"; // Example page props form
60+
import { ComponentRegistry } from "@/components/ui/ui-builder/types";
61+
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions"; // Basic html primitives registry
62+
import { complexComponentDefinitions } from "@/lib/ui-builder/registry/complex-component-definitions"; // Basic shadcn/ui components registry
63+
import { ThemePanel } from "@/components/ui/ui-builder/internal/theme-panel"; // Tailwind+shadcn theme settings form for the page root component
6364

6465
// Combine or define your component registry
6566
const myComponentRegistry: ComponentRegistry = {
@@ -81,18 +82,19 @@ By default the state of the UI is stored in the browser's local storage, so it w
8182

8283
### Example with initial state and onChange callback
8384

85+
You can initialize the UI with initial layers from a database and use the onChange callback to persist the state to a database.
86+
8487
```tsx
8588
import React from "react";
8689
import UIBuilder from "@/components/ui/ui-builder";
87-
import { ComponentLayer } from "@/lib/ui-builder/store/layer-store";
88-
import { ComponentRegistry } from "@/lib/ui-builder/store/editor-store";
89-
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions"; // Example registry
90-
import { ThemePanel } from "@/components/ui/ui-builder/internal/theme-panel"; // Example page props form
90+
import { ComponentLayer, ComponentRegistry } from "@/components/ui/ui-builder/types";
91+
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions";
92+
import { complexComponentDefinitions } from "@/lib/ui-builder/registry/complex-component-definitions";
93+
import { ThemePanel } from "@/components/ui/ui-builder/internal/theme-panel";
9194

92-
// Combine or define your component registry
9395
const myComponentRegistry: ComponentRegistry = {
9496
...primitiveComponentDefinitions,
95-
// ...add your custom components here
97+
...complexComponentDefinitions
9698
};
9799

98100

@@ -176,8 +178,8 @@ You can also render the page layer without editor functionality by using the Lay
176178

177179
```tsx
178180
import LayerRenderer from "@/components/ui/ui-builder/layer-renderer";
179-
import { ComponentLayer } from "@/lib/ui-builder/store/layer-store";
180-
import { ComponentRegistry } from "@/lib/ui-builder/store/editor-store";
181+
import { ComponentLayer } from "@/components/ui/ui-builder/types";
182+
import { ComponentRegistry } from "@/components/ui/ui-builder/types";
181183
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions"; // Example registry
182184

183185
// Component registry is needed for the renderer too
@@ -197,7 +199,7 @@ export function MyPage() {
197199

198200
## Add your custom components to the registry
199201

200-
You add custom components by defining them in a `ComponentRegistry` object and passing that object to the `UIBuilder` component via the `componentRegistry` prop.
202+
You add custom components by defining them in a `ComponentRegistry` object and passing that object to the `UIBuilder` component via the `componentRegistry` prop. The registry maps a unique component type name (string) to its definition.
201203

202204
Here is an example of how to define a custom component within the registry object:
203205

@@ -206,13 +208,16 @@ import { z } from 'zod';
206208
import { FancyComponent } from '@/components/ui/fancy-component';
207209
import { classNameFieldOverrides, childrenFieldOverrides } from "@/lib/ui-builder/registry/form-field-overrides";
208210
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions"; // Example primitive components
211+
import { ComponentRegistry } from "@/components/ui/ui-builder/types"; // Correct type import
209212

210-
// Define your custom component
213+
// Define your custom component's schema and configuration
211214
const fancyComponentDefinition = {
215+
// The React component itself
212216
component: FancyComponent,
217+
// The Zod schema defining the component's props
213218
schema: z.object({
214219
className: z.string().optional(),
215-
children: z.any().optional(),
220+
children: z.any().optional(), // Use z.any() or a more specific schema for children if needed
216221
title: z.string().default("Default Title"),
217222
count: z.coerce.number().default(1),
218223
disabled: z.boolean().optional(),
@@ -224,36 +229,49 @@ const fancyComponentDefinition = {
224229
])
225230
.default("fancy"),
226231
}),
227-
from: "@/components/ui/fancy-component", // Correct import path
232+
// The import path for code generation
233+
from: "@/components/ui/fancy-component",
234+
// Customizations for the auto-generated properties form
228235
fieldOverrides: {
229-
className:(layer)=> classNameFieldOverrides(layer),
230-
children: (layer)=> childrenFieldOverrides(layer)
236+
className: (layer) => classNameFieldOverrides(layer), // Standard handling for className
237+
children: (layer) => childrenFieldOverrides(layer), // Standard handling for children
238+
// You can add overrides for other fields here, e.g.:
239+
// count: { label: "Number of items" }, // Change the label
240+
// disabled: { // Conditionally hide the field
241+
// fieldType: "switch", // Specify field type
242+
// isHidden: (layer) => layer.props.mode === "boring",
243+
// },
231244
}
232245
};
233246

234247
// Create the full registry, potentially combining with existing definitions
235248
export const myComponentRegistry: ComponentRegistry = {
236249
...primitiveComponentDefinitions, // Include base components if needed
237-
FancyComponent: fancyComponentDefinition, // Add your custom component
250+
FancyComponent: fancyComponentDefinition, // Add your custom component under a unique name
238251
// ... add other custom components
239252
};
240253

241254
// Then pass `myComponentRegistry` to the UIBuilder prop:
242-
// <UIBuilder componentRegistry={myComponentRegistry} ... />
255+
// <UIBuilder componentRegistry={myComponentRegistry} pagePropsForm={...} />
243256
```
244257

245-
- `component`: The React component itself.
246-
- `from`: The source path of the component. Used when exporting the page as code.
247-
- `fieldOverrides`: Customizes auto-form fields for the component's properties.
248-
249-
- `schema`: A Zod schema defining the properties and validation rules for the component props. We use zod to define the component schema which represents the props that the component accepts. The required props **MUST** have a default value, this allows the UI Builder to render the component with the default value when the user adds the component to the page. This project leverages [Auto-Form](https://github.com/vantezzen/autoform/tree/pure-shadcn) to dynamically render component property forms based on the component definitions zod schema. Currently only these zod types are supported:
250-
- boolean
251-
- date
252-
- number
253-
- string
254-
- enum of supported zod types
255-
- object with properties of the supported zod types
256-
- array of objects with properties of the supported zod types
258+
**Component Definition Fields:**
259+
260+
- `component`: **Required**. The React component function or class.
261+
- `schema`: **Required**. A Zod schema defining the component's props, their types, and validation rules.
262+
- This schema powers the automatic generation of a properties form in the editor using [Auto-Form](https://github.com/vantezzen/autoform/tree/pure-shadcn).
263+
- Props intended to be configurable in the UI Builder **MUST** have a default value specified in the schema (using `.default(...)`). This allows the UI Builder to render the component correctly when it's first added, before the user configures it.
264+
- Currently supported Zod types for auto-form generation include: `boolean`, `date`, `number`, `string`, `enum` (of supported types), `object` (with supported property types), and `array` (of objects with supported property types).
265+
- `from`: **Required**. The source import path for the component. This is used when exporting the page structure as React code.
266+
- `fieldOverrides`: Optional. An object to customize the auto-generated form fields for the component's properties in the editor's sidebar.
267+
- The keys of this object correspond to the prop names defined in the Zod schema.
268+
- The values are typically functions that receive the current `layer` object and return configuration options for the `AutoForm` field. These options can control the field's label, input type (`fieldType`), visibility (`isHidden`), placeholder text, render logic, and more. See the AutoForm documentation for available options.
269+
- This is useful for:
270+
* Providing more descriptive labels or help text.
271+
* Using specific input components (e.g., a color picker, a custom slider).
272+
* Hiding props that shouldn't be user-editable (like internal state).
273+
* Implementing conditional logic (e.g., showing/hiding a field based on another prop's value).
274+
- The example uses `classNameFieldOverrides` and `childrenFieldOverrides` from `@/lib/ui-builder/registry/form-field-overrides` to provide standardized handling for common props like `className` (using a textarea) and `children` (often hidden or handled specially). You can create your own override functions or objects.
257275

258276

259277
---
@@ -264,9 +282,10 @@ For more detailed documentation read the [docs](https://uibuilder.app/)
264282
## Changelog
265283

266284
### v1.0.0
267-
- Removed _page_ layer type in favor of using any component as a page. This allows for more fexibility, like react-email components. You should migrate any layers stored in the database to use the new components. You can use the [migrateV2ToV3](lib/ui-builder/store/layer-utils.ts) function in layer-utils.ts to help with the migration.
268-
- Component registry is now a prop of the UIBuilder component instead of a standalone file.
269-
- Removed script to generate component definitions from the registry. As these were problematic to maintain and were not functioning correctly for complex components.
285+
- Removed _page_ layer type in favor of using any component type (like `div`, `main`, or custom containers) as the root page layer. This enhances flexibility, enabling use cases like building react-email templates directly. You should migrate any layers stored in the database to use a standard component type as the root. The [migrateV2ToV3](lib/ui-builder/store/layer-utils.ts) function in `layer-utils.ts` can assist with this migration.
286+
- The `componentRegistry` is now passed as a prop to the `UIBuilder` component instead of being defined in a standalone file.
287+
- Removed the script used to generate component schema definitions. This approach proved problematic to maintain and didn't function correctly for complex components or varying project setups. Component schema definitions should now be manually created or generated using project-specific tooling if desired.
288+
- `pagePropsForm` prop added to `UIBuilder` to allow customization of the form used for editing page-level (root layer) properties.
270289

271290

272291
### v0.0.2
@@ -301,13 +320,18 @@ npm run test
301320
## Roadmap
302321

303322
- [ ] Add user friendly styling component instead of directly using tailwind classes
323+
- [ ] Add tiptap editor for markdown content
324+
- [ ] Add global variables for component props
304325
- [ ] Add Blocks. Reusable component blocks that can be used in multiple pages
305326
- [ ] Add data sources to component layers (ex, getUser() binds prop user.name)
306327
- [ ] Add event handlers to component layers (onClick, onSubmit, etc)
307328
- [ ] React native support
308329
- [ ] Update to React 19
309-
- [ ] Update to latest Shadcn/ui + Tailwind v4
330+
- [ ] Update to latest Shadcn/ui + Tailwind CSS v4
331+
332+
## Contributing
310333

334+
Contributions are welcome! Please feel free to submit issues and pull requests.
311335

312336
## License
313337

__tests__/clickable-wrapper.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import React from "react";
33
import { render, screen } from "@testing-library/react";
44
import { ClickableWrapper } from "@/components/ui/ui-builder/internal/clickable-wrapper";
5-
import { ComponentLayer } from "@/lib/ui-builder/store/layer-store";
5+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
66
import { getScrollParent } from "@/lib/ui-builder/utils/get-scroll-parent";
77

88
describe("ClickableWrapper", () => {

__tests__/component-registry.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
generateFieldOverrides
55
} from "@/lib/ui-builder/store/editor-utils";
66
import { RegistryEntry } from "@/lib/ui-builder/store/editor-store";
7-
import { ComponentLayer } from "@/lib/ui-builder/store/layer-store";
7+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
88
import { FieldConfigItem } from "@/components/ui/auto-form/types";
99
import { complexComponentDefinitions } from "@/lib/ui-builder/registry/complex-component-definitions";
1010
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions";

__tests__/layer-store.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { renderHook, act } from '@testing-library/react';
2-
import { ComponentLayer, useLayerStore } from '@/lib/ui-builder/store/layer-store';
2+
import { useLayerStore } from '@/lib/ui-builder/store/layer-store';
3+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
34
import { z } from 'zod';
45

56
import { useEditorStore } from '@/lib/ui-builder/store/editor-store';

__tests__/layer-utils.test.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import {
1111
migrateV1ToV2,
1212
migrateV2ToV3
1313
} from "../lib/ui-builder/store/layer-utils";
14-
import {
15-
ComponentLayer,
16-
} from "../lib/ui-builder/store/layer-store";
14+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
1715

1816
describe("Layer Utils", () => {
1917
let mockPages: ComponentLayer[];

__tests__/layers-panel.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import React from "react";
33
import { render, screen, fireEvent } from "@testing-library/react";
44
import LayersPanel from "@/components/ui/ui-builder/internal/layers-panel";
5-
import { ComponentLayer, useLayerStore} from "@/lib/ui-builder/store/layer-store";
5+
import { useLayerStore} from "@/lib/ui-builder/store/layer-store";
6+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
67
import { RegistryEntry, useEditorStore } from "@/lib/ui-builder/store/editor-store";
78
import { z } from "zod";
89

__tests__/props-panel.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import PropsPanel from "@/components/ui/ui-builder/internal/props-panel";
66
import {
77
useLayerStore,
88
} from "@/lib/ui-builder/store/layer-store";
9-
import { ComponentLayer } from "@/lib/ui-builder/store/layer-store";
9+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
1010
import { z } from "zod";
1111
import { RegistryEntry, useEditorStore } from "@/lib/ui-builder/store/editor-store";
1212

__tests__/render-utils.test.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import { render, screen } from "@testing-library/react";
33
import {
44
RenderLayer,
55
} from "../components/ui/ui-builder/internal/render-utils";
6-
import {
7-
ComponentLayer,
8-
} from "../lib/ui-builder/store/layer-store";
6+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
97
import { BaseColor, baseColors } from "../components/ui/ui-builder/internal/base-colors";
108
import { z } from "zod";
119
// Mock dependencies

__tests__/templates.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22

33
import { pageLayerToCode, generateLayerCode, generatePropsString } from "../components/ui/ui-builder/internal/templates";
4-
import { ComponentLayer } from "../lib/ui-builder/store/layer-store";
4+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
55
import template from "lodash/template";
66
import { normalizeSchema } from "./test-utils";
77
import { z } from "zod";

0 commit comments

Comments
 (0)