Skip to content

Commit 9fe6b93

Browse files
committed
feat: enhance CRMApp and ObjectGrid components with new settings schema and data binding support
1 parent bf8ae72 commit 9fe6b93

File tree

3 files changed

+116
-13
lines changed

3 files changed

+116
-13
lines changed

packages/components/src/stories/CRMApp.stories.tsx

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,32 @@ const dashboardSchema = {
5757
props: { title: "Dashboard" },
5858
children: [
5959
{
60-
type: "page:header",
61-
props: { title: "Dashboard", description: "Overview of your business performance." }
60+
type: "div",
61+
className: "flex items-center justify-between mb-6",
62+
children: [
63+
{
64+
type: "page:header",
65+
props: { title: "Dashboard", description: "Overview of your business performance." },
66+
className: "mb-0"
67+
},
68+
{
69+
type: "div",
70+
className: "flex gap-2",
71+
children: [
72+
{ type: "button", props: { variant: "outline", size: "sm", children: "Download Report" } },
73+
{ type: "button", props: { size: "sm", children: "Refresh" } }
74+
]
75+
}
76+
]
6277
},
6378
{
6479
type: "grid",
65-
props: { cols: 3, gap: 4, className: "mb-8 mt-6" },
80+
props: { cols: 4, gap: 4, className: "mb-8 mt-2" },
6681
children: [
6782
{ type: "card", props: { title: "Total Revenue", description: "${data.revenueGrowth}" }, children: [{type: "text", content: "${data.revenue}", className: "text-2xl font-bold"}] },
6883
{ type: "card", props: { title: "Subscriptions", description: "${data.subGrowth}" }, children: [{type: "text", content: "${data.subscriptions}", className: "text-2xl font-bold"}] },
6984
{ type: "card", props: { title: "Sales", description: "${data.salesGrowth}" }, children: [{type: "text", content: "${data.sales}", className: "text-2xl font-bold"}] },
85+
{ type: "card", props: { title: "Active Now", description: "+201 since last hour" }, children: [{type: "text", content: "+573", className: "text-2xl font-bold"}] },
7086
]
7187
},
7288
{
@@ -75,24 +91,37 @@ const dashboardSchema = {
7591
children: [
7692
{
7793
type: "page:card",
78-
props: { title: "Overview", className: "col-span-4" },
94+
props: { title: "Revenue Overview", className: "col-span-4" },
7995
children: [{ type: "chart:bar", props: {
8096
data: "${data.overview}",
8197
index: "name",
82-
categories: ["total"]
98+
categories: ["total"],
99+
colors: ["blue"]
83100
}}]
84101
},
85102
{
86103
type: "page:card",
87-
props: { title: "Recent Sales", className: "col-span-3" },
104+
props: { title: "Recent Sales", description: "You made 265 sales this month.", className: "col-span-3" },
88105
children: [
89106
{ type: "view:simple", children: [
90-
{ type: "div", className: "flex items-center justify-between py-2 border-b", children: [
91-
{ type: "div", className: "flex items-center gap-2", children: [{type:"avatar", props:{fallback:"OM"}}, {type:"div", children:[{type:"text", content:"Olivia Martin", className:"font-medium"}, {type:"text", content:"olivia.martin@email.com", className:"text-xs text-muted-foreground"}]}] },
107+
{ type: "div", className: "flex items-center justify-between py-3 border-b hover:bg-muted/50 px-2 rounded transition-colors", children: [
108+
{ type: "div", className: "flex items-center gap-3", children: [{type:"avatar", props:{fallback:"OM"}}, {type:"div", children:[{type:"text", content:"Olivia Martin", className:"font-medium block"}, {type:"text", content:"olivia.martin@email.com", className:"text-xs text-muted-foreground block"}]}] },
92109
{ type: "text", content: "+$1,999.00", className: "font-medium" }
93110
]},
94-
{ type: "div", className: "flex items-center justify-between py-2 border-b", children: [
95-
{ type: "div", className: "flex items-center gap-2", children: [{type:"avatar", props:{fallback:"JL"}}, {type:"div", children:[{type:"text", content:"Jackson Lee", className:"font-medium"}, {type:"text", content:"jackson.lee@email.com", className:"text-xs text-muted-foreground"}]}] },
111+
{ type: "div", className: "flex items-center justify-between py-3 border-b hover:bg-muted/50 px-2 rounded transition-colors", children: [
112+
{ type: "div", className: "flex items-center gap-3", children: [{type:"avatar", props:{fallback:"JL"}}, {type:"div", children:[{type:"text", content:"Jackson Lee", className:"font-medium block"}, {type:"text", content:"jackson.lee@email.com", className:"text-xs text-muted-foreground block"}]}] },
113+
{ type: "text", content: "+$39.00", className: "font-medium" }
114+
]},
115+
{ type: "div", className: "flex items-center justify-between py-3 border-b hover:bg-muted/50 px-2 rounded transition-colors", children: [
116+
{ type: "div", className: "flex items-center gap-3", children: [{type:"avatar", props:{fallback:"IN"}}, {type:"div", children:[{type:"text", content:"Isabella Nguyen", className:"font-medium block"}, {type:"text", content:"isabella.nguyen@email.com", className:"text-xs text-muted-foreground block"}]}] },
117+
{ type: "text", content: "+$299.00", className: "font-medium" }
118+
]},
119+
{ type: "div", className: "flex items-center justify-between py-3 border-b hover:bg-muted/50 px-2 rounded transition-colors", children: [
120+
{ type: "div", className: "flex items-center gap-3", children: [{type:"avatar", props:{fallback:"WK"}}, {type:"div", children:[{type:"text", content:"William Kim", className:"font-medium block"}, {type:"text", content:"will@email.com", className:"text-xs text-muted-foreground block"}]}] },
121+
{ type: "text", content: "+$99.00", className: "font-medium" }
122+
]},
123+
{ type: "div", className: "flex items-center justify-between py-3 hover:bg-muted/50 px-2 rounded transition-colors", children: [
124+
{ type: "div", className: "flex items-center gap-3", children: [{type:"avatar", props:{fallback:"SD"}}, {type:"div", children:[{type:"text", content:"Sofia Davis", className:"font-medium block"}, {type:"text", content:"sofia.davis@email.com", className:"text-xs text-muted-foreground block"}]}] },
96125
{ type: "text", content: "+$39.00", className: "font-medium" }
97126
]},
98127
]}
@@ -164,6 +193,68 @@ const opportunitiesSchema = {
164193
]
165194
};
166195

196+
const settingsSchema = {
197+
type: "page",
198+
props: { title: "Settings" },
199+
children: [
200+
{
201+
type: "page:header",
202+
props: { title: "Settings", description: "Manage your account settings and preferences." }
203+
},
204+
{
205+
type: "div",
206+
className: "grid grid-cols-1 md:grid-cols-3 gap-8 mt-6",
207+
children: [
208+
{
209+
type: "div",
210+
className: "col-span-1",
211+
children: [
212+
{ type: "text", content: "Profile Information", className: "text-lg font-semibold block mb-2" },
213+
{ type: "text", content: "Update your account's profile information and email address.", className: "text-sm text-muted-foreground" },
214+
{
215+
type: "div",
216+
className: "mt-4 flex flex-col gap-1",
217+
children: [
218+
{ type: "button", props: { variant: "ghost", className: "justify-start font-semibold bg-muted", children: "General" } },
219+
{ type: "button", props: { variant: "ghost", className: "justify-start", children: "Security" } },
220+
{ type: "button", props: { variant: "ghost", className: "justify-start", children: "Billing" } },
221+
{ type: "button", props: { variant: "ghost", className: "justify-start", children: "Notifications" } },
222+
]
223+
}
224+
]
225+
},
226+
{
227+
type: "page:card",
228+
className: "col-span-2",
229+
children: [
230+
{
231+
type: "div", // Using div as form container for now
232+
className: "space-y-4",
233+
children: [
234+
{ type: "field:text", props: { label: "Username", defaultValue: "${data.profile.name}", description: "This is your public display name." } },
235+
{ type: "field:text", props: { label: "Email", defaultValue: "${data.profile.email}", description: "Please enter a valid email address." } },
236+
{ type: "field:text", props: { label: "Role", defaultValue: "${data.profile.role}", readonly: true, disabled: true } },
237+
{ type: "div", className: "pt-4 flex justify-end", children: [
238+
{ type: "button", props: { children: "Save Changes" } }
239+
]}
240+
]
241+
}
242+
]
243+
}
244+
]
245+
}
246+
]
247+
};
248+
249+
const SETTINGS_DATA = {
250+
profile: {
251+
name: "Admin User",
252+
email: "admin@objectui.dev",
253+
role: "Administrator",
254+
notifications: true
255+
}
256+
};
257+
167258
/* --- APP COMPONENT --- */
168259

169260
const CRMStoryApp = () => {
@@ -177,6 +268,8 @@ const CRMStoryApp = () => {
177268
return <SchemaRendererProvider dataSource={CONTACTS_DATA}><SchemaRenderer schema={contactsSchema} /></SchemaRendererProvider>;
178269
case "opportunities":
179270
return <SchemaRendererProvider dataSource={OPPORTUNITIES_DATA}><SchemaRenderer schema={opportunitiesSchema} /></SchemaRendererProvider>;
271+
case "settings":
272+
return <SchemaRendererProvider dataSource={SETTINGS_DATA}><SchemaRenderer schema={settingsSchema} /></SchemaRendererProvider>;
180273
default:
181274
return (
182275
<div className="p-8">

packages/plugin-grid/src/ObjectGrid.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import React, { useEffect, useState, useCallback } from 'react';
2525
import type { ObjectGridSchema, DataSource, ListColumn, ViewData } from '@object-ui/types';
26-
import { SchemaRenderer } from '@object-ui/react';
26+
import { SchemaRenderer, useDataScope } from '@object-ui/react';
2727
import { getCellRenderer } from '@object-ui/fields';
2828
import { Button } from '@object-ui/components';
2929
import {
@@ -114,12 +114,22 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
114114
const [error, setError] = useState<Error | null>(null);
115115
const [objectSchema, setObjectSchema] = useState<any>(null);
116116

117+
// Resolve bound data if 'bind' property exists
118+
const boundData = useDataScope(schema.bind);
119+
117120
// Get data configuration (supports both new and legacy formats)
118121
const rawDataConfig = getDataConfig(schema);
119122
// Memoize dataConfig using deep comparison to prevent infinite loops
120123
const dataConfig = React.useMemo(() => {
124+
// If we have bound data, it takes precedence as inline value
125+
if (boundData && Array.isArray(boundData)) {
126+
return {
127+
provider: 'value',
128+
items: boundData
129+
};
130+
}
121131
return rawDataConfig;
122-
}, [JSON.stringify(rawDataConfig)]);
132+
}, [JSON.stringify(rawDataConfig), boundData]);
123133

124134
const hasInlineData = dataConfig?.provider === 'value';
125135

packages/react/src/SchemaRenderer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const SchemaRenderer = forwardRef<any, { schema: SchemaNode } & Record<st
3030
if (newSchema.props) {
3131
const newProps = { ...newSchema.props };
3232
for (const [key, val] of Object.entries(newProps)) {
33-
newProps[key] = evaluator.evaluate(val);
33+
newProps[key] = evaluator.evaluate(val as any);
3434
}
3535
newSchema.props = newProps;
3636
}

0 commit comments

Comments
 (0)