From 6825fc9fd98ac1fd1c584dcf6d95b420b423ee0f Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 9 Apr 2026 08:16:21 +0000 Subject: [PATCH 1/4] Add tabbed layout for object detail page Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/32d509ca-9783-48ab-a16a-0182b122bfc7 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- .../schema/ObjectDetailTabsWidget.tsx | 137 ++++++++++++++++++ .../schema/registerObjectDetailWidgets.ts | 8 + .../src/schemas/objectDetailPageSchema.ts | 23 +-- 3 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 apps/console/src/components/schema/ObjectDetailTabsWidget.tsx diff --git a/apps/console/src/components/schema/ObjectDetailTabsWidget.tsx b/apps/console/src/components/schema/ObjectDetailTabsWidget.tsx new file mode 100644 index 00000000..de297c22 --- /dev/null +++ b/apps/console/src/components/schema/ObjectDetailTabsWidget.tsx @@ -0,0 +1,137 @@ +/** + * Object Detail Tabs Widget + * + * Self-contained, schema-driven tabbed widget for the object detail page. + * Organizes object configuration into logical tabs similar to Power Apps: + * - Details: Object properties and basic information + * - Fields: Field designer for managing object fields + * - Relationships: Relationships and unique keys + * - Data: Data preview and experience placeholders + * + * Schema: { type: 'object-detail-tabs', objectName: 'account' } + * + * @module components/schema/ObjectDetailTabsWidget + */ + +import { useState } from 'react'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@object-ui/components'; +import { Settings2, Columns, Link2, Table } from 'lucide-react'; +import type { SchemaNode } from '@object-ui/core'; +import { + ObjectPropertiesWidget, + ObjectRelationshipsWidget, + ObjectKeysWidget, + ObjectDataExperienceWidget, + ObjectDataPreviewWidget, +} from './objectDetailWidgets'; +import { ObjectFieldDesignerWidget } from './ObjectFieldDesignerWidget'; + +/** Schema props for object detail tabs widget. */ +interface ObjectDetailTabsSchema extends SchemaNode { + objectName: string; +} + +export function ObjectDetailTabsWidget({ schema }: { schema: ObjectDetailTabsSchema }) { + const objectName = schema.objectName; + const [activeTab, setActiveTab] = useState('details'); + + // Create schema objects for each widget + const detailsSchema: SchemaNode & { objectName: string } = { + type: 'object-properties', + id: `${objectName}-properties`, + objectName, + }; + + const fieldsSchema: SchemaNode & { objectName: string } = { + type: 'object-field-designer', + id: `${objectName}-field-designer`, + objectName, + }; + + const relationshipsSchema: SchemaNode & { objectName: string } = { + type: 'object-relationships', + id: `${objectName}-relationships`, + objectName, + }; + + const keysSchema: SchemaNode & { objectName: string } = { + type: 'object-keys', + id: `${objectName}-keys`, + objectName, + }; + + const dataExperienceSchema: SchemaNode & { objectName: string } = { + type: 'object-data-experience', + id: `${objectName}-data-experience`, + objectName, + }; + + const dataPreviewSchema: SchemaNode & { objectName: string } = { + type: 'object-data-preview', + id: `${objectName}-data-preview`, + objectName, + }; + + return ( +
+ + + + + Details + + + + Fields + + + + Relationships + + + + Data + + + + +
+ +
+
+ + +
+ +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + + ); +} diff --git a/apps/console/src/components/schema/registerObjectDetailWidgets.ts b/apps/console/src/components/schema/registerObjectDetailWidgets.ts index 46b93df3..6c66ad35 100644 --- a/apps/console/src/components/schema/registerObjectDetailWidgets.ts +++ b/apps/console/src/components/schema/registerObjectDetailWidgets.ts @@ -6,6 +6,7 @@ * so that SchemaRenderer can resolve these types when rendering PageSchema. * * Widget types registered: + * - `object-detail-tabs` — Tabbed layout for object detail page * - `object-properties` — Object property card * - `object-relationships` — Relationships card * - `object-keys` — Keys card @@ -25,12 +26,19 @@ import { ObjectDataPreviewWidget, } from './objectDetailWidgets'; import { ObjectFieldDesignerWidget } from './ObjectFieldDesignerWidget'; +import { ObjectDetailTabsWidget } from './ObjectDetailTabsWidget'; const widgetMeta = { namespace: 'console', category: 'system', }; +ComponentRegistry.register('object-detail-tabs', ObjectDetailTabsWidget, { + ...widgetMeta, + label: 'Object Detail Tabs', + icon: 'LayoutDashboard', +}); + ComponentRegistry.register('object-properties', ObjectPropertiesWidget, { ...widgetMeta, label: 'Object Properties', diff --git a/apps/console/src/schemas/objectDetailPageSchema.ts b/apps/console/src/schemas/objectDetailPageSchema.ts index c79f4368..4a095d79 100644 --- a/apps/console/src/schemas/objectDetailPageSchema.ts +++ b/apps/console/src/schemas/objectDetailPageSchema.ts @@ -1,10 +1,10 @@ /** * Object Detail Page Schema Factory * - * Generates a PageSchema for the object detail page. The schema uses custom - * widget types (registered in ComponentRegistry via registerObjectDetailWidgets) - * that are fully self-contained — each widget resolves its data from React - * context rather than requiring prop drilling. + * Generates a PageSchema for the object detail page with a tabbed layout. + * The schema uses custom widget types (registered in ComponentRegistry via + * registerObjectDetailWidgets) that are fully self-contained — each widget + * resolves its data from React context rather than requiring prop drilling. * * Usage: * const schema = buildObjectDetailPageSchema('account', metadataItem); @@ -21,7 +21,13 @@ interface ObjectWidgetNode extends BaseSchema { } /** - * Build a PageSchema for an object detail page. + * Build a PageSchema for an object detail page with tabbed navigation. + * + * Tabs: + * - Details: Object properties and basic information + * - Fields: Field designer for managing object fields + * - Relationships: Relationships and unique keys + * - Data: Data preview and experience placeholders * * @param objectName - The API name of the object (e.g. 'account') * @param item - The raw metadata item (optional, used for title/description) @@ -35,12 +41,7 @@ export function buildObjectDetailPageSchema( const description = (item?.description as string) || objectName; const widgets: ObjectWidgetNode[] = [ - { type: 'object-properties', id: `${objectName}-properties`, objectName }, - { type: 'object-relationships', id: `${objectName}-relationships`, objectName }, - { type: 'object-keys', id: `${objectName}-keys`, objectName }, - { type: 'object-data-experience', id: `${objectName}-data-experience`, objectName }, - { type: 'object-data-preview', id: `${objectName}-data-preview`, objectName }, - { type: 'object-field-designer', id: `${objectName}-field-designer`, objectName }, + { type: 'object-detail-tabs', id: `${objectName}-tabs`, objectName }, ]; return { From b10d60883bed89cfe51102a7b49098e276b6190c Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 9 Apr 2026 08:18:11 +0000 Subject: [PATCH 2/4] Improve widget styling with cleaner Power Apps-inspired layout Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/32d509ca-9783-48ab-a16a-0182b122bfc7 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- .../components/schema/objectDetailWidgets.tsx | 283 ++++++++++-------- 1 file changed, 163 insertions(+), 120 deletions(-) diff --git a/apps/console/src/components/schema/objectDetailWidgets.tsx b/apps/console/src/components/schema/objectDetailWidgets.tsx index 51943ed9..83aa4e11 100644 --- a/apps/console/src/components/schema/objectDetailWidgets.tsx +++ b/apps/console/src/components/schema/objectDetailWidgets.tsx @@ -14,7 +14,6 @@ import { useMemo } from 'react'; import { Badge } from '@object-ui/components'; import { - Settings2, Link2, KeyRound, LayoutList, @@ -75,52 +74,63 @@ export function ObjectPropertiesWidget({ schema }: { schema: ObjectWidgetSchema if (!object) return null; return ( -
-

- - Object Properties -

-
-
- API Name -

{object.name}

-
-
- Label -

{object.label}

-
- {object.pluralLabel && ( -
- Plural Label -

{object.pluralLabel}

+
+ {/* Basic Information Section */} +
+

+ Basic Information +

+
+
+ +

{object.label}

- )} - {object.group && ( -
- Group -

{object.group}

+
+ +

{object.name}

- )} -
- Status -

