| name | sidesheet |
| description | Build sidesheet tabs and detail panels for CC apps. USE FOR: creating sidesheet components, detail tabs, data fetching hooks (useQuery), sidesheet types, sidesheetConfig wiring, StyledSideSheetContainer, SidesheetHeader, CustomStyledTabs, tab navigation, MccrTab, MaterialTab, NotificationTab, ModelViewerTab. DO NOT USE FOR: workspace configuration (use workspace-config skill), column definitions (use table-config skill). |
Sidesheet
Each CC app has a sidesheet lib at libs/<appname>sidesheet/ that provides the detail panel shown when a user clicks an item in the grid or garden.
File Structure
libs/<appname>sidesheet/src/lib/
āāā index.ts # Re-exports
āāā types/
ā āāā index.ts
ā āāā <entity>Mccr.ts # Checklist type
ā āāā <entity>Material.ts # Material type
ā āāā <entity>Notification.ts # Notification type
āāā ui-sidesheet/
ā āāā index.ts
ā āāā <Entity>Sidesheet.tsx # Main sidesheet with tabs
ā āāā DetailsTab.tsx # Details tab content
āāā utils-sidesheet/
āāā index.ts
āāā useMccr.ts # Fetch checklists
āāā useMaterial.ts # Fetch materials
āāā useNotifications.ts # Fetch notifications
Sidesheet Component Pattern
import { Tabs } from '@equinor/eds-core-react';
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import {
LinkCell, MaterialTab, MccrTab, NotificationTab,
useContextId, useHttpClient,
} from '@cc-components/shared';
import {
BannerItem, CustomStyledPanels, CustomStyledTabs,
SidesheetHeader, SidesheetSkeleton, StyledBanner,
StyledPanel, StyledSideSheetContainer, StyledTabListWrapper,
StyledTabsList, TabTitle,
} from '@cc-components/sharedcomponents';
import { <Entity> } from '@cc-components/<appname>shared';
import { useMccr, useMaterial } from '../utils-sidesheet';
import { DetailsTab } from './DetailsTab';
export const <Entity>Sidesheet = (props: {
id: string;
item?: <Entity>;
close: VoidFunction;
}) => {
const [activeTab, setActiveTab] = useState(0);
const client = useHttpClient();
const contextId = useContextId();
const { data, error, isLoading } = useQuery<Entity>({
queryKey: ['<entity>', props.id],
queryFn: async ({ signal }) => {
const res = await client.fetch(
`/api/contexts/${contextId}/<api-route>/${props.id}`,
{ signal }
);
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
},
});
const { mccr, isFetching: isFetchingMccr } = useMccr(props.id);
const { material, isFetching: isFetchingMaterial } = useMaterial(props.id);
if (isLoading) return <SidesheetSkeleton close={props.close} />;
if (error || !data) return <div>Error loading data</div>;
return (
<StyledSideSheetContainer>
<SidesheetHeader title={data.title} onClose={props.close} />
<StyledBanner>
<BannerItem title="Status" value={data.status} />
{/* Additional banner items */}
</StyledBanner>
<CustomStyledTabs activeTab={activeTab} onChange={setActiveTab}>
<StyledTabListWrapper>
<StyledTabsList>
<Tabs.Tab>Details</Tabs.Tab>
<Tabs.Tab>
<TabTitle title="Checklists" data={mccr} isLoading={isFetchingMccr} />
</Tabs.Tab>
<Tabs.Tab>
<TabTitle title="Material" data={material} isLoading={isFetchingMaterial} />
</Tabs.Tab>
</StyledTabsList>
</StyledTabListWrapper>
<CustomStyledPanels>
<StyledPanel><DetailsTab entity={data} /></StyledPanel>
<StyledPanel><MccrTab mccr={mccr} isFetching={isFetchingMccr} /></StyledPanel>
<StyledPanel><MaterialTab material={material} isFetching={isFetchingMaterial} /></StyledPanel>
</CustomStyledPanels>
</CustomStyledTabs>
</StyledSideSheetContainer>
);
};
Data Fetching Hook Pattern
import { useContextId } from '@cc-components/shared';
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
import { useQuery } from '@tanstack/react-query';
import { <EntityMccr> } from '../types';
export const useMccr = (packageId: string | null) => {
const ccApp = useHttpClient('cc-app');
const contextId = useContextId();
const { data, isFetching, error } = useQuery({
queryFn: async ({ signal }) => {
const res = await ccApp.fetch(
`/api/contexts/${contextId}/<api-route>/${packageId}/checklists`,
{ signal }
);
if (!res.ok) throw new Error('Failed to fetch');
return (await res.json()) as <EntityMccr>[];
},
queryKey: ['mccr', packageId],
enabled: !!packageId,
});
return { mccr: data, isFetching, error };
};
Sidesheet Config Wiring (sidesheetConfig.tsx)
import { <Entity> } from '@cc-components/<appname>shared';
import { SidesheetConfig } from '@equinor/workspace-fusion/sidesheet';
import { <Entity>Sidesheet } from '@cc-components/<appname>sidesheet';
export const sidesheetConfig: SidesheetConfig<<Entity>> = {
type: 'default',
DetailsSidesheet: <Entity>Sidesheet,
};
Shared Components
Import layout components from @cc-components/sharedcomponents:
| Component | Purpose |
|---|
StyledSideSheetContainer | Root container |
SidesheetHeader | Title bar with close button |
SidesheetSkeleton | Loading placeholder |
StyledBanner / BannerItem | Key-value banner row |
CustomStyledTabs / StyledTabsList / CustomStyledPanels | Tab navigation |
StyledPanel | Tab panel wrapper |
TabTitle | Tab label with count badge |
Shared tab components from @cc-components/shared:
| Component | Purpose |
|---|
MccrTab | Checklists table |
MaterialTab | Materials table |
NotificationTab | Notifications table |
ModelViewerTab | 3D model viewer (from @cc-components/modelviewer) |
Checklist