From a65a994ec459b4da62a9b12516ef1d4e9eee7827 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 22:07:45 +0000 Subject: [PATCH] feat: Implement JSON components with wrappers for LoadingFallback, NavigationItem, PageHeaderContent Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/components/molecules/TreeCard.tsx | 75 +++++++++++++++++++ src/components/molecules/TreeListHeader.tsx | 53 +++++++++++++ src/components/molecules/index.ts | 6 ++ .../wrappers/LoadingFallbackWrapper.tsx | 16 ++++ .../wrappers/NavigationItemWrapper.tsx | 27 +++++++ .../wrappers/PageHeaderContentWrapper.tsx | 22 ++++++ src/lib/json-ui/wrappers/TreeCardWrapper.tsx | 33 ++++++++ .../definitions/loading-fallback.json | 32 ++++++++ .../wrappers/definitions/navigation-item.json | 37 +++++++++ .../definitions/page-header-content.json | 33 ++++++++ .../wrappers/definitions/tree-card.json | 63 ++++++++++++++++ src/lib/json-ui/wrappers/index.ts | 4 + 12 files changed, 401 insertions(+) create mode 100644 src/components/molecules/TreeCard.tsx create mode 100644 src/components/molecules/TreeListHeader.tsx create mode 100644 src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx create mode 100644 src/lib/json-ui/wrappers/NavigationItemWrapper.tsx create mode 100644 src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx create mode 100644 src/lib/json-ui/wrappers/TreeCardWrapper.tsx create mode 100644 src/lib/json-ui/wrappers/definitions/loading-fallback.json create mode 100644 src/lib/json-ui/wrappers/definitions/navigation-item.json create mode 100644 src/lib/json-ui/wrappers/definitions/page-header-content.json create mode 100644 src/lib/json-ui/wrappers/definitions/tree-card.json diff --git a/src/components/molecules/TreeCard.tsx b/src/components/molecules/TreeCard.tsx new file mode 100644 index 0000000..e42a52c --- /dev/null +++ b/src/components/molecules/TreeCard.tsx @@ -0,0 +1,75 @@ +import { Card, Badge, ActionIcon, IconButton, Stack, Flex, Text, Heading } from '@/components/atoms' +import { ComponentTree } from '@/types/project' + +interface TreeCardProps { + tree: ComponentTree + isSelected: boolean + onSelect: () => void + onEdit: () => void + onDuplicate: () => void + onDelete: () => void + disableDelete?: boolean +} + +export function TreeCard({ + tree, + isSelected, + onSelect, + onEdit, + onDuplicate, + onDelete, + disableDelete = false, +}: TreeCardProps) { + return ( + + + + + {tree.name} + {tree.description && ( + + {tree.description} + + )} +
+ + {tree.rootNodes.length} components + +
+
+
+
e.stopPropagation()}> + + } + variant="ghost" + size="sm" + onClick={onEdit} + title="Edit tree" + /> + } + variant="ghost" + size="sm" + onClick={onDuplicate} + title="Duplicate tree" + /> + } + variant="ghost" + size="sm" + onClick={onDelete} + disabled={disableDelete} + title="Delete tree" + /> + +
+
+
+ ) +} diff --git a/src/components/molecules/TreeListHeader.tsx b/src/components/molecules/TreeListHeader.tsx new file mode 100644 index 0000000..d381b2c --- /dev/null +++ b/src/components/molecules/TreeListHeader.tsx @@ -0,0 +1,53 @@ +import { Button, TreeIcon, ActionIcon, Flex, Heading, Stack, IconButton } from '@/components/atoms' + +interface TreeListHeaderProps { + onCreateNew: () => void + onImportJson: () => void + onExportJson: () => void + hasSelectedTree?: boolean +} + +export function TreeListHeader({ + onCreateNew, + onImportJson, + onExportJson, + hasSelectedTree = false, +}: TreeListHeaderProps) { + return ( + + + + + Component Trees + + } + size="sm" + onClick={onCreateNew} + /> + + + + + + + + ) +} diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts index a23d2d5..92926f1 100644 --- a/src/components/molecules/index.ts +++ b/src/components/molecules/index.ts @@ -18,3 +18,9 @@ export { SearchInput } from './SearchInput' export { BindingEditor } from './BindingEditor' export { DataSourceEditorDialog } from './DataSourceEditorDialog' export { ComponentBindingDialog } from './ComponentBindingDialog' +export { TreeCard } from './TreeCard' +export { TreeListHeader } from './TreeListHeader' +export { LoadingFallbackWrapper as LoadingFallback } from '@/lib/json-ui/wrappers' +export { NavigationItemWrapper as NavigationItem } from '@/lib/json-ui/wrappers' +export { PageHeaderContentWrapper as PageHeaderContent } from '@/lib/json-ui/wrappers' +export { preloadMonacoEditor } from './LazyMonacoEditor' diff --git a/src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx b/src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx new file mode 100644 index 0000000..caa2251 --- /dev/null +++ b/src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx @@ -0,0 +1,16 @@ +import { ComponentRenderer } from '@/lib/json-ui/component-renderer' +import loadingFallbackDef from './definitions/loading-fallback.json' + +interface LoadingFallbackWrapperProps { + message?: string +} + +export function LoadingFallbackWrapper({ message = 'Loading...' }: LoadingFallbackWrapperProps) { + return ( + + ) +} diff --git a/src/lib/json-ui/wrappers/NavigationItemWrapper.tsx b/src/lib/json-ui/wrappers/NavigationItemWrapper.tsx new file mode 100644 index 0000000..6746b54 --- /dev/null +++ b/src/lib/json-ui/wrappers/NavigationItemWrapper.tsx @@ -0,0 +1,27 @@ +import { ComponentRenderer } from '@/lib/json-ui/component-renderer' +import navigationItemDef from './definitions/navigation-item.json' + +interface NavigationItemWrapperProps { + icon: React.ReactNode + label: string + isActive: boolean + badge?: number + onClick: () => void +} + +export function NavigationItemWrapper(props: NavigationItemWrapperProps) { + return ( +
+ +
+ ) +} diff --git a/src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx b/src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx new file mode 100644 index 0000000..bd68fd0 --- /dev/null +++ b/src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx @@ -0,0 +1,22 @@ +import { ComponentRenderer } from '@/lib/json-ui/component-renderer' +import pageHeaderContentDef from './definitions/page-header-content.json' + +interface PageHeaderContentWrapperProps { + title: string + icon: React.ReactNode + description?: string +} + +export function PageHeaderContentWrapper(props: PageHeaderContentWrapperProps) { + return ( + + ) +} diff --git a/src/lib/json-ui/wrappers/TreeCardWrapper.tsx b/src/lib/json-ui/wrappers/TreeCardWrapper.tsx new file mode 100644 index 0000000..b64409e --- /dev/null +++ b/src/lib/json-ui/wrappers/TreeCardWrapper.tsx @@ -0,0 +1,33 @@ +import { ComponentRenderer } from '@/lib/json-ui/component-renderer' +import treeCardDefinition from './definitions/tree-card.json' +import { ComponentTree } from '@/types/project' + +interface TreeCardWrapperProps { + tree: ComponentTree + isSelected: boolean + onSelect: () => void + onEdit: () => void + onDuplicate: () => void + onDelete: () => void + disableDelete?: boolean +} + +export function TreeCardWrapper(props: TreeCardWrapperProps) { + return ( +
+ +
e.stopPropagation()}> + + + +
+
+ ) +} diff --git a/src/lib/json-ui/wrappers/definitions/loading-fallback.json b/src/lib/json-ui/wrappers/definitions/loading-fallback.json new file mode 100644 index 0000000..a2c7101 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/loading-fallback.json @@ -0,0 +1,32 @@ +{ + "id": "loading-container", + "type": "div", + "props": { + "className": "flex items-center justify-center h-full w-full" + }, + "children": [ + { + "id": "loading-content", + "type": "div", + "props": { + "className": "flex flex-col items-center gap-3" + }, + "children": [ + { + "id": "loading-spinner", + "type": "LoadingSpinner" + }, + { + "id": "loading-message", + "type": "p", + "props": { + "className": "text-sm text-muted-foreground" + }, + "bindings": { + "children": "message" + } + } + ] + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/navigation-item.json b/src/lib/json-ui/wrappers/definitions/navigation-item.json new file mode 100644 index 0000000..4015dd6 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/navigation-item.json @@ -0,0 +1,37 @@ +{ + "id": "nav-item-button", + "type": "button", + "bindings": { + "className": { + "source": "isActive", + "transform": "data ? 'w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors bg-primary text-primary-foreground' : 'w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors hover:bg-muted text-foreground'" + } + }, + "children": [ + { + "id": "nav-item-icon", + "type": "IconWrapper", + "bindings": { + "icon": "icon", + "variant": { "source": "isActive", "transform": "data ? 'default' : 'muted'" } + }, + "props": { "size": "md" } + }, + { + "id": "nav-item-label", + "type": "Text", + "props": { "className": "flex-1 text-left font-medium", "variant": "small" }, + "bindings": { "children": "label" } + }, + { + "id": "nav-item-badge", + "type": "Badge", + "bindings": { + "variant": { "source": "isActive", "transform": "data ? 'secondary' : 'destructive'" }, + "children": "badge" + }, + "props": { "className": "ml-auto" }, + "conditional": { "if": "badge !== undefined && badge > 0" } + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/page-header-content.json b/src/lib/json-ui/wrappers/definitions/page-header-content.json new file mode 100644 index 0000000..8cd8ad3 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/page-header-content.json @@ -0,0 +1,33 @@ +{ + "id": "page-header-container", + "type": "div", + "props": { "className": "flex items-center gap-3" }, + "children": [ + { + "id": "page-header-icon", + "type": "TabIcon", + "bindings": { "icon": "icon" }, + "props": { "variant": "gradient" } + }, + { + "id": "page-header-content", + "type": "div", + "props": { "className": "min-w-0" }, + "children": [ + { + "id": "page-header-title", + "type": "h2", + "props": { "className": "text-lg sm:text-xl font-bold truncate" }, + "bindings": { "children": "title" } + }, + { + "id": "page-header-description", + "type": "p", + "props": { "className": "text-xs sm:text-sm text-muted-foreground hidden sm:block" }, + "bindings": { "children": "description" }, + "conditional": { "if": "description" } + } + ] + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/tree-card.json b/src/lib/json-ui/wrappers/definitions/tree-card.json new file mode 100644 index 0000000..313b71e --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/tree-card.json @@ -0,0 +1,63 @@ +{ + "id": "tree-card-container", + "type": "Card", + "bindings": { + "className": { + "source": "isSelected", + "transform": "data ? 'cursor-pointer transition-all p-4 ring-2 ring-primary bg-accent' : 'cursor-pointer transition-all p-4 hover:bg-accent/50'" + } + }, + "children": [ + { + "id": "tree-card-content", + "type": "Stack", + "props": { "spacing": "sm" }, + "children": [ + { + "id": "tree-card-header", + "type": "Flex", + "props": { "justify": "between", "align": "start", "gap": "sm" }, + "children": [ + { + "id": "tree-card-info", + "type": "Stack", + "props": { "spacing": "xs", "className": "flex-1 min-w-0" }, + "children": [ + { + "id": "tree-card-name", + "type": "Heading", + "props": { "level": 4, "className": "text-sm truncate" }, + "bindings": { "children": "tree.name" } + }, + { + "id": "tree-card-description", + "type": "Text", + "props": { "variant": "caption", "className": "line-clamp-2" }, + "bindings": { "children": "tree.description" }, + "conditional": { "if": "tree.description" } + }, + { + "id": "tree-card-badge-container", + "type": "div", + "children": [ + { + "id": "tree-card-badge", + "type": "Badge", + "props": { "variant": "outline", "className": "text-xs" }, + "bindings": { + "children": { + "source": "tree.rootNodes.length", + "transform": "`${data} components`" + } + } + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/lib/json-ui/wrappers/index.ts b/src/lib/json-ui/wrappers/index.ts index 11b179b..0377fde 100644 --- a/src/lib/json-ui/wrappers/index.ts +++ b/src/lib/json-ui/wrappers/index.ts @@ -8,3 +8,7 @@ export { GitHubBuildStatusWrapper } from './GitHubBuildStatusWrapper' export { ComponentBindingDialogWrapper } from './ComponentBindingDialogWrapper' export { DataSourceEditorDialogWrapper } from './DataSourceEditorDialogWrapper' export { ComponentTreeWrapper } from './ComponentTreeWrapper' +export { LoadingFallbackWrapper } from './LoadingFallbackWrapper' +export { NavigationItemWrapper } from './NavigationItemWrapper' +export { PageHeaderContentWrapper } from './PageHeaderContentWrapper' +export { LoadingFallbackWrapper } from './LoadingFallbackWrapper'