From 32b14d23daa8fd384f211a3f374e539f00f7f7aa Mon Sep 17 00:00:00 2001 From: Oksana Klimova Date: Fri, 3 Apr 2026 12:33:05 +0200 Subject: [PATCH 1/6] fix(table): use DrawerHeader with close button in preview drawer Use DrawerHeader and DrawerClose in table preview drawer header instead of custom HStack layout. Extract shared SecurityPreviewContent component with showTitle flag to avoid content duplication. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/Table/Table.stories.tsx | 5 ++- .../Table/TableContext/TableProvider.tsx | 3 ++ .../components/Table/TableContext/types.ts | 1 + .../components/Table/TablePreviewDrawer.tsx | 24 ++++++++++--- .../src/components/Table/mocks.tsx | 36 +++++++++++++++++-- .../src/components/Table/types.ts | 2 ++ 6 files changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/design-system/src/components/Table/Table.stories.tsx b/packages/design-system/src/components/Table/Table.stories.tsx index fc1fe8a9..bf4fc20d 100644 --- a/packages/design-system/src/components/Table/Table.stories.tsx +++ b/packages/design-system/src/components/Table/Table.stories.tsx @@ -28,6 +28,8 @@ import { METHOD_COLORS, multiplySecurityEvents, renderSecurityPreview, + renderSecurityPreviewHeader, + renderSecurityPreviewWithTitle, type SecurityEvent, type SecurityHeaderEntry, securityColumnHelper, @@ -612,6 +614,7 @@ export const MasterCellWithActions: StoryFn = () => { columnSizing={columnSizing} onColumnSizingChange={setColumnSizing} previewTrigger='button' + renderPreviewHeader={renderSecurityPreviewHeader} renderPreviewContent={renderSecurityPreview} /> ); @@ -655,7 +658,7 @@ export const MasterCellWithPreviewDrawer: StoryFn = () => { getRowId={row => row.id} sorting={sorting} onSortingChange={setSorting} - renderPreviewContent={renderSecurityPreview} + renderPreviewContent={renderSecurityPreviewWithTitle} /> ); }; diff --git a/packages/design-system/src/components/Table/TableContext/TableProvider.tsx b/packages/design-system/src/components/Table/TableContext/TableProvider.tsx index 6c7d1daf..72603f0b 100644 --- a/packages/design-system/src/components/Table/TableContext/TableProvider.tsx +++ b/packages/design-system/src/components/Table/TableContext/TableProvider.tsx @@ -80,6 +80,7 @@ export const TableProvider = (props: TableProviderProps) => { overscan = TABLE_VIRTUALIZATION_OVERSCAN, onEndReached, onEndReachedThreshold, + renderPreviewHeader, renderPreviewContent, previewTrigger = 'master', previewRowId: previewRowIdProp, @@ -331,6 +332,7 @@ export const TableProvider = (props: TableProviderProps) => { onEndReachedThreshold, previewRowId, setPreviewRowId, + renderPreviewHeader: renderPreviewHeader as ((row: Row) => ReactNode) | undefined, renderPreviewContent: renderPreviewContent as ((row: Row) => ReactNode) | undefined, previewTrigger, }), @@ -359,6 +361,7 @@ export const TableProvider = (props: TableProviderProps) => { onEndReached, onEndReachedThreshold, previewRowId, + renderPreviewHeader, renderPreviewContent, previewTrigger, ], diff --git a/packages/design-system/src/components/Table/TableContext/types.ts b/packages/design-system/src/components/Table/TableContext/types.ts index 41aee505..48dedc85 100644 --- a/packages/design-system/src/components/Table/TableContext/types.ts +++ b/packages/design-system/src/components/Table/TableContext/types.ts @@ -56,6 +56,7 @@ export interface TableContextValue { // Preview drawer previewRowId: string | null; setPreviewRowId: (id: string | null) => void; + renderPreviewHeader?: (row: Row) => ReactNode; renderPreviewContent?: (row: Row) => ReactNode; previewTrigger: 'master' | 'button'; } diff --git a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx index f9ce5c41..336051df 100644 --- a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx +++ b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx @@ -4,14 +4,28 @@ import { Drawer, DrawerBody, DrawerContent, DrawerHeader } from '../Drawer'; import { useTableContext } from './TableContext'; export const TablePreviewDrawer: FC = () => { - const { table, previewRowId, setPreviewRowId, renderPreviewContent } = useTableContext(); + const { + table, + previewRowId, + setPreviewRowId, + renderPreviewHeader, + renderPreviewContent, + previewTrigger, + } = useTableContext(); const row = previewRowId ? table.getRowModel().rowsById[previewRowId] : undefined; + const header = + row && renderPreviewHeader && previewTrigger === 'button' + ? renderPreviewHeader(row) + : undefined; const preview = row && renderPreviewContent ? renderPreviewContent(row) : undefined; // Keep the last valid preview so drawer content doesn't flash empty during close animation + const lastHeaderRef = useRef(null); const lastPreviewRef = useRef(null); + if (header) lastHeaderRef.current = header; if (preview) lastPreviewRef.current = preview; + const displayHeader = header ?? lastHeaderRef.current; const displayPreview = preview ?? lastPreviewRef.current; if (!renderPreviewContent) return null; @@ -28,9 +42,11 @@ export const TablePreviewDrawer: FC = () => { width={960} > - - - + {displayHeader ?? ( + + + + )} {displayPreview} diff --git a/packages/design-system/src/components/Table/mocks.tsx b/packages/design-system/src/components/Table/mocks.tsx index 52509056..0350d9ae 100644 --- a/packages/design-system/src/components/Table/mocks.tsx +++ b/packages/design-system/src/components/Table/mocks.tsx @@ -4,6 +4,7 @@ import { abbreviateNumber } from '../../utils/abbreviateNumber'; import { Badge } from '../Badge'; import { InlineCodeSnippet } from '../CodeSnippet'; import type { CountryCode } from '../Country'; +import { DrawerHeader } from '../Drawer'; import { DropdownMenu, DropdownMenuContent, @@ -611,12 +612,29 @@ export const createLargeGroupedData = ( // Large flat data — for a Virtualized story // --------------------------------------------------------------------------- -/** Shared preview drawer content for security events */ -export const renderSecurityPreview = (row: { original: SecurityEvent }) => ( - +/** Shared preview drawer header for security events */ +export const renderSecurityPreviewHeader = (row: { original: SecurityEvent }) => ( + {row.original.objectName} + +); + +/** Preview drawer content for security events */ +const SecurityPreviewContent = ({ + row, + showTitle, +}: { + row: { original: SecurityEvent }; + showTitle?: boolean; +}) => ( + + {showTitle && ( + + {row.original.objectName} + + )} ( ); +SecurityPreviewContent.displayName = 'SecurityPreviewContent'; + +/** Preview content for use with header (objectName shown in header) */ +export const renderSecurityPreview = (row: { original: SecurityEvent }) => ( + +); + +/** Preview content for use without header (includes objectName) */ +export const renderSecurityPreviewWithTitle = (row: { original: SecurityEvent }) => ( + +); + /** Duplicate securityEvents N times with unique IDs */ export const multiplySecurityEvents = (times = 4): SecurityEvent[] => Array.from({ length: times }, (_, batch) => diff --git a/packages/design-system/src/components/Table/types.ts b/packages/design-system/src/components/Table/types.ts index 61aed4db..5db467aa 100644 --- a/packages/design-system/src/components/Table/types.ts +++ b/packages/design-system/src/components/Table/types.ts @@ -216,6 +216,8 @@ export interface TableProps extends TestableProps { onEndReachedThreshold?: number; // --- Preview drawer --- + /** Render preview drawer header for a row. Rendered inside DrawerHeader. */ + renderPreviewHeader?: (row: TableRow) => ReactNode; /** Render preview drawer content for a row. */ renderPreviewContent?: (row: TableRow) => ReactNode; /** How the preview drawer is triggered: From 3142c2643176b7cb5c5d53e10439cde62b1ceeab Mon Sep 17 00:00:00 2001 From: Oksana Klimova Date: Fri, 3 Apr 2026 12:43:41 +0200 Subject: [PATCH 2/6] fix(table): show preview header regardless of trigger type Remove previewTrigger guard from header rendering so renderPreviewHeader works with both master and button triggers. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/Table/TablePreviewDrawer.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx index 336051df..851f788f 100644 --- a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx +++ b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx @@ -4,20 +4,11 @@ import { Drawer, DrawerBody, DrawerContent, DrawerHeader } from '../Drawer'; import { useTableContext } from './TableContext'; export const TablePreviewDrawer: FC = () => { - const { - table, - previewRowId, - setPreviewRowId, - renderPreviewHeader, - renderPreviewContent, - previewTrigger, - } = useTableContext(); + const { table, previewRowId, setPreviewRowId, renderPreviewHeader, renderPreviewContent } = + useTableContext(); const row = previewRowId ? table.getRowModel().rowsById[previewRowId] : undefined; - const header = - row && renderPreviewHeader && previewTrigger === 'button' - ? renderPreviewHeader(row) - : undefined; + const header = row && renderPreviewHeader ? renderPreviewHeader(row) : undefined; const preview = row && renderPreviewContent ? renderPreviewContent(row) : undefined; // Keep the last valid preview so drawer content doesn't flash empty during close animation From be1867ad577df568205bde0dc41403c7827dd044 Mon Sep 17 00:00:00 2001 From: Oksana Klimova Date: Fri, 3 Apr 2026 13:10:21 +0200 Subject: [PATCH 3/6] fix(drawer): align header content to top Change DrawerHeader flex alignment from items-center to items-start so header content aligns to the top when it wraps to multiple lines. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/design-system/src/components/Drawer/DrawerHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/design-system/src/components/Drawer/DrawerHeader.tsx b/packages/design-system/src/components/Drawer/DrawerHeader.tsx index 5f61770b..e0701ccf 100644 --- a/packages/design-system/src/components/Drawer/DrawerHeader.tsx +++ b/packages/design-system/src/components/Drawer/DrawerHeader.tsx @@ -19,7 +19,7 @@ export const DrawerHeader: FC = ({ children, ref }) => { className={cn( 'relative shrink-0 w-full', 'bg-bg-surface-2', - 'flex items-center justify-between gap-12', + 'flex items-start justify-between gap-12', 'pt-16 pb-12 pl-24 pr-16', 'rounded-t-12', 'outline-none', From 7f7d6479008daecd76aa44eaf1bdffbdca107095 Mon Sep 17 00:00:00 2001 From: Oksana Klimova Date: Fri, 3 Apr 2026 13:22:25 +0200 Subject: [PATCH 4/6] refactor(table): group preview drawer props into single preview object Replace separate renderPreviewHeader, renderPreviewContent, previewTrigger, previewRowId, and onPreviewRowChange props with a single preview object. Add tooltipText support with 'Open preview' default on master cell for both trigger types. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/Table/Table.stories.tsx | 10 ++++---- .../Table/TableBody/TableBodyCell.tsx | 24 +++++++++++++------ .../Table/TableContext/TableProvider.tsx | 15 ++++++++---- .../components/Table/TableContext/types.ts | 1 + .../components/Table/hooks/usePreviewCell.ts | 12 ++++++++-- .../src/components/Table/index.ts | 1 + .../src/components/Table/types.ts | 22 +++++++++++------ 7 files changed, 60 insertions(+), 25 deletions(-) diff --git a/packages/design-system/src/components/Table/Table.stories.tsx b/packages/design-system/src/components/Table/Table.stories.tsx index bf4fc20d..5ddc390c 100644 --- a/packages/design-system/src/components/Table/Table.stories.tsx +++ b/packages/design-system/src/components/Table/Table.stories.tsx @@ -613,9 +613,11 @@ export const MasterCellWithActions: StoryFn = () => { onSortingChange={setSorting} columnSizing={columnSizing} onColumnSizingChange={setColumnSizing} - previewTrigger='button' - renderPreviewHeader={renderSecurityPreviewHeader} - renderPreviewContent={renderSecurityPreview} + preview={{ + trigger: 'button', + renderHeader: renderSecurityPreviewHeader, + renderContent: renderSecurityPreview, + }} /> ); }; @@ -658,7 +660,7 @@ export const MasterCellWithPreviewDrawer: StoryFn = () => { getRowId={row => row.id} sorting={sorting} onSortingChange={setSorting} - renderPreviewContent={renderSecurityPreviewWithTitle} + preview={{ renderContent: renderSecurityPreviewWithTitle }} /> ); }; diff --git a/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx b/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx index 9f70033d..846e0590 100644 --- a/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx +++ b/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx @@ -1,6 +1,7 @@ import { type Cell, flexRender } from '@tanstack/react-table'; import { cn } from '../../../utils/cn'; import { useTestId } from '../../../utils/testId'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../../Tooltip'; import { usePreviewCell } from '../hooks'; import { getAlignClass, @@ -39,20 +40,29 @@ export const TableBodyCell = ({ const { canDnd, setNodeRef, dndStyle } = useColumnDnd(column); const pinningStyles = getPinningStyles(column); const lastLeft = isLastPinnedLeft(column, allLeafColumns, column.id); - const { isMasterTrigger, isButtonTrigger, isActive, togglePreview } = usePreviewCell( - column.id, - cell.row.id, - ); + const { isMasterTrigger, isButtonTrigger, isActive, togglePreview, tooltipText } = + usePreviewCell(column.id, cell.row.id); const isCut = column.id === masterColumnId || meta?.resizeType === 'cut'; const content = flexRender(cell.column.columnDef.cell, cell.getContext()); const hasActions = isButtonTrigger || !!meta?.renderPreviewAction || !!meta?.renderMenuAction; const renderContent = () => { + const wrappedContent = tooltipText ? ( + + + {content} + + {tooltipText} + + ) : ( + {content} + ); + if (isCut && hasActions) { return (
- {content} + {wrappedContent} {isButtonTrigger && } {meta?.renderPreviewAction?.(cell.row)} @@ -65,12 +75,12 @@ export const TableBodyCell = ({ if (isCut) { return (
- {content} + {tooltipText ? wrappedContent : content}
); } - return content; + return tooltipText ? wrappedContent : content; }; return ( diff --git a/packages/design-system/src/components/Table/TableContext/TableProvider.tsx b/packages/design-system/src/components/Table/TableContext/TableProvider.tsx index 72603f0b..a2e6de9a 100644 --- a/packages/design-system/src/components/Table/TableContext/TableProvider.tsx +++ b/packages/design-system/src/components/Table/TableContext/TableProvider.tsx @@ -80,13 +80,16 @@ export const TableProvider = (props: TableProviderProps) => { overscan = TABLE_VIRTUALIZATION_OVERSCAN, onEndReached, onEndReachedThreshold, - renderPreviewHeader, - renderPreviewContent, - previewTrigger = 'master', - previewRowId: previewRowIdProp, - onPreviewRowChange, + preview, } = props; + const renderPreviewHeader = preview?.renderHeader; + const renderPreviewContent = preview?.renderContent; + const previewTrigger = preview?.trigger ?? 'master'; + const previewTooltipText = preview?.tooltipText ?? 'Open preview'; + const previewRowIdProp = preview?.rowId; + const onPreviewRowChange = preview?.onRowChange; + // Feature detection const sortingEnabled = !!onSortingChange; const selectionEnabled = !!onRowSelectionChange; @@ -335,6 +338,7 @@ export const TableProvider = (props: TableProviderProps) => { renderPreviewHeader: renderPreviewHeader as ((row: Row) => ReactNode) | undefined, renderPreviewContent: renderPreviewContent as ((row: Row) => ReactNode) | undefined, previewTrigger, + previewTooltipText, }), [ table, @@ -364,6 +368,7 @@ export const TableProvider = (props: TableProviderProps) => { renderPreviewHeader, renderPreviewContent, previewTrigger, + previewTooltipText, ], ); diff --git a/packages/design-system/src/components/Table/TableContext/types.ts b/packages/design-system/src/components/Table/TableContext/types.ts index 48dedc85..cc1423b3 100644 --- a/packages/design-system/src/components/Table/TableContext/types.ts +++ b/packages/design-system/src/components/Table/TableContext/types.ts @@ -59,6 +59,7 @@ export interface TableContextValue { renderPreviewHeader?: (row: Row) => ReactNode; renderPreviewContent?: (row: Row) => ReactNode; previewTrigger: 'master' | 'button'; + previewTooltipText: string; } export interface TableProviderProps extends Omit, 'children' | 'aria-label'> { diff --git a/packages/design-system/src/components/Table/hooks/usePreviewCell.ts b/packages/design-system/src/components/Table/hooks/usePreviewCell.ts index f272bcd7..79a838db 100644 --- a/packages/design-system/src/components/Table/hooks/usePreviewCell.ts +++ b/packages/design-system/src/components/Table/hooks/usePreviewCell.ts @@ -6,8 +6,14 @@ import { useTableContext } from '../TableContext'; * Returns flags and a click handler based on `previewTrigger` mode. */ export const usePreviewCell = (columnId: string, rowId: string) => { - const { masterColumnId, previewRowId, setPreviewRowId, renderPreviewContent, previewTrigger } = - useTableContext(); + const { + masterColumnId, + previewRowId, + setPreviewRowId, + renderPreviewContent, + previewTrigger, + previewTooltipText, + } = useTableContext(); const isMasterColumn = columnId === masterColumnId; const hasPreview = isMasterColumn && !!renderPreviewContent; @@ -28,5 +34,7 @@ export const usePreviewCell = (columnId: string, rowId: string) => { isActive, /** Toggle preview for this row (open/close) */ togglePreview, + /** Tooltip text for master cell hover */ + tooltipText: hasPreview ? previewTooltipText : undefined, }; }; diff --git a/packages/design-system/src/components/Table/index.ts b/packages/design-system/src/components/Table/index.ts index 0bb64758..563a1038 100644 --- a/packages/design-system/src/components/Table/index.ts +++ b/packages/design-system/src/components/Table/index.ts @@ -17,6 +17,7 @@ export type { TableExpandedState, TableGroupingState, TableOnChangeFn, + TablePreview, TableProps, TableRow, TableRowSelectionState, diff --git a/packages/design-system/src/components/Table/types.ts b/packages/design-system/src/components/Table/types.ts index 5db467aa..cf6aa2ae 100644 --- a/packages/design-system/src/components/Table/types.ts +++ b/packages/design-system/src/components/Table/types.ts @@ -216,16 +216,24 @@ export interface TableProps extends TestableProps { onEndReachedThreshold?: number; // --- Preview drawer --- - /** Render preview drawer header for a row. Rendered inside DrawerHeader. */ - renderPreviewHeader?: (row: TableRow) => ReactNode; - /** Render preview drawer content for a row. */ - renderPreviewContent?: (row: TableRow) => ReactNode; + /** Preview drawer configuration */ + preview?: TablePreview; +} + +/** Preview drawer configuration */ +export interface TablePreview { + /** Render drawer header for a row */ + renderHeader?: (row: TableRow) => ReactNode; + /** Render drawer content for a row */ + renderContent: (row: TableRow) => ReactNode; /** How the preview drawer is triggered: * - `'master'` — clicking the master cell toggles the drawer (default) * - `'button'` — a toggle button appears in master cell actions on hover */ - previewTrigger?: 'master' | 'button'; + trigger?: 'master' | 'button'; + /** Tooltip text on master cell hover (default: 'Open preview') */ + tooltipText?: string; /** Controlled preview row ID. Pass `null` to close. */ - previewRowId?: string | null; + rowId?: string | null; /** Callback when preview row changes (open/close/swap). */ - onPreviewRowChange?: (rowId: string | null) => void; + onRowChange?: (rowId: string | null) => void; } From ffc69377088d711c474e1d984a9552edf7a8e820 Mon Sep 17 00:00:00 2001 From: Oksana Klimova Date: Fri, 3 Apr 2026 13:54:25 +0200 Subject: [PATCH 5/6] refactor(table): group internal preview context into single object Replace flat previewRowId, setPreviewRowId, renderPreviewHeader, renderPreviewContent, previewTrigger, previewTooltipText fields in TableContextValue with a single preview object. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Table/TableContext/TableProvider.tsx | 15 +++++++----- .../components/Table/TableContext/types.ts | 14 ++++++----- .../components/Table/TablePreviewDrawer.tsx | 19 ++++++++------- .../src/components/Table/TableRow.tsx | 4 ++-- .../components/Table/hooks/usePreviewCell.ts | 23 +++++++------------ 5 files changed, 36 insertions(+), 39 deletions(-) diff --git a/packages/design-system/src/components/Table/TableContext/TableProvider.tsx b/packages/design-system/src/components/Table/TableContext/TableProvider.tsx index a2e6de9a..75dcfef3 100644 --- a/packages/design-system/src/components/Table/TableContext/TableProvider.tsx +++ b/packages/design-system/src/components/Table/TableContext/TableProvider.tsx @@ -333,12 +333,14 @@ export const TableProvider = (props: TableProviderProps) => { containerRef, onEndReached, onEndReachedThreshold, - previewRowId, - setPreviewRowId, - renderPreviewHeader: renderPreviewHeader as ((row: Row) => ReactNode) | undefined, - renderPreviewContent: renderPreviewContent as ((row: Row) => ReactNode) | undefined, - previewTrigger, - previewTooltipText, + preview: { + rowId: previewRowId, + setRowId: setPreviewRowId, + renderHeader: renderPreviewHeader as ((row: Row) => ReactNode) | undefined, + renderContent: renderPreviewContent as ((row: Row) => ReactNode) | undefined, + trigger: previewTrigger, + tooltipText: previewTooltipText, + }, }), [ table, @@ -365,6 +367,7 @@ export const TableProvider = (props: TableProviderProps) => { onEndReached, onEndReachedThreshold, previewRowId, + setPreviewRowId, renderPreviewHeader, renderPreviewContent, previewTrigger, diff --git a/packages/design-system/src/components/Table/TableContext/types.ts b/packages/design-system/src/components/Table/TableContext/types.ts index cc1423b3..32927448 100644 --- a/packages/design-system/src/components/Table/TableContext/types.ts +++ b/packages/design-system/src/components/Table/TableContext/types.ts @@ -54,12 +54,14 @@ export interface TableContextValue { onEndReachedThreshold?: number; // Preview drawer - previewRowId: string | null; - setPreviewRowId: (id: string | null) => void; - renderPreviewHeader?: (row: Row) => ReactNode; - renderPreviewContent?: (row: Row) => ReactNode; - previewTrigger: 'master' | 'button'; - previewTooltipText: string; + preview: { + rowId: string | null; + setRowId: (id: string | null) => void; + renderHeader?: (row: Row) => ReactNode; + renderContent?: (row: Row) => ReactNode; + trigger: 'master' | 'button'; + tooltipText: string; + }; } export interface TableProviderProps extends Omit, 'children' | 'aria-label'> { diff --git a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx index 851f788f..093a4a65 100644 --- a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx +++ b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx @@ -4,28 +4,27 @@ import { Drawer, DrawerBody, DrawerContent, DrawerHeader } from '../Drawer'; import { useTableContext } from './TableContext'; export const TablePreviewDrawer: FC = () => { - const { table, previewRowId, setPreviewRowId, renderPreviewHeader, renderPreviewContent } = - useTableContext(); + const { table, preview: previewCtx } = useTableContext(); - const row = previewRowId ? table.getRowModel().rowsById[previewRowId] : undefined; - const header = row && renderPreviewHeader ? renderPreviewHeader(row) : undefined; - const preview = row && renderPreviewContent ? renderPreviewContent(row) : undefined; + const row = previewCtx.rowId ? table.getRowModel().rowsById[previewCtx.rowId] : undefined; + const header = row && previewCtx.renderHeader ? previewCtx.renderHeader(row) : undefined; + const content = row && previewCtx.renderContent ? previewCtx.renderContent(row) : undefined; // Keep the last valid preview so drawer content doesn't flash empty during close animation const lastHeaderRef = useRef(null); const lastPreviewRef = useRef(null); if (header) lastHeaderRef.current = header; - if (preview) lastPreviewRef.current = preview; + if (content) lastPreviewRef.current = content; const displayHeader = header ?? lastHeaderRef.current; - const displayPreview = preview ?? lastPreviewRef.current; + const displayContent = content ?? lastPreviewRef.current; - if (!renderPreviewContent) return null; + if (!previewCtx.renderContent) return null; return ( { - if (!open) setPreviewRowId(null); + if (!open) previewCtx.setRowId(null); }} modal={false} overlay={false} @@ -38,7 +37,7 @@ export const TablePreviewDrawer: FC = () => { )} - {displayPreview} + {displayContent} ); diff --git a/packages/design-system/src/components/Table/TableRow.tsx b/packages/design-system/src/components/Table/TableRow.tsx index fca44651..b0d3bf3d 100644 --- a/packages/design-system/src/components/Table/TableRow.tsx +++ b/packages/design-system/src/components/Table/TableRow.tsx @@ -17,11 +17,11 @@ interface TableRowProps { } const TableRowInner = ({ row, ref, 'data-index': dataIndex }: TableRowProps) => { - const { expandingEnabled, previewRowId } = useTableContext(); + const { expandingEnabled, preview } = useTableContext(); const testId = useTestId('row'); const isGroupParent = row.subRows.length > 0; const isSelected = isGroupParent ? row.getIsAllSubRowsSelected() : row.getIsSelected(); - const isPreviewActive = previewRowId === row.id; + const isPreviewActive = preview.rowId === row.id; if (isGroupParent) { const cells = row.getVisibleCells(); diff --git a/packages/design-system/src/components/Table/hooks/usePreviewCell.ts b/packages/design-system/src/components/Table/hooks/usePreviewCell.ts index 79a838db..42452568 100644 --- a/packages/design-system/src/components/Table/hooks/usePreviewCell.ts +++ b/packages/design-system/src/components/Table/hooks/usePreviewCell.ts @@ -6,24 +6,17 @@ import { useTableContext } from '../TableContext'; * Returns flags and a click handler based on `previewTrigger` mode. */ export const usePreviewCell = (columnId: string, rowId: string) => { - const { - masterColumnId, - previewRowId, - setPreviewRowId, - renderPreviewContent, - previewTrigger, - previewTooltipText, - } = useTableContext(); + const { masterColumnId, preview } = useTableContext(); const isMasterColumn = columnId === masterColumnId; - const hasPreview = isMasterColumn && !!renderPreviewContent; - const isMasterTrigger = hasPreview && previewTrigger === 'master'; - const isButtonTrigger = hasPreview && previewTrigger === 'button'; - const isActive = previewRowId === rowId; + const hasPreview = isMasterColumn && !!preview.renderContent; + const isMasterTrigger = hasPreview && preview.trigger === 'master'; + const isButtonTrigger = hasPreview && preview.trigger === 'button'; + const isActive = preview.rowId === rowId; const togglePreview = useCallback(() => { - setPreviewRowId(isActive ? null : rowId); - }, [setPreviewRowId, isActive, rowId]); + preview.setRowId(isActive ? null : rowId); + }, [preview.setRowId, isActive, rowId]); return { /** Preview opens by clicking the master cell */ @@ -35,6 +28,6 @@ export const usePreviewCell = (columnId: string, rowId: string) => { /** Toggle preview for this row (open/close) */ togglePreview, /** Tooltip text for master cell hover */ - tooltipText: hasPreview ? previewTooltipText : undefined, + tooltipText: hasPreview ? preview.tooltipText : undefined, }; }; From ae34b67b40805ba9c4d5b9256c43d19389a4146c Mon Sep 17 00:00:00 2001 From: Oksana Klimova Date: Fri, 3 Apr 2026 13:55:24 +0200 Subject: [PATCH 6/6] fix(table): rename leftover ref, add cursor-pointer for both triggers - Rename lastPreviewRef to lastContentRef for consistency - Show cursor-pointer on master cell when tooltipText is set (both triggers) - Update JSDoc in usePreviewCell Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/Table/TableBody/TableBodyCell.tsx | 2 +- .../src/components/Table/TablePreviewDrawer.tsx | 6 +++--- .../src/components/Table/hooks/usePreviewCell.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx b/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx index 846e0590..56d74ba2 100644 --- a/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx +++ b/packages/design-system/src/components/Table/TableBody/TableBodyCell.tsx @@ -90,7 +90,7 @@ export const TableBodyCell = ({ getAlignClass(meta), getExpandBorderClass(isExpandColumn, cell.row.depth), isCut && 'pr-0', - isMasterTrigger && 'cursor-pointer', + (isMasterTrigger || tooltipText) && 'cursor-pointer', meta?.cellClassName, className, )} diff --git a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx index 093a4a65..0eec4fe1 100644 --- a/packages/design-system/src/components/Table/TablePreviewDrawer.tsx +++ b/packages/design-system/src/components/Table/TablePreviewDrawer.tsx @@ -12,11 +12,11 @@ export const TablePreviewDrawer: FC = () => { // Keep the last valid preview so drawer content doesn't flash empty during close animation const lastHeaderRef = useRef(null); - const lastPreviewRef = useRef(null); + const lastContentRef = useRef(null); if (header) lastHeaderRef.current = header; - if (content) lastPreviewRef.current = content; + if (content) lastContentRef.current = content; const displayHeader = header ?? lastHeaderRef.current; - const displayContent = content ?? lastPreviewRef.current; + const displayContent = content ?? lastContentRef.current; if (!previewCtx.renderContent) return null; diff --git a/packages/design-system/src/components/Table/hooks/usePreviewCell.ts b/packages/design-system/src/components/Table/hooks/usePreviewCell.ts index 42452568..8aaa6b5a 100644 --- a/packages/design-system/src/components/Table/hooks/usePreviewCell.ts +++ b/packages/design-system/src/components/Table/hooks/usePreviewCell.ts @@ -3,7 +3,7 @@ import { useTableContext } from '../TableContext'; /** * Encapsulates preview drawer logic for a body cell. - * Returns flags and a click handler based on `previewTrigger` mode. + * Returns flags and a click handler based on `preview.trigger` mode. */ export const usePreviewCell = (columnId: string, rowId: string) => { const { masterColumnId, preview } = useTableContext();