1- import React from 'react' ;
2- import { registerComponent } from '@object-ui/core' ;
1+ import { ComponentRegistry } from '@object-ui/core' ;
32import { useDataScope } from '@object-ui/react' ;
43import { cn } from '@object-ui/components' ;
54
6- // ... existing code ...
7-
8- // 4. List View (Simple)
9- const ListView = ( { schema } : any ) => {
10- // If 'bind' is present, useDataScope will resolve the data relative to the current scope
11- // But useDataScope returns the *entire* scope usually?
12- // Actually, usually SchemaRenderer handles the `bind` resolution and passes `data` prop?
13- // Let's assume looking at the architecture: "SchemaRenderer ... Bridges Core and Components."
14- // If I use `useDataScope`, I should be able to get the list.
15-
16- // However, if the protocol says:
17- // "Data binding path (e.g., 'user.address.city')"
18- // usually the architecture allows the component to access data.
19-
20- const { scope } = useDataScope ( ) ;
21- let data = schema . data ; // direct data
22-
23- if ( schema . bind ) {
24- // Simple binding resolution for this demo
25- // In a real implementation this would be more robust
26- data = scope ? scope [ schema . bind ] : [ ] ;
27- }
28-
29- // Fallback or empty
30- if ( ! data || ! Array . isArray ( data ) ) return < div className = "p-4 text-muted-foreground" > No data</ div > ;
31-
32- return (
33- < div className = "space-y-2" >
34- { data . slice ( 0 , schema . props ?. limit || 10 ) . map ( ( item : any , i : number ) => (
35- < div key = { i } className = "flex items-center justify-between p-3 border rounded bg-card text-card-foreground" >
36- < div className = "flex flex-col" >
37- < span className = "font-medium text-sm" > { renderTemplate ( schema . props ?. render ?. title , item ) || item . name } </ span >
38- < span className = "text-xs text-muted-foreground" > { renderTemplate ( schema . props ?. render ?. description , item ) } </ span >
39- </ div >
40- { schema . props ?. render ?. extra && (
41- < div className = "text-xs font-semibold px-2 py-1 bg-secondary rounded" >
42- { renderTemplate ( schema . props ?. render ?. extra , item ) }
43- </ div >
44- ) }
45- </ div >
46- ) ) }
47- </ div >
48- ) ;
49- } ;
50-
51- // Simple string template helper: "Hello ${name}" -> "Hello World"
52- function renderTemplate ( template : string , data : any ) {
53- if ( ! template ) return "" ;
54- return template . replace ( / \$ \{ ( .* ?) \} / g, ( match , key ) => {
55- return data [ key ] !== undefined ? data [ key ] : match ;
56- } ) ;
57- }
58-
59- export function registerCustomWidgets ( ) {
60- registerComponent ( "widget:metric" , MetricWidget ) ;
61- registerComponent ( "widget:chart" , ChartWidget ) ;
62- registerComponent ( "view:timeline" , TimelineView ) ;
63- registerComponent ( "view:list" , ListView ) ;
64- }
5+ // 1. Metric Widget
656const MetricWidget = ( { schema } : any ) => {
667 const { value, label, trend, format } = schema . props || { } ;
678 const isPositive = trend ?. startsWith ( '+' ) ;
689
10+ // Simple resolution logic if needed, or assume pre-resolved
11+ const { scope } = useDataScope ( ) ;
12+ const resolvedValue = resolveExpression ( value , scope ) ;
13+
6914 return (
7015 < div className = "flex flex-col gap-1" >
71- < div className = "text-2xl font-bold" > { value } </ div >
16+ < div className = "text-2xl font-bold" >
17+ { format === 'currency' && typeof resolvedValue === 'number'
18+ ? new Intl . NumberFormat ( 'en-US' , { style : 'currency' , currency : 'USD' } ) . format ( resolvedValue )
19+ : resolvedValue }
20+ </ div >
7221 < div className = "text-xs text-muted-foreground" >
7322 { trend && (
7423 < span className = { cn ( "mr-1 font-medium" , isPositive ? "text-green-600" : "text-red-600" ) } >
@@ -84,14 +33,14 @@ const MetricWidget = ({ schema }: any) => {
8433// 2. Simple CSS Bar Chart
8534const ChartWidget = ( { schema } : any ) => {
8635 const { title, data, height = 200 } = schema . props || { } ;
87- // Extract max value for scaling
88- const maxValue = Math . max ( ...( data ? .map ( ( d : any ) => d . value ) || [ 100 ] ) ) ;
36+ const safeData = Array . isArray ( data ) ? data : [ ] ;
37+ const maxValue = Math . max ( ...( safeData . map ( ( d : any ) => d . value ) || [ 100 ] ) ) ;
8938
9039 return (
9140 < div className = "flex flex-col h-full w-full" >
9241 { title && < h3 className = "text-sm font-medium mb-4" > { title } </ h3 > }
9342 < div className = "flex items-end justify-around w-full" style = { { height : `${ height } px` } } >
94- { data ? .map ( ( item : any , i : number ) => {
43+ { safeData . map ( ( item : any , i : number ) => {
9544 const percent = ( item . value / maxValue ) * 100 ;
9645 return (
9746 < div key = { i } className = "flex flex-col items-center gap-2 group w-full px-1" >
@@ -127,8 +76,7 @@ const TimelineView = ({ schema }: any) => {
12776 < div className = "relative mt-1" >
12877 < div className = "absolute top-8 left-1/2 h-full w-px -translate-x-1/2 bg-gray-200" />
12978 < div className = "relative flex h-8 w-8 items-center justify-center rounded-full border bg-background shadow-sm" >
130- { /* Simple icon mapping or fallback */ }
131- < span className = "text-xs" > ●</ span >
79+ < div className = "w-2 h-2 rounded-full bg-blue-500" />
13280 </ div >
13381 </ div >
13482 < div className = "flex flex-col gap-1 pb-8" >
@@ -143,45 +91,51 @@ const TimelineView = ({ schema }: any) => {
14391} ;
14492
14593// 4. List View (Simple)
146- const ListView = ( { schema, data } : any ) => {
147- // If bind is used, the renderer might pass resolved data, but here we assume rudimentary binding access
148- // In a real scenario, useDataScope or similar hook would be used.
149- // For this simple registry, let's assume the parent resolved it or we just render props.
150- // Actually, bind logic is handled by the SchemaRenderer usually?
151- // If this component supports binding, it should receive `data` if the parent handles it
152- // OR it should use `useDataScope`.
153-
154- // Simplification: We will just render what is passed in props for now or handle simple binding manualy if needed.
155- // But wait, the schema used `bind: "opportunities"`.
156- // The SchemaRenderer logic should resolve this if wrapped correctly?
157- // Let's assume standard prop passing for now.
158-
159- // For the bound data, we need to access `schema.data` if the engine injects it.
160- // Or we use a hook.
94+ const ListView = ( { schema } : any ) => {
95+ const { scope } = useDataScope ( ) ;
96+ let data = schema . data ;
97+
98+ if ( schema . bind ) {
99+ // Resolve simple property path from scope
100+ data = scope ? scope [ schema . bind ] : [ ] ;
101+ }
161102
162- // NOTE: Since I cannot easily import `useDataScope` without checking where it is exported from (likely @object-ui/react),
163- // I will try to import it.
103+ if ( ! data || ! Array . isArray ( data ) ) return < div className = "p-4 text-muted-foreground" > No data found</ div > ;
164104
165105 return (
166- < div className = "space-y-4 " >
167- { ( schema . data || [ ] ) . map ( ( item : any , i : number ) => (
168- < div key = { i } className = "flex items-center justify-between p-4 border rounded-lg " >
169- < div >
170- < div className = "font-medium" > { item . name || item . title || "Unknown" } </ div >
171- < div className = "text-sm text-gray-500 " > { item . description } </ div >
106+ < div className = "space-y-2 " >
107+ { data . slice ( 0 , schema . props ?. limit || 10 ) . map ( ( item : any , i : number ) => (
108+ < div key = { i } className = "flex items-center justify-between p-3 border rounded bg-card text-card-foreground " >
109+ < div className = "flex flex-col" >
110+ < span className = "font-medium text-sm " > { resolveExpression ( schema . props ?. render ?. title , item ) || item . name } </ span >
111+ < span className = "text-xs text-muted-foreground " > { resolveExpression ( schema . props ?. render ?. description , item ) } </ span >
172112 </ div >
173- < div className = "text-sm font-bold" > { item . amount || item . status } </ div >
113+ { schema . props ?. render ?. extra && (
114+ < div className = "text-xs font-semibold px-2 py-1 bg-secondary rounded" >
115+ { resolveExpression ( schema . props ?. render ?. extra , item ) }
116+ </ div >
117+ ) }
174118 </ div >
175119 ) ) }
176120 </ div >
177121 ) ;
122+ } ;
123+
124+ function resolveExpression ( expr : string , context : any ) {
125+ if ( typeof expr !== 'string' ) return expr ;
126+ // Handle simplified ${prop}
127+ return expr . replace ( / \$ \{ ( .* ?) \} / g, ( match , key ) => {
128+ // Strip data. prefix if simple context binding
129+ const cleanKey = key . replace ( / ^ d a t a \. / , '' ) ;
130+ // Traverse object properties
131+ const val = cleanKey . split ( '.' ) . reduce ( ( o : any , i : string ) => ( o ? o [ i ] : null ) , context ) ;
132+ return val !== undefined && val !== null ? val : match ;
133+ } ) ;
178134}
179135
180136export function registerCustomWidgets ( ) {
181- registerComponent ( "widget:metric" , MetricWidget ) ;
182- registerComponent ( "widget:chart" , ChartWidget ) ;
183- registerComponent ( "view:timeline" , TimelineView ) ;
184- // Note: view:list might already exist or handled by plugin-view.
185- // If I want to override or add it:
186- registerComponent ( "view:list" , ListView ) ;
137+ ComponentRegistry . register ( "widget:metric" , MetricWidget ) ;
138+ ComponentRegistry . register ( "widget:chart" , ChartWidget ) ;
139+ ComponentRegistry . register ( "view:timeline" , TimelineView ) ;
140+ ComponentRegistry . register ( "view:list" , ListView ) ;
187141}
0 commit comments