diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickySelectionExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickySelectionExample.tsx new file mode 100644 index 00000000..01fce174 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickySelectionExample.tsx @@ -0,0 +1,96 @@ +import { FunctionComponent } from 'react'; +import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; +import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import { Button } from '@patternfly/react-core'; +import { ActionsColumn } from '@patternfly/react-table'; + +interface Repository { + id: number; + name: string; + branches: string | null; + prs: string | null; + workspaces: string; + lastCommit: string; + contributors: string; + stars: string; + forks: string; +} + +const repositories: Repository[] = [ + { id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one', contributors: '25 contributors', stars: '1.2k stars', forks: '340 forks' }, + { id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two', contributors: '45 contributors', stars: '3.5k stars', forks: '890 forks' }, + { id: 3, name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three', contributors: '200 contributors', stars: '15k stars', forks: '2.1k forks' }, + { id: 4, name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four', contributors: '80 contributors', stars: '5.7k stars', forks: '1.2k forks' }, + { id: 5, name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five', contributors: '60 contributors', stars: '4.3k stars', forks: '780 forks' }, + { id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six', contributors: '300 contributors', stars: '22k stars', forks: '4.5k forks' }, + { id: 7, name: 'Repository seven', branches: 'Branch seven', prs: 'Pull request seven', workspaces: 'Workspace seven', lastCommit: 'Timestamp seven', contributors: '12 contributors', stars: '567 stars', forks: '120 forks' }, + { id: 8, name: 'Repository eight', branches: 'Branch eight', prs: 'Pull request eight', workspaces: 'Workspace eight', lastCommit: 'Timestamp eight', contributors: '98 contributors', stars: '7.8k stars', forks: '1.5k forks' }, + { id: 9, name: 'Repository nine', branches: 'Branch nine', prs: 'Pull request nine', workspaces: 'Workspace nine', lastCommit: 'Timestamp nine', contributors: '33 contributors', stars: '2.1k stars', forks: '456 forks' }, + { id: 10, name: 'Repository ten', branches: 'Branch ten', prs: 'Pull request ten', workspaces: 'Workspace ten', lastCommit: 'Timestamp ten', contributors: '150 contributors', stars: '11k stars', forks: '2.8k forks' }, + { id: 11, name: 'Repository eleven', branches: 'Branch eleven', prs: 'Pull request eleven', workspaces: 'Workspace eleven', lastCommit: 'Timestamp eleven', contributors: '67 contributors', stars: '5.2k stars', forks: '980 forks' }, + { id: 12, name: 'Repository twelve', branches: 'Branch twelve', prs: 'Pull request twelve', workspaces: 'Workspace twelve', lastCommit: 'Timestamp twelve', contributors: '41 contributors', stars: '3.1k stars', forks: '670 forks' } +]; + +const rowActions = [ + { + title: 'Some action', + onClick: () => console.log('clicked on Some action') // eslint-disable-line no-console + }, + { + title:
Another action
, + onClick: () => console.log('clicked on Another action') // eslint-disable-line no-console + }, + { + isSeparator: true + }, + { + title: 'Third action', + onClick: () => console.log('clicked on Third action') // eslint-disable-line no-console + } +]; + +const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspaces, lastCommit, contributors, stars, forks }) => [ + { id, cell: , props: { isStickyColumn: true, hasRightBorder: true, modifier: "nowrap" } }, + { cell: branches, props: { modifier: "nowrap" } }, + { cell: prs, props: { modifier: "nowrap" } }, + { cell: workspaces, props: { modifier: "nowrap" } }, + { cell: lastCommit, props: { modifier: "nowrap" } }, + { cell: contributors, props: { modifier: "nowrap" } }, + { cell: stars, props: { modifier: "nowrap" } }, + { cell: forks, props: { modifier: "nowrap" } }, + { cell: , props: { isActionCell: true } }, +]); + +const columns: DataViewTh[] = [ + { cell: 'Repositories', props: { isStickyColumn: true, modifier: 'fitContent', hasRightBorder: true } }, + { cell: <>Branches, props: { width: 20 } }, + { cell: 'Pull requests', props: { width: 20 } }, + { cell: 'Workspaces', props: { info: { tooltip: 'More information' }, width: 20 } }, + { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 }, width: 20 } }, + { cell: 'Contributors', props: { width: 20 } }, + { cell: 'Stars', props: { width: 20 } }, + { cell: 'Forks', props: { width: 20 } }, + null, // Actions column header +]; + +const ouiaId = 'TableStickySelectionExample'; + +export const StickySelectionExample: FunctionComponent = () => { + const selection = useDataViewSelection({ matchOption: (a, b) => a[0].id === b[0].id }); + + return ( + +
+ +
+
+ ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md index 2aa382ad..2c3320e5 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md @@ -104,6 +104,7 @@ When sticky headers and columns are enabled: - Columns marked with `isStickyColumn: true` remain visible when scrolling horizontally - The table is wrapped in `OuterScrollContainer` and `InnerScrollContainer` components to enable sticky behavior - Sticky columns can have additional styling like borders using `hasRightBorder` or `hasLeftBorder` props +- When selection is enabled (via ``), the selection checkbox column automatically becomes sticky when the first data column has `isStickyColumn: true`. The selection column is properly offset to appear to the left of the sticky data column. ### Sticky header and columns example @@ -111,6 +112,14 @@ When sticky headers and columns are enabled: ``` +### Sticky selection column example + +This example demonstrates how the selection checkbox column automatically becomes sticky when the first data column has `isStickyColumn: true`. The selection column is positioned at the left edge with proper offset handling. + +```js file="./DataViewTableStickySelectionExample.tsx" + +``` + ### Interactive example - Interactive example show how the different composable options work together. - By toggling the toggles you can switch between them and observe the behaviour diff --git a/packages/module/patternfly-docs/generated/index.js b/packages/module/patternfly-docs/generated/index.js index 610e983e..b9d46728 100644 --- a/packages/module/patternfly-docs/generated/index.js +++ b/packages/module/patternfly-docs/generated/index.js @@ -14,8 +14,8 @@ module.exports = { '/extensions/data-view/table/react': { id: "Table", title: "Data view table", - toc: [{"text":"Configuring rows and columns"},[{"text":"Table example"}],{"text":"Expandable rows"},[{"text":"Expandable rows example"}],{"text":"Sticky header and columns"},[{"text":"Sticky header and columns example"},{"text":"Interactive example"},{"text":"Resizable columns"}],{"text":"Tree table"},[{"text":"Tree table example"}],{"text":"Sorting"},[{"text":"Sorting example"},{"text":"Sorting state"}],{"text":"States"},[{"text":"Empty"},{"text":"Error"},{"text":"Loading"}]], - examples: ["Table example","Expandable rows example","Sticky header and columns example","Interactive example","Resizable columns","Tree table example","Sorting example","Empty","Error","Loading"], + toc: [{"text":"Configuring rows and columns"},[{"text":"Table example"}],{"text":"Expandable rows"},[{"text":"Expandable rows example"}],{"text":"Sticky header and columns"},[{"text":"Sticky header and columns example"},{"text":"Sticky selection column example"},{"text":"Interactive example"},{"text":"Resizable columns"}],{"text":"Tree table"},[{"text":"Tree table example"}],{"text":"Sorting"},[{"text":"Sorting example"},{"text":"Sorting state"}],{"text":"States"},[{"text":"Empty"},{"text":"Error"},{"text":"Loading"}]], + examples: ["Table example","Expandable rows example","Sticky header and columns example","Sticky selection column example","Interactive example","Resizable columns","Tree table example","Sorting example","Empty","Error","Loading"], section: "extensions", subsection: "Data view", source: "react", diff --git a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx index 7d3fe3ac..4fa257b9 100644 --- a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx +++ b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx @@ -82,11 +82,18 @@ export const DataViewTableBasic: FC = ({ (content) => content.rowId === rowId ) : []; + // Check if the first cell in this row has isStickyColumn + const firstCellProps = rowData[0] && isDataViewTdObject(rowData[0]) ? (rowData[0]?.props ?? {}) : {}; + const firstCellIsSticky = firstCellProps.isStickyColumn; + const rowContent = ( {isSelectable && ( { @@ -102,10 +109,18 @@ export const DataViewTableBasic: FC = ({ const cellExpandableContent = isExpandable ? expandedRows?.find( (content) => content.rowId === rowId && content.columnId === colIndex ) : undefined; + + // Get the cell props + const cellProps = cellIsObject ? (cell?.props ?? {}) : {}; + // If the first column is sticky and selection is enabled, offset it by the selection column width + const enhancedCellProps = colIndex === 0 && cellProps.isStickyColumn && isSelectable + ? { ...cellProps, stickyLeftOffset: '45px' } + : cellProps; + return ( = ({ - { activeHeadState || } + { activeHeadState || } { bodyContent }
@@ -167,7 +182,7 @@ export const DataViewTableBasic: FC = ({ } else { return ( - { activeHeadState || } + { activeHeadState || } { bodyContent }
); diff --git a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx index 81fc1e19..78bacf5d 100644 --- a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx +++ b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx @@ -14,6 +14,8 @@ export interface DataViewTableHeadProps extends TheadProps { ouiaId?: string; /** @hide Indicates whether table is resizable */ hasResizableColumns?: boolean; + /** Toggles sticky columns and header */ + isSticky?: boolean; } export const DataViewTableHead: FC = ({ @@ -21,28 +23,49 @@ export const DataViewTableHead: FC = ({ columns, ouiaId = 'DataViewTableHead', hasResizableColumns, + isSticky = false, ...props }: DataViewTableHeadProps) => { const { selection } = useInternalContext(); const { onSelect, isSelected } = selection ?? {}; const cells = useMemo( - () => [ - onSelect && isSelected && !isTreeTable ? ( - - ) : null, - ...columns.map((column, index) => ( - - )) - ], - [ columns, ouiaId, onSelect, isSelected, isTreeTable, hasResizableColumns ] + () => { + // Check if the first column has isStickyColumn + const firstColumnProps = isDataViewThObject(columns[0]) ? (columns[0]?.props ?? {}) : {}; + const firstColumnIsSticky = firstColumnProps.isStickyColumn; + + return [ + onSelect && isSelected && !isTreeTable ? ( + + ) : null, + ...columns.map((column, index) => { + const thProps = isDataViewThObject(column) ? (column?.props ?? {}) : {}; + // If the first column is sticky and selection is enabled, offset it by the selection column width + const enhancedThProps = index === 0 && thProps.isStickyColumn && onSelect && isSelected + ? { ...thProps, stickyLeftOffset: '45px' } + : thProps; + + return ( + + ); + }) + ]; + }, + [ columns, ouiaId, onSelect, isSelected, isTreeTable, hasResizableColumns, isSticky ] ); return (