diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx new file mode 100644 index 00000000..dc4c061b --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx @@ -0,0 +1,108 @@ +import { FunctionComponent } from 'react'; +import { DataViewTable, DataViewTr, DataViewTh, ExpandableContent } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +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; +} + +const expandableContents: ExpandableContent[] = [ + // Row 1 - Repository one + { rowId: 1, columnId: 3, content:
PR Details: 3 open PRs, 45 merged this month, avg review time: 2 days
}, + { rowId: 1, columnId: 5, content:
Commit Info: Author: John Doe, Message: "Fix critical authentication bug", SHA: a1b2c3d
}, + + // Row 2 - Repository two + { rowId: 2, columnId: 2, content:
Branch Details: 8 active branches, main, staging, feature/api-v2, feature/dashboard
}, + { rowId: 2, columnId: 3, content:
PR Details: 5 open PRs, 120 merged this month, avg review time: 1.5 days
}, + { rowId: 2, columnId: 4, content:
Workspace Info: Development env, 3 active deployments, last updated 30 mins ago
}, + { rowId: 2, columnId: 5, content:
Commit Info: Author: Jane Smith, Message: "Add new API endpoints", SHA: x9y8z7w
}, + + // Row 3 - Repository three + { rowId: 3, columnId: 2, content:
Branch Details: 12 active branches including main, develop, multiple feature branches
}, + { rowId: 3, columnId: 3, content:
PR Details: 8 open PRs, 200 merged this month, avg review time: 3 days
}, + { rowId: 3, columnId: 4, content:
Workspace Info: Staging env, 10 active deployments, last updated 1 day ago
}, + { rowId: 3, columnId: 5, content:
Commit Info: Author: Bob Johnson, Message: "Refactor core modules", SHA: p0o9i8u
}, + + // Row 4 - Repository four + { rowId: 4, columnId: 2, content:
Branch Details: 6 active branches, focusing on microservices architecture
}, + { rowId: 4, columnId: 3, content:
PR Details: 2 open PRs, 90 merged this month, avg review time: 2.5 days
}, + { rowId: 4, columnId: 4, content:
Workspace Info: QA env, 7 active deployments, automated testing enabled
}, + { rowId: 4, columnId: 5, content:
Commit Info: Author: Alice Williams, Message: "Update dependencies", SHA: m5n4b3v
}, + + // Row 5 - Repository five + { rowId: 5, columnId: 2, content:
Branch Details: 4 active branches, clean branch strategy
}, + { rowId: 5, columnId: 3, content:
PR Details: 6 open PRs, 75 merged this month, avg review time: 1 day
}, + { rowId: 5, columnId: 4, content:
Workspace Info: Pre-production env, CI/CD pipeline configured
}, + { rowId: 5, columnId: 5, content:
Commit Info: Author: Charlie Brown, Message: "Implement dark mode", SHA: q2w3e4r
}, + + // Row 6 - Repository six + { rowId: 6, columnId: 2, content:
Branch Details: 15 active branches, complex branching model
}, + { rowId: 6, columnId: 3, content:
PR Details: 10 open PRs, 250 merged this month, avg review time: 4 days
}, + { rowId: 6, columnId: 4, content:
Workspace Info: Multi-region deployment, high availability setup
}, + { rowId: 6, columnId: 5, content:
Commit Info: Author: David Lee, Message: "Security patches applied", SHA: t6y7u8i
}, +]; + +const repositories: Repository[] = [ + { id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' }, + { id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' }, + { id: 3, name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' }, + { id: 4, name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' }, + { id: 5, name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' }, + { id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' } +]; + +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 }) => [ + { + id, + cell: workspaces, + props: { + favorites: { isFavorited: true } + } + }, + { cell: }, + branches, + prs, + workspaces, + lastCommit, + { cell: , props: { isActionCell: true } }, +]); + +const columns: DataViewTh[] = [ + null, + 'Repositories', + { cell: <>Branches }, + 'Pull requests', + { cell: 'Workspaces', props: { info: { tooltip: 'More information' }, isStickyColumn: true } }, + { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } }, +]; + +const ouiaId = 'TableExample'; + +export const ExpandableExample: FunctionComponent = () => ( + +); diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx new file mode 100644 index 00000000..4972770d --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx @@ -0,0 +1,148 @@ +import { FunctionComponent, useState } from 'react'; +import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { ExpandableContent } from '@patternfly/react-data-view/dist/dynamic/DataViewTableBasic'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import { Button, Toolbar, ToolbarContent, ToolbarItem, Switch } from '@patternfly/react-core'; + +interface Repository { + id: number; + name: string; + branches: string | null; + prs: string | null; + workspaces: string; + lastCommit: string; + contributors: string; + stars: string; + forks: string; +} + +const expandableContents: ExpandableContent[] = [ + // Row 1 - Repository one + { rowId: 1, columnId: 2, content:
Branch Details: 5 active branches, main, develop, feature/new-ui, hotfix/bug-123, release/v2.0
}, + { rowId: 1, columnId: 3, content:
PR Details: 3 open PRs, 45 merged this month, avg review time: 2 days
}, + { rowId: 1, columnId: 5, content:
Commit Info: Author: John Doe, Message: "Fix critical authentication bug", SHA: a1b2c3d
}, + + // Row 2 - Repository two + { rowId: 2, columnId: 2, content:
Branch Details: 8 active branches, main, staging, feature/api-v2, feature/dashboard
}, + { rowId: 2, columnId: 3, content:
PR Details: 5 open PRs, 120 merged this month, avg review time: 1.5 days
}, + { rowId: 2, columnId: 4, content:
Workspace Info: Development env, 3 active deployments, last updated 30 mins ago
}, + { rowId: 2, columnId: 5, content:
Commit Info: Author: Jane Smith, Message: "Add new API endpoints", SHA: x9y8z7w
}, + + // Row 3 - Repository three + { rowId: 3, columnId: 2, content:
Branch Details: 12 active branches including main, develop, multiple feature branches
}, + { rowId: 3, columnId: 3, content:
PR Details: 8 open PRs, 200 merged this month, avg review time: 3 days
}, + { rowId: 3, columnId: 4, content:
Workspace Info: Staging env, 10 active deployments, last updated 1 day ago
}, + { rowId: 3, columnId: 5, content:
Commit Info: Author: Bob Johnson, Message: "Refactor core modules", SHA: p0o9i8u
}, + + // Row 4 - Repository four + { rowId: 4, columnId: 2, content:
Branch Details: 6 active branches, focusing on microservices architecture
}, + { rowId: 4, columnId: 3, content:
PR Details: 2 open PRs, 90 merged this month, avg review time: 2.5 days
}, + { rowId: 4, columnId: 4, content:
Workspace Info: QA env, 7 active deployments, automated testing enabled
}, + { rowId: 4, columnId: 5, content:
Commit Info: Author: Alice Williams, Message: "Update dependencies", SHA: m5n4b3v
}, + + // Row 5 - Repository five + { rowId: 5, columnId: 2, content:
Branch Details: 4 active branches, clean branch strategy
}, + { rowId: 5, columnId: 3, content:
PR Details: 6 open PRs, 75 merged this month, avg review time: 1 day
}, + { rowId: 5, columnId: 4, content:
Workspace Info: Pre-production env, CI/CD pipeline configured
}, + { rowId: 5, columnId: 5, content:
Commit Info: Author: Charlie Brown, Message: "Implement dark mode", SHA: q2w3e4r
}, + + // Row 6 - Repository six + { rowId: 6, columnId: 2, content:
Branch Details: 15 active branches, complex branching model
}, + { rowId: 6, columnId: 3, content:
PR Details: 10 open PRs, 250 merged this month, avg review time: 4 days
}, + { rowId: 6, columnId: 4, content:
Workspace Info: Multi-region deployment, high availability setup
}, + { rowId: 6, columnId: 5, content:
Commit Info: Author: David Lee, Message: "Security patches applied", SHA: t6y7u8i
}, +]; + +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' }, + { id: 13, name: 'Repository thirteen', branches: 'Branch thirteen', prs: 'Pull request thirteen', workspaces: 'Workspace thirteen', lastCommit: 'Timestamp thirteen', contributors: '89 contributors', stars: '6.4k stars', forks: '1.3k forks' }, + { id: 14, name: 'Repository fourteen', branches: 'Branch fourteen', prs: 'Pull request fourteen', workspaces: 'Workspace fourteen', lastCommit: 'Timestamp fourteen', contributors: '120 contributors', stars: '9.2k stars', forks: '1.9k forks' }, + { id: 15, name: 'Repository fifteen', branches: 'Branch fifteen', prs: 'Pull request fifteen', workspaces: 'Workspace fifteen', lastCommit: 'Timestamp fifteen', contributors: '78 contributors', stars: '5.9k stars', forks: '1.1k forks' } +]; + +const ouiaId = 'TableInteractiveExample'; + +export const InteractiveExample: FunctionComponent = () => { + const [isExpandable, setIsExpandable] = useState(true); + const [isSticky, setIsSticky] = useState(true); + + // Generate rows based on current settings + const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspaces, lastCommit, contributors, stars, forks }) => [ + { + id, + cell: workspaces, + props: { + favorites: { isFavorited: true } + } + }, + { cell: , props: { isStickyColumn: isSticky, hasRightBorder: true, hasLeftBorder: 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" } } + ]); + + const columns: DataViewTh[] = [ + null, + { cell: 'Repositories', props: { isStickyColumn: isSticky, modifier: 'fitContent', hasRightBorder: true, hasLeftBorder: 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 } }, + ]; + + return ( + <> + + + + setIsExpandable(checked)} + aria-label="Toggle expandable rows" + /> + + + setIsSticky(checked)} + aria-label="Toggle sticky header and columns" + /> + + + +
+ +
+ + ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickyExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickyExample.tsx new file mode 100644 index 00000000..ce6fa304 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickyExample.tsx @@ -0,0 +1,90 @@ +import { FunctionComponent } from 'react'; +import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +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: workspaces, props: { favorites: { isFavorited: true } } }, + { cell: , props: { isStickyColumn: true, hasRightBorder: true, hasLeftBorder: 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[] = [ + null, + { cell: 'Repositories', props: { isStickyColumn: true, modifier: 'fitContent', hasRightBorder: true, hasLeftBorder: 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 = 'TableStickyExample'; + +export const StickyExample: FunctionComponent = () => ( +
+ +
+); 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 23a63a83..2aa382ad 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 @@ -58,6 +58,67 @@ If you want to have all expandable nodes open on initial load pass the `expandAl ``` +## Expandable rows + +To add expandable content to table cells, pass an array of `ExpandableContent` objects to the `expandedRows` prop of the `` component. Each expandable content object defines which cell can be expanded and what content to display when expanded. + +The `ExpandableContent` interface is defined as: + +```typescript +interface ExpandableContent { + /** The ID of the row containing the expandable cell (must match the id property in the row data) */ + rowId: number; + /** The column index (0-based) that should be expandable */ + columnId: number; + /** The content to display when the cell is expanded */ + content: ReactNode; +} +``` + +When a cell has expandable content: +- A compound expand toggle button appears in the cell +- Clicking the toggle expands the row to show the additional content below +- Only one expanded cell is shown per row at a time +- Clicking another expandable cell in the same row switches the expanded content + +### Expandable rows example + +```js file="./DataViewTableExpandableExample.tsx" + +``` + +## Sticky header and columns + +To enable sticky headers and columns, set the `isSticky` prop to `true` on the `` component. This keeps the table header and designated columns visible when scrolling. + +To make specific columns sticky, add the `isStickyColumn` property to the column's `props` in the column definition: + +```typescript +const columns: DataViewTh[] = [ + { cell: 'Column Name', props: { isStickyColumn: true } } +]; +``` + +When sticky headers and columns are enabled: +- The table header remains visible when scrolling vertically +- 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 + +### Sticky header and columns example + +```js file="./DataViewTableStickyExample.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 + +```js file="./DataViewTableInteractiveExample.tsx" + +``` + ### Resizable columns To allow a column to resize, add `isResizable` to the `DataViewTable` element, and pass `resizableProps` to each applicable header cell. The `resizableProps` object consists of the following fields: diff --git a/packages/module/patternfly-docs/generated/index.js b/packages/module/patternfly-docs/generated/index.js index be42c242..b6598b52 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":"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","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":"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"], section: "extensions", subsection: "Data view", source: "react", @@ -26,7 +26,7 @@ module.exports = { '/extensions/data-view/overview/extensions': { id: "Overview", title: "Data view overview", - toc: [[{"text":"Layout"},{"text":"Modularity"}],{"text":"Events context"},[{"text":"Row click subscription example"}]], + toc: [{"text":"How to structure and implement the data view"},[{"text":"Layout"},{"text":"Modularity"}],{"text":"Events context"},[{"text":"Row click subscription example"}]], examples: ["Layout","Modularity","Row click subscription example"], section: "extensions", subsection: "Data view", diff --git a/packages/module/src/DataViewTable/DataViewTable.tsx b/packages/module/src/DataViewTable/DataViewTable.tsx index fc96fe17..123c4df5 100644 --- a/packages/module/src/DataViewTable/DataViewTable.tsx +++ b/packages/module/src/DataViewTable/DataViewTable.tsx @@ -1,9 +1,11 @@ import { FC, ReactNode } from 'react'; import { TdProps, ThProps, TrProps, InnerScrollContainer } from '@patternfly/react-table'; import { DataViewTableTree, DataViewTableTreeProps } from '../DataViewTableTree'; -import { DataViewTableBasic, DataViewTableBasicProps } from '../DataViewTableBasic'; +import { DataViewTableBasic, DataViewTableBasicProps, ExpandableContent } from '../DataViewTableBasic'; import { DataViewThResizableProps } from '../DataViewTh/DataViewTh'; +export type { ExpandableContent }; + // Table head typings export type DataViewTh = | ReactNode diff --git a/packages/module/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap b/packages/module/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap index 074cb341..662ccb99 100644 --- a/packages/module/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap +++ b/packages/module/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap @@ -4,7 +4,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
({ - row: Object.values(repo), -})); +const rows = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [ + { id, cell: name }, + branches, + prs, + workspaces, + lastCommit +]); const columns = [ 'Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit' ]; +const expandableContents: ExpandableContent[] = [ + { rowId: 1, columnId: 1, content:
Branch details for Repository one
}, +]; + const ouiaId = 'TableExample'; describe('DataViewTable component', () => { @@ -57,8 +67,40 @@ describe('DataViewTable component', () => { const { container } = render( - + ); expect(container).toMatchSnapshot(); }); + + test('when isExpandable cell should be clickable and expandable', async () => { + const user = userEvent.setup(); + + render( + + ); + + // Initially, expandable content is rendered but should be hidden (not visible) + const initialBranchContent = screen.getByText('Branch details for Repository one'); + expect(initialBranchContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeFalsy(); + + // Find the first expandable button by ID + const branchExpandButton = document.getElementById('expandable-0-0-1'); + expect(branchExpandButton).toBeTruthy(); + // Verify the button is in the cell with "Branch one" text + expect(branchExpandButton?.closest('td')?.textContent).toContain('Branch one'); + + // Click the expand button for Branches column + await user.click(branchExpandButton!); + + // After clicking, the expandable content should be visible + const branchContent = screen.getByText('Branch details for Repository one'); + expect(branchContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeTruthy(); + }); }); diff --git a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx index 8208e9e6..7d3fe3ac 100644 --- a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx +++ b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx @@ -1,5 +1,8 @@ -import { FC, useMemo } from 'react'; +import { FC, useMemo, useState, useRef } from 'react'; import { + ExpandableRowContent, + InnerScrollContainer, + OuterScrollContainer, Table, TableProps, Tbody, @@ -11,12 +14,20 @@ import { DataViewTableHead } from '../DataViewTableHead'; import { DataViewTh, DataViewTr, isDataViewTdObject, isDataViewTrObject } from '../DataViewTable'; import { DataViewState } from '../DataView/DataView'; +export interface ExpandableContent { + rowId: number; + columnId: number; + content: React.ReactNode; +} + /** extends TableProps */ export interface DataViewTableBasicProps extends Omit { /** Columns definition */ columns: DataViewTh[]; /** Current page rows */ rows: DataViewTr[]; + /** Expanded rows content */ + expandedRows?: ExpandableContent[]; /** Table head states to be displayed when active */ headStates?: Partial> /** Table body states to be displayed when active */ @@ -25,15 +36,22 @@ export interface DataViewTableBasicProps extends Omit = ({ columns, rows, + expandedRows, ouiaId = 'DataViewTableBasic', headStates, bodyStates, hasResizableColumns, + isExpandable = false, + isSticky = false, ...props }: DataViewTableBasicProps) => { const { selection, activeState, isSelectable } = useInternalContext(); @@ -42,10 +60,30 @@ export const DataViewTableBasic: FC = ({ const activeHeadState = useMemo(() => activeState ? headStates?.[activeState] : undefined, [ activeState, headStates ]); const activeBodyState = useMemo(() => activeState ? bodyStates?.[activeState] : undefined, [ activeState, bodyStates ]); + const [expandedRowsState, setExpandedRowsState] = useState>({}) + const [expandedColumnIndex, setExpandedColumnIndex] = useState>({}) + + const tableRef = useRef(null); + + const needsSeparateTbody = isExpandable; + const renderedRows = useMemo(() => rows.map((row, rowIndex) => { const rowIsObject = isDataViewTrObject(row); - return ( - + const isRowExpanded = expandedRowsState[rowIndex] || false; + const expandedColIndex = expandedColumnIndex[rowIndex]; + + // Get the first cell to extract the row ID + const rowData = rowIsObject ? row.row : row; + const firstCell = rowData[0]; + const rowId = isDataViewTdObject(firstCell) ? (firstCell as { id?: number }).id : undefined; + + // Find all expandable contents for this row + const rowExpandableContents = isExpandable ? expandedRows?.filter( + (content) => content.rowId === rowId + ) : []; + + const rowContent = ( + {isSelectable && ( ); - }), [ rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId ]); - return ( -
= ({ )} {(rowIsObject ? row.row : row).map((cell, colIndex) => { const cellIsObject = isDataViewTdObject(cell); + const cellExpandableContent = isExpandable ? expandedRows?.find( + (content) => content.rowId === rowId && content.columnId === colIndex + ) : undefined; return ( { + setExpandedRowsState(prev => { + const isSameColumn = expandedColIndex === colIndex; + const wasExpanded = prev[rowIndex]; + return { ...prev, [rowIndex]: isSameColumn ? !wasExpanded : true }; + }); + setExpandedColumnIndex(prev => ({ ...prev, [rowIndex]: colIndex })); + }, + rowIndex, + columnIndex: colIndex + } + })} data-ouia-component-id={`${ouiaId}-td-${rowIndex}-${colIndex}`} > {cellIsObject ? cell.cell : cell} @@ -73,14 +130,48 @@ export const DataViewTableBasic: FC = ({ })}
- { activeHeadState || } - { activeBodyState || {renderedRows} } -
- ); + if (needsSeparateTbody) { + return ( + + {rowContent} + {rowExpandableContents?.map((expandableContent) => ( + + + + {expandableContent.content} + + + + ))} + + ); + } else { + return rowContent; + } + }), [ rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId, expandedRowsState, expandedColumnIndex, expandedRows, isExpandable, needsSeparateTbody ]); + + const bodyContent = activeBodyState || (needsSeparateTbody ? renderedRows : {renderedRows}); + + if (isSticky) { + return ( + + + + { activeHeadState || } + { bodyContent } +
+
+
+ ); + } else { + return ( + + { activeHeadState || } + { bodyContent } +
+ ); + } }; export default DataViewTableBasic; diff --git a/packages/module/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap b/packages/module/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap index 9c589c6a..61effb57 100644 --- a/packages/module/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap +++ b/packages/module/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap @@ -4,7 +4,7 @@ exports[`DataViewTable component should render correctly 1`] = `