Skip to content

Commit e62befe

Browse files
committed
fix: bugs and browser warnings
1 parent 847da5a commit e62befe

26 files changed

+357
-209
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ For more detailed documentation read the [docs](https://uibuilder.app/)
286286
- The `componentRegistry` is now passed as a prop to the `UIBuilder` component instead of being defined in a standalone file.
287287
- 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.
288288
- `pagePropsForm` prop added to `UIBuilder` to allow customization of the form used for editing page-level (root layer) properties.
289+
- Made `layer-store` local storage persistence optional and configurable via props.
289290

290291

291292
### v0.0.2
@@ -323,11 +324,15 @@ npm run test
323324
- [ ] Add tiptap editor for markdown content
324325
- [ ] Add global variables for component props
325326
- [ ] Add Blocks. Reusable component blocks that can be used in multiple pages
327+
- [ ] Move component schemas to separate shadcn registry to keep main registry light
328+
- [ ] Move prop form field components (overrides) to separate shadcn registry to keep main registry light
326329
- [ ] Add data sources to component layers (ex, getUser() binds prop user.name)
327330
- [ ] Add event handlers to component layers (onClick, onSubmit, etc)
328331
- [ ] React native support
329332
- [ ] Update to React 19
330333
- [ ] Update to latest Shadcn/ui + Tailwind CSS v4
334+
- [ ] Update to new AutoForm when stable
335+
- [ ] Update to Zod v4 (when stable) for native json schema conversion to enforce safety in layer props
331336

332337
## Contributing
333338

__tests__/component-registry.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import {
44
generateFieldOverrides
55
} from "@/lib/ui-builder/store/editor-utils";
6-
import { RegistryEntry } from "@/lib/ui-builder/store/editor-store";
7-
import { ComponentLayer } from '@/components/ui/ui-builder/types';
6+
import { ComponentLayer, RegistryEntry } from '@/components/ui/ui-builder/types';
87
import { FieldConfigItem } from "@/components/ui/auto-form/types";
98
import { complexComponentDefinitions } from "@/lib/ui-builder/registry/complex-component-definitions";
109
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions";

__tests__/layer-store.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ describe('LayerStore', () => {
149149
expect(result.current.pages).toHaveLength(2);
150150
const duplicatedPage = result.current.pages[1];
151151
expect(duplicatedPage.type).toBe('_page_');
152-
expect(duplicatedPage.name).toBe('Page 1');
152+
expect(duplicatedPage.name).toBe('Page 1 (Copy)');
153153
expect(result.current.selectedPageId).toBe(duplicatedPage.id);
154154
});
155155

__tests__/layer-utils.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ describe("Layer Utils", () => {
448448
],
449449
};
450450

451-
const duplicatedLayer = duplicateWithNewIdsAndName(originalLayer);
451+
const duplicatedLayer = duplicateWithNewIdsAndName(originalLayer, true);
452452

453453
expect(duplicatedLayer.id).not.toBe(originalLayer.id);
454454
expect(duplicatedLayer.name).toBe(`${originalLayer.name} (Copy)`);
@@ -460,7 +460,7 @@ describe("Layer Utils", () => {
460460
duplicatedLayer.children.forEach((child, index) => {
461461
expect(child.id).not.toBe((originalLayer.children![index] as ComponentLayer).id);
462462
expect(child.name).toBe(
463-
`${(originalLayer.children![index] as ComponentLayer).name} (Copy)`
463+
`${(originalLayer.children![index] as ComponentLayer).name}`
464464
);
465465
expect(child.type).toBe((originalLayer.children![index] as ComponentLayer).type);
466466
expect(child.props).toEqual((originalLayer.children![index] as ComponentLayer).props);
@@ -537,15 +537,15 @@ describe("Layer Utils", () => {
537537
const duplicatedChild = duplicatedLayer.children![0] as ComponentLayer;
538538
expect(duplicatedChild.id).not.toBe((originalLayer.children![0] as ComponentLayer).id);
539539
expect(duplicatedChild.name).toBe(
540-
`${(originalLayer.children![0] as ComponentLayer).name} (Copy)`
540+
`${(originalLayer.children![0] as ComponentLayer).name}`
541541
);
542542

543543
const duplicatedDeepChild = duplicatedChild.children![0] as ComponentLayer;
544544
expect(duplicatedDeepChild.id).not.toBe(
545545
((originalLayer.children![0] as ComponentLayer).children![0] as ComponentLayer).id
546546
);
547547
expect(duplicatedDeepChild.name).toBe(
548-
`${((originalLayer.children![0] as ComponentLayer).children![0] as ComponentLayer).name} (Copy)`
548+
`${((originalLayer.children![0] as ComponentLayer).children![0] as ComponentLayer).name}`
549549
);
550550
});
551551

__tests__/layers-panel.test.tsx

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

1010
// Mock dependencies

__tests__/props-panel.test.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ 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 '@/components/ui/ui-builder/types';
9+
import { RegistryEntry, ComponentLayer } from '@/components/ui/ui-builder/types';
1010
import { z } from "zod";
11-
import { RegistryEntry, useEditorStore } from "@/lib/ui-builder/store/editor-store";
11+
import { useEditorStore } from "@/lib/ui-builder/store/editor-store";
1212

1313
// Mock dependencies
1414
jest.mock("../lib/ui-builder/store/layer-store", () => ({
@@ -132,7 +132,7 @@ describe("PropsPanel", () => {
132132
});
133133

134134
const renderPropsPanel = () => {
135-
const { container } = render(<PropsPanel className="test-class" />);
135+
const { container } = render(<PropsPanel className="test-class" pagePropsForm={<div>Page Props Form</div>} />);
136136
return { container };
137137
};
138138

@@ -185,7 +185,7 @@ describe("PropsPanel", () => {
185185
const selectedLayer = { id: 'layer1', type: 'Button', props: {} };
186186
mockFindLayerById.mockReturnValue(selectedLayer);
187187

188-
render(<PropsPanel />);
188+
render(<PropsPanel pagePropsForm={<div>Page Props Form</div>} />);
189189

190190
const deleteButton = screen.getByText('Delete Component');
191191
fireEvent.click(deleteButton);
@@ -197,7 +197,7 @@ describe("PropsPanel", () => {
197197
const selectedLayer = { id: 'layer1', type: 'Button', props: {} };
198198
mockFindLayerById.mockReturnValue(selectedLayer);
199199

200-
render(<PropsPanel />);
200+
render(<PropsPanel pagePropsForm={<div>Page Props Form</div>} />);
201201

202202
const duplicateButton = screen.getByText('Duplicate Component');
203203
fireEvent.click(duplicateButton);
@@ -206,7 +206,7 @@ describe("PropsPanel", () => {
206206
});
207207

208208
it("should render the form", async () => {
209-
const { container } = render(<PropsPanel />);
209+
const { container } = render(<PropsPanel pagePropsForm={<div>Page Props Form</div>} />);
210210
await waitFor(() => {
211211
expect(container.querySelector('form')).toBeInTheDocument();
212212
expect(container.querySelector('input[name="label"]')).toBeInTheDocument();
@@ -306,7 +306,7 @@ describe("PropsPanel", () => {
306306
});
307307

308308
it("should render the form", async () => {
309-
const { container } = render(<PropsPanel />);
309+
const { container } = render(<PropsPanel pagePropsForm={<div>Page Props Form</div>} />);
310310
await waitFor(() => {
311311
expect(container.querySelector('form')).toBeInTheDocument();
312312
expect(container.querySelector('input[name="label"]')).toBeInTheDocument();

app/platform/simple-builder.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"use client";
22

3+
import React from "react";
34
import UIBuilder from "@/components/ui/ui-builder";
45
import { complexComponentDefinitions } from "@/lib/ui-builder/registry/complex-component-definitions";
56
import { primitiveComponentDefinitions } from "@/lib/ui-builder/registry/primitive-component-definitions";
67

7-
export function SimpleBuilder() {
8+
export const SimpleBuilder = () => {
89
return (
910
<UIBuilder
11+
persistLayerStore={true}
1012
componentRegistry={{
1113
...complexComponentDefinitions,
1214
...primitiveComponentDefinitions,

components/ui/ui-builder/index.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Learn more about the UI Builder:
3+
* https://github.com/olliethedev/ui-builder
4+
*/
5+
16
"use client";
27

38
import React, { useEffect, useState, useMemo } from "react";
@@ -35,27 +40,38 @@ interface UIBuilderProps {
3540
onChange?: (pages: ComponentLayer[]) => void;
3641
componentRegistry: ComponentRegistry;
3742
panelConfig?: PanelConfig;
43+
persistLayerStore?: boolean;
3844
}
3945

4046
const UIBuilder = ({
4147
initialLayers,
4248
onChange,
4349
componentRegistry,
44-
panelConfig = defaultPanelConfig(true),
50+
panelConfig: userPanelConfig,
51+
persistLayerStore = true,
4552
}: UIBuilderProps) => {
4653
const layerStore = useStore(useLayerStore, (state) => state);
4754
const editorStore = useStore(useEditorStore, (state) => state);
4855

56+
4957
const [editorStoreInitialized, setEditorStoreInitialized] = useState(false);
5058
const [layerStoreInitialized, setLayerStoreInitialized] = useState(false);
5159

60+
// Memoize the default panel configuration.
61+
// The dependency array is empty because defaultPanelConfig is called with a constant 'true'.
62+
const memoizedDefaultPanelConfig = useMemo(() => defaultPanelConfig(true), []);
63+
64+
// Use user-provided config if available, otherwise use the memoized default.
65+
const currentPanelConfig = userPanelConfig || memoizedDefaultPanelConfig;
66+
67+
5268
// Effect 1: Initialize Editor Store with registry and page form props
5369
useEffect(() => {
5470
if (editorStore && componentRegistry && !editorStoreInitialized) {
55-
editorStore.initializeRegistry(componentRegistry);
71+
editorStore.initialize(componentRegistry, persistLayerStore);
5672
setEditorStoreInitialized(true);
5773
}
58-
}, [editorStore, componentRegistry, editorStoreInitialized]);
74+
}, [editorStore, componentRegistry, editorStoreInitialized, persistLayerStore]);
5975

6076
// Effect 2: Conditionally initialize Layer Store *after* Editor Store is initialized
6177
useEffect(() => {
@@ -91,7 +107,7 @@ const UIBuilder = ({
91107
const layout = isLoading ? (
92108
<LoadingSkeleton />
93109
) : (
94-
<MainLayout panelConfig={panelConfig} />
110+
<MainLayout panelConfig={currentPanelConfig} />
95111
);
96112

97113
return (

components/ui/ui-builder/internal/children-searchable-select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { X as XIcon, ChevronsUpDown } from "lucide-react";
55
import {
66
useLayerStore,
77
} from "@/lib/ui-builder/store/layer-store";
8-
import { ComponentLayer } from '../types';
8+
import { ComponentLayer } from '@/components/ui/ui-builder/types';
99
import { Button } from "@/components/ui/button";
1010
import { Badge } from "@/components/ui/badge";
1111
import { AddComponentsPopover } from "@/components/ui/ui-builder/internal/add-component-popover";

0 commit comments

Comments
 (0)