|
| 1 | +/** |
| 2 | + * ObjectUI |
| 3 | + * Copyright (c) 2024-present ObjectStack Inc. |
| 4 | + * |
| 5 | + * This source code is licensed under the MIT license found in the |
| 6 | + * LICENSE file in the root directory of this source tree. |
| 7 | + */ |
| 8 | + |
| 9 | +import React from 'react'; |
| 10 | +import { ComponentRegistry } from '@object-ui/core'; |
| 11 | +import { cn } from '../lib/utils'; |
| 12 | +import { Box, FileQuestion } from 'lucide-react'; |
| 13 | + |
| 14 | +export const PlaceholderRenderer = ({ schema, className, ...props }: any) => { |
| 15 | + const type = schema.type; |
| 16 | + const isView = type.startsWith('view:'); |
| 17 | + const isWidget = type.startsWith('widget:'); |
| 18 | + const isField = type.startsWith('field:'); |
| 19 | + |
| 20 | + return ( |
| 21 | + <div className={cn( |
| 22 | + "flex flex-col items-center justify-center p-6 border-2 border-dashed rounded-lg bg-muted/30 transition-colors hover:bg-muted/50", |
| 23 | + isView && "border-blue-300 bg-blue-50/50 min-h-[200px]", |
| 24 | + isWidget && "border-purple-300 bg-purple-50/50 min-h-[150px]", |
| 25 | + isField && "border-yellow-300 bg-yellow-50/50 p-2 min-h-[40px] flex-row gap-2 justify-start", |
| 26 | + className |
| 27 | + )}> |
| 28 | + <div className={cn("flex items-center gap-2 text-muted-foreground", isField && "text-sm")}> |
| 29 | + {isField ? <Box className="h-4 w-4" /> : <FileQuestion className="h-8 w-8 mb-2 opacity-50" />} |
| 30 | + <div className="flex flex-col items-center text-center"> |
| 31 | + <span className="font-mono font-medium text-foreground">{type}</span> |
| 32 | + {!isField && <span className="text-xs">Component Placeholder</span>} |
| 33 | + </div> |
| 34 | + </div> |
| 35 | + {schema.props && !isField && ( |
| 36 | + <div className="mt-4 w-full text-xs text-muted-foreground bg-background/50 p-2 rounded overflow-hidden"> |
| 37 | + <div className="opacity-70">Properties:</div> |
| 38 | + <pre className="mt-1 truncate">{JSON.stringify(schema.props, null, 0)}</pre> |
| 39 | + </div> |
| 40 | + )} |
| 41 | + </div> |
| 42 | + ); |
| 43 | +}; |
| 44 | + |
| 45 | +// List of all protocol-defined components that need placeholders |
| 46 | +const PROTOCOL_COMPONENTS = [ |
| 47 | + // 1. Views (List) |
| 48 | + 'view:grid', 'view:kanban', 'view:map', 'view:calendar', 'view:gantt', |
| 49 | + 'view:timeline', 'view:gallery', 'view:spreadsheet', |
| 50 | + |
| 51 | + // 2. Views (Form) |
| 52 | + 'view:simple', 'view:wizard', 'view:tabbed', 'view:drawer', 'view:modal', 'view:split', |
| 53 | + |
| 54 | + // 3. Fields (Textual) |
| 55 | + 'field:text', 'field:textarea', 'field:password', 'field:email', 'field:url', 'field:phone', |
| 56 | + |
| 57 | + // 4. Fields (Rich) |
| 58 | + 'field:markdown', 'field:html', 'field:code', |
| 59 | + |
| 60 | + // 5. Fields (Numeric) |
| 61 | + 'field:number', 'field:currency', 'field:percent', 'field:slider', |
| 62 | + |
| 63 | + // 6. Fields (Selection) |
| 64 | + 'field:boolean', 'field:checkboxes', 'field:select', 'field:multiselect', 'field:radio', |
| 65 | + |
| 66 | + // 7. Fields (Date/Time) |
| 67 | + 'field:date', 'field:datetime', 'field:time', 'field:duration', |
| 68 | + |
| 69 | + // 8. Fields (Relational) |
| 70 | + 'field:lookup', 'field:master_detail', 'field:tree', |
| 71 | + |
| 72 | + // 9. Fields (Media) |
| 73 | + 'field:image', 'field:file', 'field:video', 'field:audio', 'field:avatar', |
| 74 | + |
| 75 | + // 10. Fields (Visual) |
| 76 | + 'field:color', 'field:rating', 'field:signature', 'field:qrcode', 'field:progress', |
| 77 | + |
| 78 | + // 11. Fields (Structure) |
| 79 | + 'field:json', 'field:address', 'field:location', |
| 80 | + |
| 81 | + // 12. Page Components |
| 82 | + 'page:footer', 'page:tabs', 'page:accordion', 'page:card', 'page:sidebar', |
| 83 | + 'record:details', 'record:highlights', 'record:related_list', 'record:activity', |
| 84 | + 'record:chatter', 'record:path', |
| 85 | + 'app:launcher', 'nav:menu', 'nav:breadcrumb', |
| 86 | + 'global:search', 'global:notifications', 'user:profile', |
| 87 | + |
| 88 | + // 13. Dashboard Widgets |
| 89 | + 'widget:metric', 'widget:bar', 'widget:line', 'widget:pie', 'widget:funnel', |
| 90 | + 'widget:radar', 'widget:scatter', 'widget:heatmap', 'widget:pivot', 'widget:table', 'widget:text', 'widget:image', |
| 91 | + |
| 92 | + // 14. Smart Actions |
| 93 | + 'action:button', 'action:group', 'action:menu', 'action:icon', |
| 94 | + |
| 95 | + // 15. AI |
| 96 | + 'ai:chat_window', 'ai:input', 'ai:suggestion', 'ai:feedback' |
| 97 | +]; |
| 98 | + |
| 99 | +export function registerPlaceholders() { |
| 100 | + PROTOCOL_COMPONENTS.forEach(type => { |
| 101 | + // Only register if not already registered (to avoid overwriting real implementations) |
| 102 | + if (!ComponentRegistry.get(type)) { |
| 103 | + ComponentRegistry.register(type, PlaceholderRenderer); |
| 104 | + } |
| 105 | + }); |
| 106 | +} |
0 commit comments