- - {object.enabled !== false ? 'Enabled' : 'Disabled'} - -

-
-
- Fields - {object.fieldCount ?? fields.length} + {object.pluralLabel && ( +
+ +

{object.pluralLabel}

+
+ )} + {object.group && ( +
+ +

{object.group}

+
+ )}
- {object.isSystem && ( -
- Type -

- System Object -

+
+ + {/* Configuration Section */} +
+

+ Configuration +

+
+
+ +
+ + {object.enabled !== false ? 'Enabled' : 'Disabled'} + +
- )} +
+ +

{object.fieldCount ?? fields.length} fields

+
+ {object.isSystem && ( +
+ +
+ System Object +
+
+ )} +
); @@ -137,34 +147,47 @@ export function ObjectRelationshipsWidget({ schema }: { schema: ObjectWidgetSche if (!object) return null; + const hasRelationships = object.relationships && object.relationships.length > 0; + return ( -
-

- - Relationships -

- {object.relationships && object.relationships.length > 0 ? ( -
- {object.relationships.map((rel, i) => ( -
- - {rel.type} - -
- {rel.label || rel.relatedObject} - {rel.label && rel.label !== rel.relatedObject && ( - → {rel.relatedObject} - )} - {rel.foreignKey && ( - (FK: {rel.foreignKey}) - )} +
+
+

+ Relationships +

+ {hasRelationships ? ( +
+ {object.relationships.map((rel, i) => ( +
+ + {rel.type} + +
+

{rel.label || rel.relatedObject}

+ {rel.label && rel.label !== rel.relatedObject && ( +

+ Related Object: {rel.relatedObject} +

+ )} + {rel.foreignKey && ( +

+ Foreign Key: {rel.foreignKey} +

+ )} +
-
- ))} -
- ) : ( -

No relationships defined for this object.

- )} + ))} +
+ ) : ( +
+ +

No relationships defined for this object.

+
+ )} +
); } @@ -184,28 +207,40 @@ export function ObjectKeysWidget({ schema }: { schema: ObjectWidgetSchema }) { ); return ( -
-

- - Keys -

- {keyFields.length > 0 ? ( -
- {keyFields.map((kf) => ( -
- - {kf.name === 'id' ? 'Primary Key' : kf.externalId ? 'External ID' : 'Unique'} - -
- {kf.label || kf.name} - ({kf.type}) +
+
+

+ Unique Keys +

+ {keyFields.length > 0 ? ( +
+ {keyFields.map((kf) => ( +
+ + {kf.name === 'id' ? 'Primary Key' : kf.externalId ? 'External ID' : 'Unique'} + +
+

{kf.label || kf.name}

+

+ Type: {kf.type} +

+
-
- ))} -
- ) : ( -

No unique keys or primary keys found.

- )} + ))} +
+ ) : ( +
+ +

No unique keys or primary keys found.

+
+ )} +
); } @@ -217,26 +252,30 @@ export function ObjectKeysWidget({ schema }: { schema: ObjectWidgetSchema }) { export function ObjectDataExperienceWidget(_props: { schema: ObjectWidgetSchema }) { return ( -
-

- - Data Experience -

-
-
- -

Forms

-

Design forms for data entry

-
-
- -

Views

-

Configure list and detail views

-
-
- -

Dashboards

-

Build visual dashboards

+
+
+

+ Data Experience +

+

+ Configure how users interact with data in this object +

+
+
+ +

Forms

+

Design forms for data entry

+
+
+ +

Views

+

Configure list and detail views

+
+
+ +

Dashboards

+

Build visual dashboards

+
@@ -253,17 +292,21 @@ export function ObjectDataPreviewWidget({ schema }: { schema: ObjectWidgetSchema const { object } = useObjectData(objectName); return ( -
-

-

- Data Preview - -
-
-

Sample Data

-

- Live data preview for “{object?.label || objectName}” will be available here +

+
+

+ Data Preview +

+

+ View sample records from this object

+
+
+

Sample Data

+

+ Live data preview for “{object?.label || objectName}” will be available here +

+ ); From 6f8c952ac0765780d98a7327d8b5d54d0797e841 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 9 Apr 2026 08:18:56 +0000 Subject: [PATCH 3/4] Update tests to support new tabbed layout Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/32d509ca-9783-48ab-a16a-0182b122bfc7 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- .../src/__tests__/MetadataDetailPage.test.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/console/src/__tests__/MetadataDetailPage.test.tsx b/apps/console/src/__tests__/MetadataDetailPage.test.tsx index b5372ffc..5533f2f8 100644 --- a/apps/console/src/__tests__/MetadataDetailPage.test.tsx +++ b/apps/console/src/__tests__/MetadataDetailPage.test.tsx @@ -77,6 +77,7 @@ beforeEach(() => { ); + ComponentRegistry.register('object-detail-tabs', mockWidget('object-detail-tabs')); ComponentRegistry.register('object-properties', mockWidget('object-properties')); ComponentRegistry.register('object-relationships', mockWidget('object-relationships')); ComponentRegistry.register('object-keys', mockWidget('object-keys')); @@ -209,7 +210,7 @@ describe('MetadataDetailPage', () => { expect(screen.getByTestId('schema-detail-content')).toBeInTheDocument(); }); - it('should render all object detail widget sections', () => { + it('should render object detail tabs widget', () => { mockGetItems.mockResolvedValue([ { name: 'account', label: 'Accounts', description: 'Customer accounts' }, ]); @@ -223,13 +224,8 @@ describe('MetadataDetailPage', () => { , ); - // All widget sections should be rendered via SchemaRenderer - expect(screen.getByTestId('mock-object-properties')).toBeInTheDocument(); - expect(screen.getByTestId('mock-object-relationships')).toBeInTheDocument(); - expect(screen.getByTestId('mock-object-keys')).toBeInTheDocument(); - expect(screen.getByTestId('mock-object-data-experience')).toBeInTheDocument(); - expect(screen.getByTestId('mock-object-data-preview')).toBeInTheDocument(); - expect(screen.getByTestId('mock-object-field-designer')).toBeInTheDocument(); + // The tabbed widget should be rendered + expect(screen.getByTestId('mock-object-detail-tabs')).toBeInTheDocument(); }); it('should navigate back to object list route when back button is clicked', () => { From 05d7e5cfdc0a4388e149080233881d5478f638b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 08:36:52 +0000 Subject: [PATCH 4/4] fix: address all PR review comments - use SchemaRenderer, semantic HTML, pluralization, remove false affordance Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/f45bbfad-453a-42a8-9f55-09107853caaa Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../schema/ObjectDetailTabsWidget.tsx | 21 +++----- .../components/schema/objectDetailWidgets.tsx | 50 ++++++++++--------- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/apps/console/src/components/schema/ObjectDetailTabsWidget.tsx b/apps/console/src/components/schema/ObjectDetailTabsWidget.tsx index de297c22..56703fe4 100644 --- a/apps/console/src/components/schema/ObjectDetailTabsWidget.tsx +++ b/apps/console/src/components/schema/ObjectDetailTabsWidget.tsx @@ -17,14 +17,7 @@ import { useState } from 'react'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@object-ui/components'; import { Settings2, Columns, Link2, Table } from 'lucide-react'; import type { SchemaNode } from '@object-ui/core'; -import { - ObjectPropertiesWidget, - ObjectRelationshipsWidget, - ObjectKeysWidget, - ObjectDataExperienceWidget, - ObjectDataPreviewWidget, -} from './objectDetailWidgets'; -import { ObjectFieldDesignerWidget } from './ObjectFieldDesignerWidget'; +import { SchemaRenderer } from '@object-ui/react'; /** Schema props for object detail tabs widget. */ interface ObjectDetailTabsSchema extends SchemaNode { @@ -108,27 +101,27 @@ export function ObjectDetailTabsWidget({ schema }: { schema: ObjectDetailTabsSch
- +
- +
- - + +
- - + +
diff --git a/apps/console/src/components/schema/objectDetailWidgets.tsx b/apps/console/src/components/schema/objectDetailWidgets.tsx index 83aa4e11..88471085 100644 --- a/apps/console/src/components/schema/objectDetailWidgets.tsx +++ b/apps/console/src/components/schema/objectDetailWidgets.tsx @@ -80,28 +80,28 @@ export function ObjectPropertiesWidget({ schema }: { schema: ObjectWidgetSchema

Basic Information

-
+
- -

{object.label}

+
Display Name
+
{object.label}
- -

{object.name}

+
API Name
+
{object.name}
{object.pluralLabel && (
- -

{object.pluralLabel}

+
Plural Label
+
{object.pluralLabel}
)} {object.group && (
- -

{object.group}

+
Group
+
{object.group}
)} -
+ {/* Configuration Section */} @@ -109,28 +109,32 @@ export function ObjectPropertiesWidget({ schema }: { schema: ObjectWidgetSchema

Configuration

-
+
- -
+
Status
+
{object.enabled !== false ? 'Enabled' : 'Disabled'} -
+
- -

{object.fieldCount ?? fields.length} fields

+
Field Count
+
+ {(object.fieldCount ?? fields.length) === 1 + ? '1 field' + : `${object.fieldCount ?? fields.length} fields`} +
{object.isSystem && (
- -
+
Type
+
System Object -
+
)} -
+ ); @@ -261,17 +265,17 @@ export function ObjectDataExperienceWidget(_props: { schema: ObjectWidgetSchema Configure how users interact with data in this object

-
+

Forms

Design forms for data entry

-
+

Views

Configure list and detail views

-
+

Dashboards

Build visual dashboards