From ad9946051792a52bd1f5b77d7d72b8a05ae7a806 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Fri, 19 Sep 2025 11:17:30 -0400 Subject: [PATCH 01/29] feat(DataView): add support for resizable columns --- .../DataViewTableResizableColumnsExample.tsx | 117 ++++++++ .../data-view/examples/Table/Table.md | 59 +++- .../src/DataViewTable/DataViewTable.tsx | 93 +++++-- .../DataViewTableHead/DataViewTableHead.tsx | 43 ++- packages/module/src/DataViewTh/DataViewTh.tsx | 261 ++++++++++++++++++ packages/module/src/DataViewTh/pf-resize.svg | 12 + 6 files changed, 521 insertions(+), 64 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx create mode 100644 packages/module/src/DataViewTh/DataViewTh.tsx create mode 100644 packages/module/src/DataViewTh/pf-resize.svg diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx new file mode 100644 index 0000000..7ee55cc --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -0,0 +1,117 @@ +import { FunctionComponent } from 'react'; +import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +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 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 + } +]; + +// you can also pass props to Tr by returning { row: DataViewTd[], props: TrProps } } +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: 'col', + resizableProps: { isResizable: true, onResize: (e, width) => console.log('resized width: ', width) } + }, + 'Pull requests', + { cell: 'Workspaces', props: { info: { tooltip: 'More information' } } }, + { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } } +]; + +const ouiaId = 'TableExample'; + +export const ResizableColumnsExample: 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 b5c771c..a68d6ac 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 @@ -12,9 +12,18 @@ source: react # If you use typescript, the name of the interface to display props for # These are found through the sourceProps function provided in patternfly-docs.source.js sortValue: 3 -propComponents: ['DataViewTableBasic', 'DataViewTableTree', 'DataViewTrTree', 'DataViewTrObject'] +propComponents: + [ + 'DataViewTableBasic', + 'DataViewTableTree', + 'DataViewTrTree', + 'DataViewTrObject', + 'DataViewTh', + 'DataViewThResizableProps' + ] sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md --- + import { FunctionComponent, useMemo } from 'react'; import { BrowserRouter, useSearchParams } from 'react-router-dom'; import { Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter } from '@patternfly/react-core'; @@ -28,7 +37,9 @@ import { DataView, DataViewState } from '@patternfly/react-data-view/dist/dynami The **data view table** component renders your data into columns and rows within a [PatternFly table](/components/table) component. You can easily customize and configure the table with these additional [data view components and props](/extensions/data-view/table#props). ## Configuring rows and columns + To define rows and columns for your table, use these props: + - `columns`: Defines the column heads of the table. Each item in the array can be a `ReactNode` for simple heads, or an object with the following properties: - `cell`: Content to display in the column head. - `props` (optional): (`ThProps`) to pass to the `` component, such as `width`, `sort`, and other table head cell properties. @@ -42,20 +53,35 @@ It is also possible to disable row selection using the `isSelectDisabled` functi If you want to have all expandable nodes open on initial load pass the `expandAll` prop to the DataViewTable component ### Table example + ```js file="./DataViewTableExample.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: + +- `isResizable` - indicates that the column is resizable +- `onResize` - a callback that will return the source event and the new width of the column +- `width` - a default width value for a column +- `minWidth` - the minimum width a column may shrink to +- `increment` - how many pixels the column will move left or right for keyboard navigation + +```js file="./DataViewTableResizableColumnsExample.tsx" + +``` + ## Tree table -A tree table includes expandable rows and custom icons for leaf and parent nodes. +A tree table includes expandable rows and custom icons for leaf and parent nodes. To enable a tree table, pass the `isTreeTable` flag to the `` component. - Tree table rows have to be defined with following keys: - - `row`: Defines the content for each cell in the row. - - `id`: Unique identifier for the row that's used for matching selected items. - - `children` (optional): Defines the children rows. + +- `row`: Defines the content for each cell in the row. +- `id`: Unique identifier for the row that's used for matching selected items. +- `children` (optional): Defines the children rows. To update a row's icon to reflect its expansion state, pass `collapsedIcon`, `expandedIcon`, and `leafIcon` to ``. @@ -68,17 +94,21 @@ To disable row selection, pass the `isSelectDisabled` function to `selection` pr ``` ## Sorting + The following example demonstrates how to enable sorting functionality within a data view. This implementation supports dynamic sorting by column and persists the sort state in the page's URL via [React Router](https://reactrouter.com/). ### Sorting example + ```js file="./SortingExample.tsx" ``` + ### Sorting state The `useDataViewSort` hook manages the sorting state of a data view and provides an easy way to handle sorting logic, such as synchronization with URL parameters and the definition of default sorting behavior. **Initial values:** + - `initialSort` object to set default `sortBy` and `direction` values: - `sortBy`: Key of the initial column to sort. - `direction`: Default sorting direction (`asc` or `desc`). @@ -88,44 +118,47 @@ The `useDataViewSort` hook manages the sorting state of a data view and provides - Customizable parameter names for the URL: - `sortByParam`: Name of the URL parameter for the column key. - `directionParam`: Name of the URL parameter for the sorting direction. -The `useDataViewSort` hook integrates seamlessly with [React Router](https://reactrouter.com/) to manage the sort state via URL parameters. Alternatively, you can use `URLSearchParams` and `window.history.pushState` APIs, or other routing libraries. If URL synchronization is not configured, the sort state is managed internally within the component. + The `useDataViewSort` hook integrates seamlessly with [React Router](https://reactrouter.com/) to manage the sort state via URL parameters. Alternatively, you can use `URLSearchParams` and `window.history.pushState` APIs, or other routing libraries. If URL synchronization is not configured, the sort state is managed internally within the component. **Return values:** + - `sortBy`: Key of the column currently being sorted. - `direction`: Current sorting direction (`asc` or `desc`). - `onSort`: Function to handle sorting changes programmatically or via user interaction. ## States -The data view table allows you to react to the `activeState` of the data view (such as `empty`, `error`, `loading`). You can use the `headStates` and `bodyStates` props to define the table head and body for a given state. +The data view table allows you to react to the `activeState` of the data view (such as `empty`, `error`, `loading`). You can use the `headStates` and `bodyStates` props to define the table head and body for a given state. ### Empty -When there is no data to render in the data view, you can instead display an empty state. -You can create your empty state by passing a [PatternFly empty state](/components/empty-state) to the `empty` key of `headStates` or `bodyStates`. +When there is no data to render in the data view, you can instead display an empty state. + +You can create your empty state by passing a [PatternFly empty state](/components/empty-state) to the `empty` key of `headStates` or `bodyStates`. ```js file="./DataViewTableEmptyExample.tsx" ``` ### Error + When there is a data connection or retrieval error, you can display an error state. The error state will be displayed when the data view `activeState` value is `error`. -You can create your error state by passing either the [component groups extension's error state](/component-groups/error-state) or a [PatternFly empty state](/components/empty-state) to the `error` key of `headStates` or `bodyStates`. +You can create your error state by passing either the [component groups extension's error state](/component-groups/error-state) or a [PatternFly empty state](/components/empty-state) to the `error` key of `headStates` or `bodyStates`. ```js file="./DataViewTableErrorExample.tsx" ``` ### Loading + To indicate that data is loading, you can display a loading state. The loading state will be displayed when the data view `activeState` value is `loading`. -You can create your loading state by passing either the [component groups extension's skeleton table](/component-groups/skeleton-table) or a customized [PatternFly empty state](/components/empty-state) to the `loading` key of `headStates` or `bodyStates`. - +You can create your loading state by passing either the [component groups extension's skeleton table](/component-groups/skeleton-table) or a customized [PatternFly empty state](/components/empty-state) to the `loading` key of `headStates` or `bodyStates`. ```js file="./DataViewTableLoadingExample.tsx" diff --git a/packages/module/src/DataViewTable/DataViewTable.tsx b/packages/module/src/DataViewTable/DataViewTable.tsx index 3c0c531..f55fd97 100644 --- a/packages/module/src/DataViewTable/DataViewTable.tsx +++ b/packages/module/src/DataViewTable/DataViewTable.tsx @@ -1,46 +1,71 @@ -import { FC, ReactNode } from 'react'; -import { - TdProps, - ThProps, - TrProps -} from '@patternfly/react-table'; +import { FC, ReactNode, MouseEvent as ReactMouseEvent, KeyboardEvent as ReactKeyboardEvent } from 'react'; +import { TdProps, ThProps, TrProps, InnerScrollContainer } from '@patternfly/react-table'; import { DataViewTableTree, DataViewTableTreeProps } from '../DataViewTableTree'; import { DataViewTableBasic, DataViewTableBasicProps } from '../DataViewTableBasic'; +// Table resizable typings +export interface DataViewThResizableProps { + /** Whether the column is resizable */ + isResizable?: boolean; + /** Callback after the column is resized */ + onResize?: ( + event: ReactMouseEvent | MouseEvent | ReactKeyboardEvent | KeyboardEvent | TouchEvent, + width: number + ) => void; + /** Width of the column */ + width?: number; + /** Minimum width of the column */ + minWidth?: number; + /** Increment for keyboard navigation */ + increment?: number; +} + // Table head typings -export type DataViewTh = ReactNode | { - /** Table head cell node */ - cell: ReactNode; - /** Props passed to Th */ - props?: ThProps -}; -export const isDataViewThObject = (value: DataViewTh): value is { cell: ReactNode; props?: ThProps } => value != null && typeof value === 'object' && 'cell' in value; +export type DataViewTh = + | ReactNode + | { + /** Table head cell node */ + cell: ReactNode; + /** Whether the column is resizable */ + resizableProps?: DataViewThResizableProps; + /** Props passed to Th */ + props?: ThProps; + }; +export const isDataViewThObject = (value: DataViewTh): value is { cell: ReactNode; props?: ThProps } => + value != null && typeof value === 'object' && 'cell' in value; // Basic table typings export interface DataViewTrObject { /** Array of rows */ - row: DataViewTd[], + row: DataViewTd[]; /** Unique identifier of a row */ - id?: string, + id?: string; /** Props passed to Tr */ - props?: TrProps + props?: TrProps; } -export type DataViewTd = ReactNode | { - /** Table body cell node */ - cell: ReactNode; - /** Props passed to Td */ - props?: TdProps -}; +export type DataViewTd = + | ReactNode + | { + /** Table body cell node */ + cell: ReactNode; + /** Props passed to Td */ + props?: TdProps; + }; export type DataViewTr = DataViewTd[] | DataViewTrObject; -export const isDataViewTdObject = (value: DataViewTd): value is { cell: ReactNode; props?: TdProps } => value != null && typeof value === 'object' && 'cell' in value; -export const isDataViewTrObject = (value: DataViewTr): value is { row: DataViewTd[], id?: string } => value != null && typeof value === 'object' && 'row' in value; +export const isDataViewTdObject = (value: DataViewTd): value is { cell: ReactNode; props?: TdProps } => + value != null && typeof value === 'object' && 'cell' in value; +export const isDataViewTrObject = (value: DataViewTr): value is { row: DataViewTd[]; id?: string } => + value != null && typeof value === 'object' && 'row' in value; // Tree table typings /** extends DataViewTrObject */ -export interface DataViewTrTree extends DataViewTrObject { id: string, children?: DataViewTrTree[] } +export interface DataViewTrTree extends DataViewTrObject { + id: string; + children?: DataViewTrTree[]; +} export type DataViewTableProps = | ({ @@ -48,10 +73,22 @@ export type DataViewTableProps = } & DataViewTableTreeProps) | ({ isTreeTable?: false; + isResizable?: boolean; } & DataViewTableBasicProps); -export const DataViewTable: FC = (props) => ( - props.isTreeTable ? : -); +export const DataViewTable: FC = (props) => { + if (props.isTreeTable) { + return ; + } else { + const { isResizable, ...rest } = props; + return isResizable ? ( + + + + ) : ( + + ); + } +}; export default DataViewTable; diff --git a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx index 931bb8e..6dcc0d1 100644 --- a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx +++ b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx @@ -1,12 +1,8 @@ import { FC, useMemo } from 'react'; -import { - Th, - Thead, - TheadProps, - Tr -} from '@patternfly/react-table'; +import { Th, Thead, TheadProps, Tr } from '@patternfly/react-table'; import { useInternalContext } from '../InternalContext'; import { DataViewTh, isDataViewThObject } from '../DataViewTable'; +import { DataViewTh as DataViewThElement } from '../DataViewTh/DataViewTh'; /** extends TheadProps */ export interface DataViewTableHeadProps extends TheadProps { @@ -27,26 +23,27 @@ export const DataViewTableHead: FC = ({ const { selection } = useInternalContext(); const { onSelect, isSelected } = selection ?? {}; - const cells = useMemo(() => [ - onSelect && isSelected && !isTreeTable ? ( - - ) : null, - ...columns.map((column, index) => ( - - {isDataViewThObject(column) ? column.cell : column} - - ) - ) ], [ columns, ouiaId, onSelect, isSelected, isTreeTable ]); + const cells = useMemo( + () => [ + onSelect && isSelected && !isTreeTable ? ( + + ) : null, + ...columns.map((column, index) => ( + + )) + ], + [ columns, ouiaId, onSelect, isSelected, isTreeTable ] + ); return ( - - {cells} - + {cells} ); }; diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx new file mode 100644 index 0000000..fb110eb --- /dev/null +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -0,0 +1,261 @@ +import { + FC, + useState, + MouseEvent as ReactMouseEvent, + TouchEvent as ReactTouchEvent, + KeyboardEvent as ReactKeyboardEvent, + ReactNode, + useCallback, + useRef, + useEffect +} from 'react'; +import { Th, ThProps } from '@patternfly/react-table'; +import { Button, getLanguageDirection } from '@patternfly/react-core'; + +export interface DataViewThProps { + /** Cell content */ + content: ReactNode; + /** Resizable props */ + resizableProps?: { + /** Whether the column is resizable */ + isResizable?: boolean; + /** Callback after the column is resized */ + onResize?: ( + event: ReactMouseEvent | MouseEvent | ReactKeyboardEvent | KeyboardEvent | TouchEvent, + width: number + ) => void; + /** Width of the column */ + width?: number; + /** Minimum width of the column */ + minWidth?: number; + /** Increment for keyboard navigation */ + increment?: number; + }; + /** Props passed to Th */ + props?: ThProps; +} + +export const DataViewTh: FC = ({ + resizableProps = { + isResizable: false, + width: 0, + increment: 10 + }, + content, + ...props +}: DataViewThProps) => { + const thRef = useRef(null); + + const isResizable = resizableProps.isResizable; + const resizeButtonRef = useRef(null); + const [ width, setWidth ] = useState(resizableProps.width ? resizableProps.width : 0); + const increment = resizableProps.increment || 5; + const onResize = resizableProps.onResize; + const setInitialVals = useRef(true); + const dragOffset = useRef(0); + const isResizing = useRef(false); + const isInView = useRef(true); + let currWidth = 0; + + useEffect(() => { + const observed = resizeButtonRef.current; + const observer = new IntersectionObserver( + ([ entry ]) => { + isInView.current = entry.isIntersecting; + }, + { threshold: 0.3 } + ); + + if (observed) { + observer.observe(observed); + } + + return () => { + if (observed) { + observer.unobserve(observed); + } + }; + }, []); + + useEffect(() => { + if (setInitialVals.current && width === 0) { + setWidth(thRef.current?.getBoundingClientRect().width || 0); + setInitialVals.current = false; + } + }, [ setInitialVals ]); + + const setDragOffset = (e: ReactMouseEvent | ReactTouchEvent) => { + const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl'; + const startingMousePos = 'clientX' in e ? e.clientX : e.touches[0].clientX; + const startingColumnPos = + (isRTL ? thRef.current?.getBoundingClientRect().left : thRef.current?.getBoundingClientRect().right) || 0; + + dragOffset.current = startingColumnPos - startingMousePos; + }; + + const handleMousedown = (e: ReactMouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + document.addEventListener('mousemove', callbackMouseMove); + document.addEventListener('mouseup', callbackMouseUp); + + // When a user begins resizing a column, set the drag offset so the drag motion aligns with the column edge + if (dragOffset.current === 0) { + setDragOffset(e); + } + + isResizing.current = true; + }; + + const handleMouseMove = (e: MouseEvent) => { + const mousePos = e.clientX; + + handleControlMove(e, dragOffset.current + mousePos); + }; + + const handleTouchStart = (e: ReactTouchEvent) => { + e.stopPropagation(); + document.addEventListener('touchmove', callbackTouchMove, { passive: false }); + document.addEventListener('touchend', callbackTouchEnd); + + // When a user begins resizing a column, set the drag offset so the drag motion aligns with the column edge + if (dragOffset.current === 0) { + setDragOffset(e); + } + + isResizing.current = true; + }; + + const handleTouchMove = (e: TouchEvent) => { + e.preventDefault(); + e.stopImmediatePropagation(); + const touchPos = e.touches[0].clientX; + + handleControlMove(e, touchPos); + }; + + const handleControlMove = (e: MouseEvent | TouchEvent, controlPosition: number) => { + e.stopPropagation(); + + if (!isResizing.current) { + return; + } + + const columnRect = thRef.current?.getBoundingClientRect(); + if (columnRect === undefined) { + return; + } + + const mousePos = controlPosition; + const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl'; + let newSize = isRTL ? columnRect?.right - mousePos : mousePos - columnRect?.left; + + // Prevent the column from shrinking below a specified minimum width + if (resizableProps.minWidth && newSize < resizableProps.minWidth) { + newSize = resizableProps.minWidth; + } + + thRef.current?.style.setProperty('min-width', newSize + 'px'); + currWidth = newSize; + }; + + const handleMouseup = (e: MouseEvent) => { + if (!isResizing.current) { + return; + } + // Reset the isResizing and dragOffset values to their initial states + isResizing.current = false; + dragOffset.current = 0; + + // Call the onResize callback with the new width + onResize && onResize(e, currWidth); + + // Handle scroll into view when column drag button is moved off screen + if (resizeButtonRef.current && !isInView.current) { + resizeButtonRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + + document.removeEventListener('mousemove', callbackMouseMove); + document.removeEventListener('mouseup', callbackMouseUp); + }; + + const handleTouchEnd = (e: TouchEvent) => { + e.stopPropagation(); + if (!isResizing) { + return; + } + // Reset the isResizing and dragOffset values to their initial states + isResizing.current = false; + dragOffset.current = 0; + + // Call the onResize callback with the new width + onResize && onResize(e, currWidth); + + document.removeEventListener('touchmove', callbackTouchMove); + document.removeEventListener('touchend', callbackTouchEnd); + }; + + const callbackMouseMove = useCallback(handleMouseMove, []); + const callbackTouchEnd = useCallback(handleTouchEnd, []); + const callbackTouchMove = useCallback(handleTouchMove, []); + const callbackMouseUp = useCallback(handleMouseup, []); + + const handleKeys = (e: React.KeyboardEvent) => { + const key = e.key; + + if (key !== 'Escape' && key !== 'Enter' && key !== 'ArrowLeft' && key !== 'ArrowRight') { + if (isResizing) { + e.preventDefault(); + } + return; + } + e.preventDefault(); + + if (key === 'Escape' || key === 'Enter') { + onResize && onResize(e, currWidth); + } + + const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl'; + const columnRect = thRef.current?.getBoundingClientRect(); + + let newSize = columnRect?.width || 0; + let delta = 0; + if (key === 'ArrowRight') { + if (isRTL) { + delta = -increment; + } else { + delta = increment; + } + } else if (key === 'ArrowLeft') { + if (isRTL) { + delta = increment; + } else { + delta = -increment; + } + } + newSize = newSize + delta; + + thRef.current?.style.setProperty('min-width', newSize + 'px'); + currWidth = newSize; + }; + + return ( + + {content} + {isResizable && ( + + )} + + ); +}; + +export default DataViewTh; diff --git a/packages/module/src/DataViewTh/pf-resize.svg b/packages/module/src/DataViewTh/pf-resize.svg new file mode 100644 index 0000000..a41e7c0 --- /dev/null +++ b/packages/module/src/DataViewTh/pf-resize.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file From e974b133b7fee52a521f79d9a4b09b3f19e4ff12 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Fri, 19 Sep 2025 11:40:09 -0400 Subject: [PATCH 02/29] fix spread props & lint, shortcut useeffects for non-resize cols, mock observer for jest env --- config/setupTests.js | 11 +++++++-- .../DataViewTableResizableColumnsExample.tsx | 1 + packages/module/src/DataViewTh/DataViewTh.tsx | 23 +++++++++++-------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/config/setupTests.js b/config/setupTests.js index 13d962a..065d210 100644 --- a/config/setupTests.js +++ b/config/setupTests.js @@ -11,8 +11,15 @@ global.MutationObserver = class { observe(element, initObject) {} }; +global.IntersectionObserver = class { + constructor(callback, options) {} + disconnect() {} + observe(element) {} + unobserve(element) {} +}; + jest.mock('react', () => ({ ...jest.requireActual('react'), - useLayoutEffect: jest.requireActual('react').useEffect, + useLayoutEffect: jest.requireActual('react').useEffect })); -Element.prototype.scrollTo = () => {}; \ No newline at end of file +Element.prototype.scrollTo = () => {}; diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 7ee55cc..464c6dc 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -103,6 +103,7 @@ const columns: DataViewTh[] = [ 'Repositories', { cell: 'col', + // eslint-disable-next-line no-console resizableProps: { isResizable: true, onResize: (e, width) => console.log('resized width: ', width) } }, 'Pull requests', diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index fb110eb..1d0c700 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -42,13 +42,14 @@ export const DataViewTh: FC = ({ increment: 10 }, content, - ...props + props: thProps, + ...restProps }: DataViewThProps) => { const thRef = useRef(null); const isResizable = resizableProps.isResizable; const resizeButtonRef = useRef(null); - const [ width, setWidth ] = useState(resizableProps.width ? resizableProps.width : 0); + const [width, setWidth] = useState(resizableProps.width ? resizableProps.width : 0); const increment = resizableProps.increment || 5; const onResize = resizableProps.onResize; const setInitialVals = useRef(true); @@ -58,9 +59,13 @@ export const DataViewTh: FC = ({ let currWidth = 0; useEffect(() => { + if (!isResizable) { + return; + } + const observed = resizeButtonRef.current; const observer = new IntersectionObserver( - ([ entry ]) => { + ([entry]) => { isInView.current = entry.isIntersecting; }, { threshold: 0.3 } @@ -78,11 +83,11 @@ export const DataViewTh: FC = ({ }, []); useEffect(() => { - if (setInitialVals.current && width === 0) { + if (isResizable && setInitialVals.current && width === 0) { setWidth(thRef.current?.getBoundingClientRect().width || 0); setInitialVals.current = false; } - }, [ setInitialVals ]); + }, [isResizable, setInitialVals]); const setDragOffset = (e: ReactMouseEvent | ReactTouchEvent) => { const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl'; @@ -190,7 +195,7 @@ export const DataViewTh: FC = ({ // Call the onResize callback with the new width onResize && onResize(e, currWidth); - + document.removeEventListener('touchmove', callbackTouchMove); document.removeEventListener('touchend', callbackTouchEnd); }; @@ -202,7 +207,7 @@ export const DataViewTh: FC = ({ const handleKeys = (e: React.KeyboardEvent) => { const key = e.key; - + if (key !== 'Escape' && key !== 'Enter' && key !== 'ArrowLeft' && key !== 'ArrowRight') { if (isResizing) { e.preventDefault(); @@ -217,7 +222,7 @@ export const DataViewTh: FC = ({ const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl'; const columnRect = thRef.current?.getBoundingClientRect(); - + let newSize = columnRect?.width || 0; let delta = 0; if (key === 'ArrowRight') { @@ -240,7 +245,7 @@ export const DataViewTh: FC = ({ }; return ( - + 0 ? { minWidth: width } : undefined} ref={thRef}> {content} {isResizable && ( From ebb15c23998afdbb2be8ae6f8045eaf14d954b90 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:13:58 -0400 Subject: [PATCH 05/29] update keyboard interaction --- .../DataViewTableResizableColumnsExample.tsx | 29 ++++++++++++++----- packages/module/src/DataViewTh/DataViewTh.tsx | 23 +++++++++++---- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 464c6dc..54af223 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -1,5 +1,10 @@ -import { FunctionComponent } from 'react'; -import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { FunctionComponent, useState } from 'react'; +import { + DataViewTable, + DataViewTr, + DataViewTh, + isDataViewThObject +} from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { Button } from '@patternfly/react-core'; import { ActionsColumn } from '@patternfly/react-table'; @@ -103,8 +108,7 @@ const columns: DataViewTh[] = [ 'Repositories', { cell: 'col', - // eslint-disable-next-line no-console - resizableProps: { isResizable: true, onResize: (e, width) => console.log('resized width: ', width) } + resizableProps: { isResizable: true } }, 'Pull requests', { cell: 'Workspaces', props: { info: { tooltip: 'More information' } } }, @@ -113,6 +117,17 @@ const columns: DataViewTh[] = [ const ouiaId = 'TableExample'; -export const ResizableColumnsExample: FunctionComponent = () => ( - -); +export const ResizableColumnsExample: FunctionComponent = () => { + const [ screenReaderText, setScreenReaderText ] = useState(''); + + if (columns[2] && isDataViewThObject(columns[2]) && columns[2].resizableProps) { + columns[2].resizableProps.onResize = (_e, width) => { + // eslint-disable-next-line no-console + console.log('resized width: ', width); + setScreenReaderText(`Column ${width} pixels`); + }; + columns[2].resizableProps.screenreaderText = screenReaderText; + } + + return ; +}; diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 602277b..6314cd3 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -29,6 +29,8 @@ export interface DataViewThResizableProps { shiftIncrement?: number; /** Aria label for the resize button */ resizeButtonAriaLabel?: string; + /** Screenreader text for the column */ + screenreaderText?: string; } export interface DataViewThProps { /** Cell content */ @@ -50,14 +52,16 @@ export const DataViewTh: FC = ({ }: DataViewThProps) => { const thRef = useRef(null); + const [ width, setWidth ] = useState(resizableProps?.width ? resizableProps.width : 0); + const isResizable = resizableProps?.isResizable || false; const increment = resizableProps?.increment || 5; const shiftIncrement = resizableProps?.shiftIncrement || 25; const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel || `Resize ${content}`; const onResize = resizableProps?.onResize || undefined; + const screenreaderText = resizableProps?.screenreaderText || `Column ${width} pixels`; const resizeButtonRef = useRef(null); - const [ width, setWidth ] = useState(resizableProps?.width ? resizableProps.width : 0); const setInitialVals = useRef(true); const dragOffset = useRef(0); const isResizing = useRef(false); @@ -214,7 +218,11 @@ export const DataViewTh: FC = ({ const handleKeys = (e: ReactKeyboardEvent) => { const key = e.key; - if (key !== 'Enter' && key !== 'ArrowLeft' && key !== 'ArrowRight' && key !== 'Tab' && key !== 'Space') { + if (key === 'Tab') { + isResizing.current = false; + } + + if (key !== 'ArrowLeft' && key !== 'ArrowRight') { if (isResizing) { e.preventDefault(); } @@ -222,10 +230,7 @@ export const DataViewTh: FC = ({ } e.preventDefault(); - if (key === 'Enter' || key === 'Tab' || key === 'Space') { - onResize && onResize(e, currWidth); - } - + isResizing.current = true; const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl'; const columnRect = thRef.current?.getBoundingClientRect(); @@ -247,6 +252,7 @@ export const DataViewTh: FC = ({ } } newSize = newSize + delta; + onResize && onResize(e, currWidth); thRef.current?.style.setProperty('min-width', newSize + 'px'); currWidth = newSize; @@ -268,6 +274,11 @@ export const DataViewTh: FC = ({ test )} + {isResizable && ( +
+ {screenreaderText} +
+ )} ); }; From b020101b002fb76c508c36a66282dcf5207764ac Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:18:17 -0400 Subject: [PATCH 06/29] remove preventDefault for non arrow keys --- packages/module/src/DataViewTh/DataViewTh.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 6314cd3..9bebb2a 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -223,9 +223,6 @@ export const DataViewTh: FC = ({ } if (key !== 'ArrowLeft' && key !== 'ArrowRight') { - if (isResizing) { - e.preventDefault(); - } return; } e.preventDefault(); From e21ec6754e07e803f8c1e935403ea1ae08172980 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:22:57 -0400 Subject: [PATCH 07/29] add useState to .md --- .../content/extensions/data-view/examples/Table/Table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a68d6ac..71abc1e 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 @@ -24,7 +24,7 @@ propComponents: sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md --- -import { FunctionComponent, useMemo } from 'react'; +import { FunctionComponent, useMemo, useState } from 'react'; import { BrowserRouter, useSearchParams } from 'react-router-dom'; import { Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter } from '@patternfly/react-core'; import { CubesIcon, FolderIcon, FolderOpenIcon, LeafIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; From 37674b878bcb9236c2a3f94d20287df40ef23a80 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:28:23 -0400 Subject: [PATCH 08/29] add other missing function --- .../content/extensions/data-view/examples/Table/Table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 71abc1e..b4cfde1 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 @@ -30,7 +30,7 @@ import { Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter import { CubesIcon, FolderIcon, FolderOpenIcon, LeafIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; import { ErrorState, ResponsiveAction, ResponsiveActions, SkeletonTableHead, SkeletonTableBody } from '@patternfly/react-component-groups'; import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; -import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { DataViewTable, isDataViewThObject } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { useDataViewSelection, useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks'; import { DataView, DataViewState } from '@patternfly/react-data-view/dist/dynamic/DataView'; From 5a7b0e0ff43fe8b669b881e4fce7f72c2e164075 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:40:36 -0400 Subject: [PATCH 09/29] updated example to properly update srtext --- .../DataViewTableResizableColumnsExample.tsx | 41 ++++++++++--------- packages/module/src/DataViewTh/DataViewTh.tsx | 1 + 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 54af223..6eb002f 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -103,31 +103,32 @@ const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspac { cell: , props: { isActionCell: true } } ]); -const columns: DataViewTh[] = [ - null, - 'Repositories', - { - cell: 'col', - resizableProps: { isResizable: true } - }, - 'Pull requests', - { cell: 'Workspaces', props: { info: { tooltip: 'More information' } } }, - { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } } -]; - const ouiaId = 'TableExample'; export const ResizableColumnsExample: FunctionComponent = () => { const [ screenReaderText, setScreenReaderText ] = useState(''); - if (columns[2] && isDataViewThObject(columns[2]) && columns[2].resizableProps) { - columns[2].resizableProps.onResize = (_e, width) => { - // eslint-disable-next-line no-console - console.log('resized width: ', width); - setScreenReaderText(`Column ${width} pixels`); - }; - columns[2].resizableProps.screenreaderText = screenReaderText; - } + const onResize = (_e, width) => { + // eslint-disable-next-line no-console + console.log('resized width: ', width); + setScreenReaderText(`Column ${width} pixels`); + }; + + const columns: DataViewTh[] = [ + null, + 'Repositories', + { + cell: 'col', + resizableProps: { + isResizable: true, + onResize, + screenreaderText: screenReaderText + } + }, + 'Pull requests', + { cell: 'Workspaces', props: { info: { tooltip: 'More information' } } }, + { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } } + ]; return ; }; diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 9bebb2a..66d2bd2 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -60,6 +60,7 @@ export const DataViewTh: FC = ({ const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel || `Resize ${content}`; const onResize = resizableProps?.onResize || undefined; const screenreaderText = resizableProps?.screenreaderText || `Column ${width} pixels`; + console.log('sr text', resizableProps?.screenreaderText); const resizeButtonRef = useRef(null); const setInitialVals = useRef(true); From b4e33cd0dee9b28b7d4ab69e450fe590b9d515d5 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:42:52 -0400 Subject: [PATCH 10/29] remove console log --- packages/module/src/DataViewTh/DataViewTh.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 66d2bd2..9bebb2a 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -60,7 +60,6 @@ export const DataViewTh: FC = ({ const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel || `Resize ${content}`; const onResize = resizableProps?.onResize || undefined; const screenreaderText = resizableProps?.screenreaderText || `Column ${width} pixels`; - console.log('sr text', resizableProps?.screenreaderText); const resizeButtonRef = useRef(null); const setInitialVals = useRef(true); From 88466f98c7718273b5399d1b478172978abf9389 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:46:00 -0400 Subject: [PATCH 11/29] remove unused helper call in example --- .../examples/Table/DataViewTableResizableColumnsExample.tsx | 3 +-- .../content/extensions/data-view/examples/Table/Table.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 6eb002f..f871c93 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -2,8 +2,7 @@ import { FunctionComponent, useState } from 'react'; import { DataViewTable, DataViewTr, - DataViewTh, - isDataViewThObject + DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { Button } from '@patternfly/react-core'; import { ActionsColumn } from '@patternfly/react-table'; 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 b4cfde1..71abc1e 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 @@ -30,7 +30,7 @@ import { Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter import { CubesIcon, FolderIcon, FolderOpenIcon, LeafIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; import { ErrorState, ResponsiveAction, ResponsiveActions, SkeletonTableHead, SkeletonTableBody } from '@patternfly/react-component-groups'; import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; -import { DataViewTable, isDataViewThObject } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { useDataViewSelection, useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks'; import { DataView, DataViewState } from '@patternfly/react-data-view/dist/dynamic/DataView'; From 0d23398b68749b6cb279760365958854638014b0 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:48:13 -0400 Subject: [PATCH 12/29] move onResize for keyboard --- packages/module/src/DataViewTh/DataViewTh.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 9bebb2a..1a26549 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -249,10 +249,10 @@ export const DataViewTh: FC = ({ } } newSize = newSize + delta; - onResize && onResize(e, currWidth); thRef.current?.style.setProperty('min-width', newSize + 'px'); currWidth = newSize; + onResize && onResize(e, currWidth); }; return ( From e4bbb26a53d6debc33bb87404830cb86ada7c8ec Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:51:13 -0400 Subject: [PATCH 13/29] round srtext width --- .../examples/Table/DataViewTableResizableColumnsExample.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index f871c93..ab538a6 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -110,7 +110,7 @@ export const ResizableColumnsExample: FunctionComponent = () => { const onResize = (_e, width) => { // eslint-disable-next-line no-console console.log('resized width: ', width); - setScreenReaderText(`Column ${width} pixels`); + setScreenReaderText(`Column ${width.toFixed(0)} pixels`); }; const columns: DataViewTh[] = [ From 4025dfad0ee06c1b201360fe6ec342c7664dc416 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 16:56:30 -0400 Subject: [PATCH 14/29] update srtext to be in state --- packages/module/src/DataViewTh/DataViewTh.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 1a26549..2f9aac5 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -53,20 +53,26 @@ export const DataViewTh: FC = ({ const thRef = useRef(null); const [ width, setWidth ] = useState(resizableProps?.width ? resizableProps.width : 0); + const [ screenreaderText, setScreenreaderText ] = useState(resizableProps?.screenreaderText || `Column ${width.toFixed(0)} pixels`); + let currWidth = 0; const isResizable = resizableProps?.isResizable || false; const increment = resizableProps?.increment || 5; const shiftIncrement = resizableProps?.shiftIncrement || 25; const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel || `Resize ${content}`; const onResize = resizableProps?.onResize || undefined; - const screenreaderText = resizableProps?.screenreaderText || `Column ${width} pixels`; const resizeButtonRef = useRef(null); const setInitialVals = useRef(true); const dragOffset = useRef(0); const isResizing = useRef(false); const isInView = useRef(true); - let currWidth = 0; + + useEffect(() => { + if (!resizableProps?.screenreaderText && currWidth > 0) { + setScreenreaderText(`Column ${currWidth.toFixed(0)} pixels`); + } + }, [ currWidth ]); useEffect(() => { if (!isResizable) { From 7f27aaaec909c954337a097f97b81111fcb0ddef Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 17:05:09 -0400 Subject: [PATCH 15/29] clean up width logic and srtext --- .../DataViewTableResizableColumnsExample.tsx | 2 +- packages/module/src/DataViewTh/DataViewTh.tsx | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index ab538a6..cc5efb3 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -109,7 +109,7 @@ export const ResizableColumnsExample: FunctionComponent = () => { const onResize = (_e, width) => { // eslint-disable-next-line no-console - console.log('resized width: ', width); + console.log('resized width: ', width.toFixed(0)); setScreenReaderText(`Column ${width.toFixed(0)} pixels`); }; diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 2f9aac5..c257c30 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -53,14 +53,13 @@ export const DataViewTh: FC = ({ const thRef = useRef(null); const [ width, setWidth ] = useState(resizableProps?.width ? resizableProps.width : 0); - const [ screenreaderText, setScreenreaderText ] = useState(resizableProps?.screenreaderText || `Column ${width.toFixed(0)} pixels`); - let currWidth = 0; const isResizable = resizableProps?.isResizable || false; const increment = resizableProps?.increment || 5; const shiftIncrement = resizableProps?.shiftIncrement || 25; const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel || `Resize ${content}`; const onResize = resizableProps?.onResize || undefined; + const screenreaderText = resizableProps?.screenreaderText || `Column ${width.toFixed(0)} pixels`; const resizeButtonRef = useRef(null); const setInitialVals = useRef(true); @@ -68,12 +67,6 @@ export const DataViewTh: FC = ({ const isResizing = useRef(false); const isInView = useRef(true); - useEffect(() => { - if (!resizableProps?.screenreaderText && currWidth > 0) { - setScreenreaderText(`Column ${currWidth.toFixed(0)} pixels`); - } - }, [ currWidth ]); - useEffect(() => { if (!isResizable) { return; @@ -177,7 +170,7 @@ export const DataViewTh: FC = ({ } thRef.current?.style.setProperty('min-width', newSize + 'px'); - currWidth = newSize; + setWidth(newSize); }; const handleMouseup = (e: MouseEvent) => { @@ -189,7 +182,7 @@ export const DataViewTh: FC = ({ dragOffset.current = 0; // Call the onResize callback with the new width - onResize && onResize(e, currWidth); + onResize && onResize(e, width); // Handle scroll into view when column drag button is moved off screen if (resizeButtonRef.current && !isInView.current) { @@ -210,7 +203,7 @@ export const DataViewTh: FC = ({ dragOffset.current = 0; // Call the onResize callback with the new width - onResize && onResize(e, currWidth); + onResize && onResize(e, width); document.removeEventListener('touchmove', callbackTouchMove); document.removeEventListener('touchend', callbackTouchEnd); @@ -257,8 +250,8 @@ export const DataViewTh: FC = ({ newSize = newSize + delta; thRef.current?.style.setProperty('min-width', newSize + 'px'); - currWidth = newSize; - onResize && onResize(e, currWidth); + setWidth(newSize); + onResize && onResize(e, newSize); }; return ( From 1ddd1e435c4fcf2682f179cb34580854e04093cb Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 23 Sep 2025 17:07:14 -0400 Subject: [PATCH 16/29] update sr prop name --- .../examples/Table/DataViewTableResizableColumnsExample.tsx | 2 +- packages/module/src/DataViewTh/DataViewTh.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index cc5efb3..f60e3a7 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -121,7 +121,7 @@ export const ResizableColumnsExample: FunctionComponent = () => { resizableProps: { isResizable: true, onResize, - screenreaderText: screenReaderText + screenReaderText } }, 'Pull requests', diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index c257c30..385d1a7 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -30,7 +30,7 @@ export interface DataViewThResizableProps { /** Aria label for the resize button */ resizeButtonAriaLabel?: string; /** Screenreader text for the column */ - screenreaderText?: string; + screenReaderText?: string; } export interface DataViewThProps { /** Cell content */ @@ -59,7 +59,7 @@ export const DataViewTh: FC = ({ const shiftIncrement = resizableProps?.shiftIncrement || 25; const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel || `Resize ${content}`; const onResize = resizableProps?.onResize || undefined; - const screenreaderText = resizableProps?.screenreaderText || `Column ${width.toFixed(0)} pixels`; + const screenReaderText = resizableProps?.screenReaderText || `Column ${width.toFixed(0)} pixels`; const resizeButtonRef = useRef(null); const setInitialVals = useRef(true); @@ -272,7 +272,7 @@ export const DataViewTh: FC = ({ )} {isResizable && (
- {screenreaderText} + {screenReaderText}
)} From 3f51e93f5159444fdf95cad58dd12ff7fe142588 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Fri, 26 Sep 2025 10:27:23 -0400 Subject: [PATCH 17/29] update some prop desc, remove default aria label and add warning if not passed, rename thprops --- .../DataViewTableHead/DataViewTableHead.tsx | 2 +- packages/module/src/DataViewTh/DataViewTh.tsx | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx index 0cd2f6a..81fc1e1 100644 --- a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx +++ b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx @@ -37,7 +37,7 @@ export const DataViewTableHead: FC = ({ content={isDataViewThObject(column) ? column.cell : column} resizableProps={isDataViewThObject(column) ? column.resizableProps : undefined} data-ouia-component-id={`${ouiaId}-th-${index}`} - props={isDataViewThObject(column) ? (column?.props ?? {}) : {}} + thProps={isDataViewThObject(column) ? (column?.props ?? {}) : {}} hasResizableColumns={hasResizableColumns} /> )) diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 385d1a7..f07bde3 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -27,9 +27,9 @@ export interface DataViewThResizableProps { increment?: number; /** Increment for keyboard navigation while shift is held */ shiftIncrement?: number; - /** Aria label for the resize button */ + /** Provides an accessible name for the resizable column via a human readable string. */ resizeButtonAriaLabel?: string; - /** Screenreader text for the column */ + /** Screenreader text that gets announced when the column is resized. */ screenReaderText?: string; } export interface DataViewThProps { @@ -40,15 +40,15 @@ export interface DataViewThProps { /** @hide Indicates whether the table has resizable columns */ hasResizableColumns?: boolean; /** Props passed to Th */ - props?: ThProps; + thProps?: ThProps; } export const DataViewTh: FC = ({ content, resizableProps = {}, hasResizableColumns = false, - props: thProps, - ...restProps + thProps, + ...props }: DataViewThProps) => { const thRef = useRef(null); @@ -57,7 +57,7 @@ export const DataViewTh: FC = ({ const isResizable = resizableProps?.isResizable || false; const increment = resizableProps?.increment || 5; const shiftIncrement = resizableProps?.shiftIncrement || 25; - const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel || `Resize ${content}`; + const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel; const onResize = resizableProps?.onResize || undefined; const screenReaderText = resizableProps?.screenReaderText || `Column ${width.toFixed(0)} pixels`; @@ -67,6 +67,11 @@ export const DataViewTh: FC = ({ const isResizing = useRef(false); const isInView = useRef(true); + if (isResizable && !resizeButtonAriaLabel) { + // eslint-disable-next-line no-console + console.warn('DataViewTh: Missing resizeButtonAriaLabel. An aria label must be passed to each resizable column to provide a context specific label for its resize button.'); + } + useEffect(() => { if (!isResizable) { return; @@ -255,7 +260,7 @@ export const DataViewTh: FC = ({ }; return ( - 0 ? { minWidth: width } : undefined} ref={thRef}> + 0 ? { minWidth: width } : undefined} ref={thRef}> {content} {isResizable && ( + className={classes.dataViewResizableButton} + /> )} {isResizable && (
diff --git a/packages/module/src/DataViewTh/pf-resize.svg b/packages/module/src/DataViewTh/pf-resize.svg deleted file mode 100644 index a41e7c0..0000000 --- a/packages/module/src/DataViewTh/pf-resize.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file From b4507afcdf54a56a7fe117260bc24f66b0248982 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Fri, 26 Sep 2025 14:48:25 -0400 Subject: [PATCH 21/29] fix callback width returning 0, add id to callback, add aria label to examples, update example resize callback with id --- .../DataViewTableResizableColumnsExample.tsx | 40 +++++++++---------- packages/module/src/DataViewTh/DataViewTh.tsx | 12 ++++-- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 7da7bf6..76c5d43 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -1,9 +1,5 @@ import { FunctionComponent, useState } from 'react'; -import { - DataViewTable, - DataViewTr, - DataViewTh -} from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { Button } from '@patternfly/react-core'; import { ActionsColumn } from '@patternfly/react-table'; @@ -105,50 +101,54 @@ const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspac const ouiaId = 'TableExample'; export const ResizableColumnsExample: FunctionComponent = () => { - const [ screenReaderText, setScreenReaderText ] = useState(''); - - const onResize = (_e, width) => { + const onResize = ( + _e: React.MouseEvent | MouseEvent | React.KeyboardEvent | KeyboardEvent | TouchEvent, + id: string | number | undefined, + width: number + ) => { // eslint-disable-next-line no-console - console.log('resized width: ', width.toFixed(0)); - setScreenReaderText(`Column ${width.toFixed(0)} pixels`); + console.log(`resized column id: ${id} width to: ${width.toFixed(0)}px`); }; const columns: DataViewTh[] = [ null, 'Repositories', { - cell: 'col', + cell: 'Branches', resizableProps: { isResizable: true, onResize, - screenReaderText - } + resizeButtonAriaLabel: 'Resize repositories column' + }, + props: { id: 'repositories' } }, { cell: 'Pull requests', resizableProps: { isResizable: true, onResize, - screenReaderText + resizeButtonAriaLabel: 'Resize pull requests column' }, - props: { info: { tooltip: 'More information' } } + props: { info: { tooltip: 'More information' }, id: 'pull-requests' } }, { cell: 'This is a really long title', resizableProps: { isResizable: true, onResize, - screenReaderText + resizeButtonAriaLabel: 'Resize this is a really long title column' }, - props: { info: { tooltip: 'More information' } } + props: { info: { tooltip: 'More information' }, id: 'this-is-a-really-long-title' } }, - { cell: 'Last commit', + { + cell: 'Last commit', resizableProps: { isResizable: true, onResize, - screenReaderText + resizeButtonAriaLabel: 'Resize last commit column' }, - props: { sort: { sortBy: {}, columnIndex: 4 } } } + props: { sort: { sortBy: {}, columnIndex: 4 }, id: 'last-commit' } + } ]; return ; diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index b9a753a..7813778 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -37,9 +37,10 @@ const useStyles = createUseStyles({ export interface DataViewThResizableProps { /** Whether the column is resizable */ isResizable?: boolean; - /** Callback after the column is resized */ + /** Callback after the column is resized. Returns the triggering event, the column id passed in via cell props, and the new width of the column. */ onResize?: ( event: ReactMouseEvent | MouseEvent | ReactKeyboardEvent | KeyboardEvent | TouchEvent, + id: string | number | undefined, width: number ) => void; /** Width of the column */ @@ -76,6 +77,8 @@ export const DataViewTh: FC = ({ const thRef = useRef(null); const [ width, setWidth ] = useState(resizableProps?.width ? resizableProps.width : 0); + // Tracks the current column width for the onResize callback, because the width state is not updated until after the resize is complete + const trackedWidth = useRef(0); const classes = useStyles(); const isResizable = resizableProps?.isResizable || false; @@ -200,6 +203,7 @@ export const DataViewTh: FC = ({ } thRef.current?.style.setProperty('min-width', newSize + 'px'); + trackedWidth.current = newSize; setWidth(newSize); }; @@ -212,7 +216,7 @@ export const DataViewTh: FC = ({ dragOffset.current = 0; // Call the onResize callback with the new width - onResize && onResize(e, width); + onResize && onResize(e, thProps?.id, trackedWidth.current); // Handle scroll into view when column drag button is moved off screen if (resizeButtonRef.current && !isInView.current) { @@ -233,7 +237,7 @@ export const DataViewTh: FC = ({ dragOffset.current = 0; // Call the onResize callback with the new width - onResize && onResize(e, width); + onResize && onResize(e, thProps?.id, trackedWidth.current); document.removeEventListener('touchmove', callbackTouchMove); document.removeEventListener('touchend', callbackTouchEnd); @@ -281,7 +285,7 @@ export const DataViewTh: FC = ({ thRef.current?.style.setProperty('min-width', newSize + 'px'); setWidth(newSize); - onResize && onResize(e, newSize); + onResize && onResize(e, thProps?.id, newSize); }; return ( From 41f26d26a96c702e1244ccd65773b0f8d1bd91d1 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Fri, 26 Sep 2025 14:50:19 -0400 Subject: [PATCH 22/29] fix lint --- .../examples/Table/DataViewTableResizableColumnsExample.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 76c5d43..211716e 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -1,4 +1,4 @@ -import { FunctionComponent, useState } from 'react'; +import { FunctionComponent } from 'react'; import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { Button } from '@patternfly/react-core'; import { ActionsColumn } from '@patternfly/react-table'; From f4a5d2e274b57d479b772f78e1df0f6d72bd9f8d Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Fri, 26 Sep 2025 16:38:33 -0400 Subject: [PATCH 23/29] remove resize from sort column --- .../examples/Table/DataViewTableResizableColumnsExample.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 211716e..0a895cd 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -142,11 +142,6 @@ export const ResizableColumnsExample: FunctionComponent = () => { }, { cell: 'Last commit', - resizableProps: { - isResizable: true, - onResize, - resizeButtonAriaLabel: 'Resize last commit column' - }, props: { sort: { sortBy: {}, columnIndex: 4 }, id: 'last-commit' } } ]; From ea9f17b007335d53cc2fd5e14bb4f9c839dc64ca Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Fri, 26 Sep 2025 16:55:46 -0400 Subject: [PATCH 24/29] fix typo in docs --- .../content/extensions/data-view/examples/Table/Table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 270646f..9ef6a7c 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 @@ -63,7 +63,7 @@ If you want to have all expandable nodes open on initial load pass the `expandAl 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: - `isResizable` - indicates that the column is resizable -- `resizeButtonAriaLabel` - an accesible name for the resizable column's resize button. This must be passed in if the column is resizable. +- `resizeButtonAriaLabel` - an accessible name for the resizable column's resize button. This must be passed in if the column is resizable. - `onResize` - a callback that will return the source event and the new width of the column - `width` - a default width value for a column - `minWidth` - the minimum width a column may shrink to From 9c0eb6b188967abdf3448aebde91fbd449051103 Mon Sep 17 00:00:00 2001 From: Michael Coker <35148959+mcoker@users.noreply.github.com> Date: Mon, 29 Sep 2025 10:11:17 -0500 Subject: [PATCH 25/29] chore: use grabbing cursor on resize --- packages/module/src/DataViewTh/DataViewTh.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 7813778..37471a5 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -31,7 +31,10 @@ const useStyles = createUseStyles({ position: 'absolute', insetInlineEnd: `calc(${globalFontSizeBodyDefault.var} / 2)`, insetBlockEnd: tableCellPaddingBlockEnd.var, - cursor: 'grab' + cursor: 'grab', + '&:active': { + cursor: 'grabbing', + }, }, }); export interface DataViewThResizableProps { From df5499f703732f342354f4263dff899eb895ea5b Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Mon, 29 Sep 2025 12:01:46 -0400 Subject: [PATCH 26/29] update to main prerelease versions, add back resize to sort column in example --- package-lock.json | 208 ++++++++++++++++-- packages/module/package.json | 10 +- .../DataViewTableResizableColumnsExample.tsx | 5 + packages/module/src/DataViewTh/DataViewTh.tsx | 76 ++++--- 4 files changed, 244 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index 594c0ba..d743369 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4313,7 +4313,8 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.1.0.tgz", "integrity": "sha512-w+QazL8NHKkg5j01eotblsswKxQQSYB0CN3yBXQL9ScpHdp/fK8M6TqWbKZNRpf+NqhMxcH/om8eR0N/fDCJqw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@patternfly/patternfly-a11y": { "version": "5.1.0", @@ -4364,6 +4365,7 @@ "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.1.0.tgz", "integrity": "sha512-ae04+DdkgXFn3wEzvNCncNa78ZK3Swh5ng8p7yqFrD6lhW69NoJf+DdQlHi8gM8Qy05DNnIemSbQWpWLpInyzw==", "dev": true, + "peer": true, "dependencies": { "@monaco-editor/react": "^4.6.0", "@patternfly/react-core": "^6.1.0", @@ -4378,19 +4380,19 @@ } }, "node_modules/@patternfly/react-component-groups": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-6.1.0.tgz", - "integrity": "sha512-8RkQv9wQk+D+nUMFtl4uk0ddMvxZ/+jFwnnXe2fw/BulouDVbpKRI24C1S8i1OQHr3++CbocBmmWRV0iw9Kvlw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-6.3.0.tgz", + "integrity": "sha512-W8vSYD4KrAhDnjRLCPK+irVhG9GORQ7PveBFJ9FAvjCc4lGv73smDY4M1Lv2peNHQaXQpn6DSPsuynaReRvIhg==", "dependencies": { "@patternfly/react-core": "^6.0.0", "@patternfly/react-icons": "^6.0.0", + "@patternfly/react-styles": "^6.0.0", "@patternfly/react-table": "^6.0.0", - "clsx": "^2.1.1", "react-jss": "^10.10.0" }, "peerDependencies": { - "react": "^17 || ^18", - "react-dom": "^17 || ^18" + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/@patternfly/react-core": { @@ -28767,17 +28769,17 @@ "license": "MIT", "dependencies": { "@patternfly/react-component-groups": "^6.1.0", - "@patternfly/react-core": "^6.0.0", - "@patternfly/react-icons": "^6.0.0", - "@patternfly/react-table": "^6.0.0", + "@patternfly/react-core": "6.4.0-prerelease.1", + "@patternfly/react-icons": "6.4.0-prerelease.1", + "@patternfly/react-table": "6.4.0-prerelease.2", "clsx": "^2.1.1", "react-jss": "^10.10.0" }, "devDependencies": { "@patternfly/documentation-framework": "^6.5.20", - "@patternfly/patternfly": "^6.0.0", + "@patternfly/patternfly": "6.4.0-prerelease.1", "@patternfly/patternfly-a11y": "^5.1.0", - "@patternfly/react-code-editor": "^6.0.0", + "@patternfly/react-code-editor": "6.4.0-prerelease.1", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@types/react-router-dom": "^5.3.3", @@ -28792,6 +28794,94 @@ "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" } + }, + "packages/module/node_modules/@patternfly/patternfly": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.4.0-prerelease.1.tgz", + "integrity": "sha512-jOLzGhRJJTJsmLV7jjFNUz+VrRs/2RTM2vtupi2K3s1rGogA3jSfUqZ5VpeHLbCQp/96WSPEjFKqyYK4iLuC0Q==", + "dev": true, + "engines": { + "node": ">=20.18.3 <22" + } + }, + "packages/module/node_modules/@patternfly/react-code-editor": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.4.0-prerelease.1.tgz", + "integrity": "sha512-x73NmyCsVC21mBeDPX5BestP5dv+0nQWZTjT774RnYjIWzGqjGryZ6qYQUdFi6WE0oembbRkAhSYLfrKuun6vA==", + "dev": true, + "dependencies": { + "@monaco-editor/react": "^4.6.0", + "@patternfly/react-core": "^6.4.0-prerelease.1", + "@patternfly/react-icons": "^6.4.0-prerelease.1", + "@patternfly/react-styles": "^6.4.0-prerelease.1", + "react-dropzone": "14.3.5", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + } + }, + "packages/module/node_modules/@patternfly/react-core": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.4.0-prerelease.1.tgz", + "integrity": "sha512-3HNvK/EpmkkDoEjNi99CqX6jLgTnjOLe5ZPsNoLixQkSfzK873YCynRMewdgpP5W3CsjN2HwCAL8ZZt0ZjRCJw==", + "dependencies": { + "@patternfly/react-icons": "^6.4.0-prerelease.1", + "@patternfly/react-styles": "^6.4.0-prerelease.1", + "@patternfly/react-tokens": "^6.4.0-prerelease.1", + "focus-trap": "7.6.4", + "react-dropzone": "^14.3.5", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + } + }, + "packages/module/node_modules/@patternfly/react-icons": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.4.0-prerelease.1.tgz", + "integrity": "sha512-hJhiLOPOcAZjwgNIA6dytoJCC/EMTLwslLwyLZNZBNu5jjK4ymf1DmZMgC4KNhIaPBrf7rQ6WTzg2AEl6zD6/A==", + "peerDependencies": { + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + } + }, + "packages/module/node_modules/@patternfly/react-styles": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.4.0-prerelease.1.tgz", + "integrity": "sha512-UlS6BjZQii8xbevK5J9CXfRBKo3GZV4O0xUKERoc6uxzDHeWSAMIF9E44SleN5G+Z2FdDst9N7ZbQlxtQbxmfA==" + }, + "packages/module/node_modules/@patternfly/react-table": { + "version": "6.4.0-prerelease.2", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-6.4.0-prerelease.2.tgz", + "integrity": "sha512-GqKKgUDfgWQAyltt7MA0ty0fS9TdU5lkUXi0wZkPntPqX4ZjQDN/aMnsQ8xoGakh3/qnms/VPYABdMGaLBGZFQ==", + "dependencies": { + "@patternfly/react-core": "^6.4.0-prerelease.1", + "@patternfly/react-icons": "^6.4.0-prerelease.1", + "@patternfly/react-styles": "^6.4.0-prerelease.1", + "@patternfly/react-tokens": "^6.4.0-prerelease.1", + "lodash": "^4.17.21", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + } + }, + "packages/module/node_modules/@patternfly/react-tokens": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.4.0-prerelease.1.tgz", + "integrity": "sha512-507dlMylOPcRqzhbYHTgQQIhfT3KNJYTX0mTnNQM5jHBsM+UFpfPbs1O6qMmX/+XniVHNtJKNRzgSG2UOhOHtg==" + }, + "packages/module/node_modules/focus-trap": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz", + "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", + "dependencies": { + "tabbable": "^6.2.0" + } } }, "dependencies": { @@ -31609,7 +31699,8 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.1.0.tgz", "integrity": "sha512-w+QazL8NHKkg5j01eotblsswKxQQSYB0CN3yBXQL9ScpHdp/fK8M6TqWbKZNRpf+NqhMxcH/om8eR0N/fDCJqw==", - "dev": true + "dev": true, + "peer": true }, "@patternfly/patternfly-a11y": { "version": "5.1.0", @@ -31640,6 +31731,7 @@ "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.1.0.tgz", "integrity": "sha512-ae04+DdkgXFn3wEzvNCncNa78ZK3Swh5ng8p7yqFrD6lhW69NoJf+DdQlHi8gM8Qy05DNnIemSbQWpWLpInyzw==", "dev": true, + "peer": true, "requires": { "@monaco-editor/react": "^4.6.0", "@patternfly/react-core": "^6.1.0", @@ -31650,14 +31742,14 @@ } }, "@patternfly/react-component-groups": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-6.1.0.tgz", - "integrity": "sha512-8RkQv9wQk+D+nUMFtl4uk0ddMvxZ/+jFwnnXe2fw/BulouDVbpKRI24C1S8i1OQHr3++CbocBmmWRV0iw9Kvlw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-6.3.0.tgz", + "integrity": "sha512-W8vSYD4KrAhDnjRLCPK+irVhG9GORQ7PveBFJ9FAvjCc4lGv73smDY4M1Lv2peNHQaXQpn6DSPsuynaReRvIhg==", "requires": { "@patternfly/react-core": "^6.0.0", "@patternfly/react-icons": "^6.0.0", + "@patternfly/react-styles": "^6.0.0", "@patternfly/react-table": "^6.0.0", - "clsx": "^2.1.1", "react-jss": "^10.10.0" } }, @@ -31678,13 +31770,13 @@ "version": "file:packages/module", "requires": { "@patternfly/documentation-framework": "^6.5.20", - "@patternfly/patternfly": "^6.0.0", + "@patternfly/patternfly": "6.4.0-prerelease.1", "@patternfly/patternfly-a11y": "^5.1.0", - "@patternfly/react-code-editor": "^6.0.0", + "@patternfly/react-code-editor": "6.4.0-prerelease.1", "@patternfly/react-component-groups": "^6.1.0", - "@patternfly/react-core": "^6.0.0", - "@patternfly/react-icons": "^6.0.0", - "@patternfly/react-table": "^6.0.0", + "@patternfly/react-core": "6.4.0-prerelease.1", + "@patternfly/react-icons": "6.4.0-prerelease.1", + "@patternfly/react-table": "6.4.0-prerelease.2", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@types/react-router-dom": "^5.3.3", @@ -31696,6 +31788,78 @@ "react-router-dom": "^6.30.1", "rimraf": "^6.0.1", "typescript": "^5.9.2" + }, + "dependencies": { + "@patternfly/patternfly": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.4.0-prerelease.1.tgz", + "integrity": "sha512-jOLzGhRJJTJsmLV7jjFNUz+VrRs/2RTM2vtupi2K3s1rGogA3jSfUqZ5VpeHLbCQp/96WSPEjFKqyYK4iLuC0Q==", + "dev": true + }, + "@patternfly/react-code-editor": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.4.0-prerelease.1.tgz", + "integrity": "sha512-x73NmyCsVC21mBeDPX5BestP5dv+0nQWZTjT774RnYjIWzGqjGryZ6qYQUdFi6WE0oembbRkAhSYLfrKuun6vA==", + "dev": true, + "requires": { + "@monaco-editor/react": "^4.6.0", + "@patternfly/react-core": "^6.4.0-prerelease.1", + "@patternfly/react-icons": "^6.4.0-prerelease.1", + "@patternfly/react-styles": "^6.4.0-prerelease.1", + "react-dropzone": "14.3.5", + "tslib": "^2.8.1" + } + }, + "@patternfly/react-core": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.4.0-prerelease.1.tgz", + "integrity": "sha512-3HNvK/EpmkkDoEjNi99CqX6jLgTnjOLe5ZPsNoLixQkSfzK873YCynRMewdgpP5W3CsjN2HwCAL8ZZt0ZjRCJw==", + "requires": { + "@patternfly/react-icons": "^6.4.0-prerelease.1", + "@patternfly/react-styles": "^6.4.0-prerelease.1", + "@patternfly/react-tokens": "^6.4.0-prerelease.1", + "focus-trap": "7.6.4", + "react-dropzone": "^14.3.5", + "tslib": "^2.8.1" + } + }, + "@patternfly/react-icons": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.4.0-prerelease.1.tgz", + "integrity": "sha512-hJhiLOPOcAZjwgNIA6dytoJCC/EMTLwslLwyLZNZBNu5jjK4ymf1DmZMgC4KNhIaPBrf7rQ6WTzg2AEl6zD6/A==", + "requires": {} + }, + "@patternfly/react-styles": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.4.0-prerelease.1.tgz", + "integrity": "sha512-UlS6BjZQii8xbevK5J9CXfRBKo3GZV4O0xUKERoc6uxzDHeWSAMIF9E44SleN5G+Z2FdDst9N7ZbQlxtQbxmfA==" + }, + "@patternfly/react-table": { + "version": "6.4.0-prerelease.2", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-6.4.0-prerelease.2.tgz", + "integrity": "sha512-GqKKgUDfgWQAyltt7MA0ty0fS9TdU5lkUXi0wZkPntPqX4ZjQDN/aMnsQ8xoGakh3/qnms/VPYABdMGaLBGZFQ==", + "requires": { + "@patternfly/react-core": "^6.4.0-prerelease.1", + "@patternfly/react-icons": "^6.4.0-prerelease.1", + "@patternfly/react-styles": "^6.4.0-prerelease.1", + "@patternfly/react-tokens": "^6.4.0-prerelease.1", + "lodash": "^4.17.21", + "tslib": "^2.8.1" + } + }, + "@patternfly/react-tokens": { + "version": "6.4.0-prerelease.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.4.0-prerelease.1.tgz", + "integrity": "sha512-507dlMylOPcRqzhbYHTgQQIhfT3KNJYTX0mTnNQM5jHBsM+UFpfPbs1O6qMmX/+XniVHNtJKNRzgSG2UOhOHtg==" + }, + "focus-trap": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz", + "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", + "requires": { + "tabbable": "^6.2.0" + } + } } }, "@patternfly/react-icons": { diff --git a/packages/module/package.json b/packages/module/package.json index 475079c..e53d7fd 100644 --- a/packages/module/package.json +++ b/packages/module/package.json @@ -32,9 +32,9 @@ }, "dependencies": { "@patternfly/react-component-groups": "^6.1.0", - "@patternfly/react-core": "^6.0.0", - "@patternfly/react-icons": "^6.0.0", - "@patternfly/react-table": "^6.0.0", + "@patternfly/react-core": "6.4.0-prerelease.1", + "@patternfly/react-icons": "6.4.0-prerelease.1", + "@patternfly/react-table": "6.4.0-prerelease.2", "clsx": "^2.1.1", "react-jss": "^10.10.0" }, @@ -44,8 +44,8 @@ }, "devDependencies": { "@patternfly/documentation-framework": "^6.5.20", - "@patternfly/patternfly": "^6.0.0", - "@patternfly/react-code-editor": "^6.0.0", + "@patternfly/patternfly": "6.4.0-prerelease.1", + "@patternfly/react-code-editor": "6.4.0-prerelease.1", "@patternfly/patternfly-a11y": "^5.1.0", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx index 0a895cd..211716e 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx @@ -142,6 +142,11 @@ export const ResizableColumnsExample: FunctionComponent = () => { }, { cell: 'Last commit', + resizableProps: { + isResizable: true, + onResize, + resizeButtonAriaLabel: 'Resize last commit column' + }, props: { sort: { sortBy: {}, columnIndex: 4 }, id: 'last-commit' } } ]; diff --git a/packages/module/src/DataViewTh/DataViewTh.tsx b/packages/module/src/DataViewTh/DataViewTh.tsx index 37471a5..a3c9066 100644 --- a/packages/module/src/DataViewTh/DataViewTh.tsx +++ b/packages/module/src/DataViewTh/DataViewTh.tsx @@ -18,8 +18,19 @@ import tableCellPaddingInlineEnd from '@patternfly/react-tokens/dist/esm/c_table import globalFontSizeBodyDefault from '@patternfly/react-tokens/dist/esm/t_global_font_size_body_default'; const ResizeIcon = () => ( -
+ {screenReaderText} +
+