Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions apps/console/src/__tests__/MetadataDetailPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ beforeEach(() => {
</div>
);

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'));
Expand Down Expand Up @@ -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' },
]);
Expand All @@ -223,13 +224,8 @@ describe('MetadataDetailPage', () => {
</Routes>
</MemoryRouter>,
);
// 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', () => {
Expand Down
130 changes: 130 additions & 0 deletions apps/console/src/components/schema/ObjectDetailTabsWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* 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 { SchemaRenderer } from '@object-ui/react';

/** 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 (
<div className="w-full" data-testid="object-detail-tabs">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="w-full justify-start border-b rounded-none bg-transparent p-0 h-auto">
<TabsTrigger
value="details"
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
>
<Settings2 className="h-4 w-4 mr-2" />
Details
</TabsTrigger>
<TabsTrigger
value="fields"
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
>
<Columns className="h-4 w-4 mr-2" />
Fields
</TabsTrigger>
<TabsTrigger
value="relationships"
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
>
<Link2 className="h-4 w-4 mr-2" />
Relationships
</TabsTrigger>
<TabsTrigger
value="data"
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
>
<Table className="h-4 w-4 mr-2" />
Data
</TabsTrigger>
</TabsList>

<TabsContent value="details" className="mt-6">
<div className="space-y-6">
<SchemaRenderer schema={detailsSchema} />
</div>
</TabsContent>

<TabsContent value="fields" className="mt-6">
<div className="space-y-6">
<SchemaRenderer schema={fieldsSchema} />
</div>
</TabsContent>

<TabsContent value="relationships" className="mt-6">
<div className="space-y-6">
<SchemaRenderer schema={relationshipsSchema} />
<SchemaRenderer schema={keysSchema} />
</div>
</TabsContent>

<TabsContent value="data" className="mt-6">
<div className="space-y-6">
<SchemaRenderer schema={dataPreviewSchema} />
<SchemaRenderer schema={dataExperienceSchema} />
</div>
</TabsContent>
</Tabs>
</div>
);
}
Loading