Skip to content
Closed
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
59 changes: 59 additions & 0 deletions packages/design-system/src/components/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,65 @@ export const MasterCellWithActions: StoryFn<typeof meta> = () => {
);
};

export const MasterCellWithPreviewDrawer: StoryFn<typeof meta> = () => {
const [sorting, setSorting] = useState<TableSortingState>([]);

const data = useMemo(
() =>
Array.from({ length: 4 }, (_, batch) =>
securityEvents.map(row => ({
...row,
id: `${row.id}-${batch}`,
objectName: batch === 0 ? row.objectName : `${row.objectName} (${batch + 1})`,
})),
).flat(),
[],
);

return (
<Table
className='max-w-920'
data={data}
columns={securityColumns}
getRowId={row => row.id}
sorting={sorting}
onSortingChange={setSorting}
renderPreviewContent={row => ({
title: row.original.objectName,
content: (
<VStack gap={16}>
<HStack gap={8}>
<Badge
variant='dotted'
color={row.original.status === 'Blocked' ? 'red' : 'yellow'}
type='secondary'
size='medium'
>
{row.original.status}
</Badge>
<InlineCodeSnippet code={row.original.parameter} size='sm' copyable={false} />
</HStack>
<VStack gap={4}>
<Text size='sm' color='secondary'>
Source: {row.original.sourceCountry} · {row.original.sourceProvider}
</Text>
<Text size='sm' color='secondary'>
First detected: {row.original.firstDetected}
</Text>
<Text size='sm' color='secondary'>
Last seen: {row.original.lastSeen}
</Text>
<Text size='sm' color='secondary'>
Security: {row.original.cweId}
</Text>
</VStack>
</VStack>
),
})}
/>
);
};

export const FullFeatured: StoryFn<typeof meta> = () => {
const data = useMemo(() => createLargeGroupedData(12, 50), []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import { Td } from '../primitives';
import { useTableContext } from '../TableContext';
import { TableMasterCellActions } from '../TableMasterCellActions';
import { TablePreviewToggle } from '../TablePreviewToggle';

interface TableBodyCellProps<T> {
cell: Cell<T, unknown>;
Expand Down Expand Up @@ -45,31 +44,22 @@ export const TableBodyCell = <T,>({
const content = flexRender(cell.column.columnDef.cell, cell.getContext());

const isMasterColumn = column.id === masterColumnId;
const hasAutoPreview = isMasterColumn && !!renderPreviewContent;
const hasActions = !!(hasAutoPreview || meta?.renderMenuAction);
const hasPreview = isMasterColumn && !!renderPreviewContent;
const hasMenuAction = !!meta?.renderMenuAction;
const hasActions = hasMenuAction;

const renderActions = () => {
const handleMasterCellClick = () => {
if (!hasPreview) return;
const rowId = cell.row.id;

return (
<>
{hasAutoPreview && (
<TablePreviewToggle
active={previewRowId === rowId}
onClick={() => setPreviewRowId(previewRowId === rowId ? null : rowId)}
/>
)}
{meta?.renderMenuAction?.(cell.row)}
</>
);
setPreviewRowId(previewRowId === rowId ? null : rowId);
};

const renderContent = () => {
if (isCut && hasActions) {
return (
<div className='flex items-center justify-between gap-2'>
<span className='min-w-0 [&>*]:block [&>*]:truncate'>{content}</span>
<TableMasterCellActions>{renderActions()}</TableMasterCellActions>
<TableMasterCellActions>{meta?.renderMenuAction?.(cell.row)}</TableMasterCellActions>
</div>
);
}
Expand All @@ -92,13 +82,15 @@ export const TableBodyCell = <T,>({
getAlignClass(meta),
getExpandBorderClass(isExpandColumn, cell.row.depth),
isCut && 'pr-0',
hasPreview && 'cursor-pointer',
meta?.cellClassName,
className,
)}
pinned={isPinned === 'left'}
lastPinnedLeft={disablePinnedShadow ? false : lastLeft}
expanded={isExpandedToggle}
ref={canDnd ? setNodeRef : undefined}
onClick={hasPreview ? handleMasterCellClick : undefined}
style={{
...pinningStyles,
width: cell.column.getSize(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { type ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
import {
closestCenter,
DndContext,
Expand Down Expand Up @@ -81,6 +81,8 @@ export const TableProvider = <T,>(props: TableProviderProps<T>) => {
onEndReached,
onEndReachedThreshold,
renderPreviewContent,
previewRowId: previewRowIdProp,
onPreviewRowChange,
} = props;

// Feature detection
Expand Down Expand Up @@ -284,8 +286,18 @@ export const TableProvider = <T,>(props: TableProviderProps<T>) => {
const theadRef = useRef<HTMLTableSectionElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);

// Preview drawer state
const [previewRowId, setPreviewRowId] = useState<string | null>(null);
// Preview drawer state (controlled/uncontrolled)
const [previewRowId = null, setPreviewRowIdInternal] = useControlled<string | null>({
controlled: previewRowIdProp,
default: null,
});
const setPreviewRowId = useCallback(
(id: string | null) => {
setPreviewRowIdInternal(id);
onPreviewRowChange?.(id);
},
[setPreviewRowIdInternal, onPreviewRowChange],
);

// Context value
const contextValue: TableContextValue<T> = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ export const TablePreviewDrawer: FC = () => {
>
{displayPreview && (
<DrawerContent>
{displayPreview.title && (
<DrawerHeader>
<DrawerTitle>{displayPreview.title}</DrawerTitle>
</DrawerHeader>
)}
<DrawerHeader>
{displayPreview.title && <DrawerTitle>{displayPreview.title}</DrawerTitle>}
</DrawerHeader>
<DrawerBody>{displayPreview.content}</DrawerBody>
</DrawerContent>
)}
Expand Down
6 changes: 5 additions & 1 deletion packages/design-system/src/components/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ export interface TableProps<T> extends TestableProps {

// --- Preview drawer ---
/** Render preview drawer for a row. Returns title and body content.
* When provided, a preview toggle button is automatically added to master cell actions. */
* When provided, clicking the master cell toggles the preview drawer. */
renderPreviewContent?: (row: TableRow<T>) => { title?: ReactNode; content: ReactNode };
/** Controlled preview row ID. Pass `null` to close. */
previewRowId?: string | null;
/** Callback when preview row changes (open/close/swap). */
onPreviewRowChange?: (rowId: string | null) => void;
}
Loading