From cfbe8f0fa040d78ce56a8237cb6cc0be44bf6229 Mon Sep 17 00:00:00 2001 From: Filip Hlavac <50696716+fhlavac@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:24:08 +0200 Subject: [PATCH 01/23] Update Components.md --- .../extensions/data-view/examples/Components/Components.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md index a7efd40..6ab0c4d 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md @@ -72,6 +72,8 @@ The `DataViewTable` component accepts the following props: - optional `props` (`TableProps`) that are passed down to the `` component, except for `onSelect`, which is managed internally. +It is also possible to disable row selection using the `isSelectDisabled` function passed to the wrapping data view component. + ### Tree table example This example shows the tree table variant with expandable rows, custom icons for leaf and parent nodes. Tree table is turned on by passing `isTreeTable` flag to the `DataViewTable` component. You can pass `collapsedIcon`, `expandedIcon` or `leafIcon` to be displayen rows with given status. The tree table rows have to be defined in a format of object with following keys: - `row` (`DataViewTd[]`) defining the content for each cell in the row. From d3af475faac8c72b17891e532310e8c7c2da3101 Mon Sep 17 00:00:00 2001 From: Filip Hlavac <50696716+fhlavac@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:49:57 +0200 Subject: [PATCH 02/23] Update Components.md --- .../extensions/data-view/examples/Components/Components.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md index 6ab0c4d..ab240ed 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md @@ -72,7 +72,7 @@ The `DataViewTable` component accepts the following props: - optional `props` (`TableProps`) that are passed down to the `
` component, except for `onSelect`, which is managed internally. -It is also possible to disable row selection using the `isSelectDisabled` function passed to the wrapping data view component. +It is also possible to disable row selection using the `isSelectDisabled` function passed to the wrapping data view component through `selection`. ### Tree table example This example shows the tree table variant with expandable rows, custom icons for leaf and parent nodes. Tree table is turned on by passing `isTreeTable` flag to the `DataViewTable` component. You can pass `collapsedIcon`, `expandedIcon` or `leafIcon` to be displayen rows with given status. The tree table rows have to be defined in a format of object with following keys: @@ -80,7 +80,7 @@ This example shows the tree table variant with expandable rows, custom icons for - `id` (`string`) for the row (used to match items in selection end expand the rows). - optional `children` (`DataViewTrTree[]`) defining the children rows. -It is also possible to disable row selection using the `isSelectDisabled` function passed to the wrapping data view component. +It is also possible to disable row selection using the `isSelectDisabled` function passed to the wrapping data view component through `selection`. ```js file="./DataViewTableTreeExample.tsx" From 1253b5b75ab358676f4b4c24c38d3611924594ea Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Fri, 1 Nov 2024 14:12:01 +0100 Subject: [PATCH 03/23] fix(DataView): Extend StackProps in DataViewProps --- packages/module/src/DataView/DataView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/module/src/DataView/DataView.tsx b/packages/module/src/DataView/DataView.tsx index 094d594..201a586 100644 --- a/packages/module/src/DataView/DataView.tsx +++ b/packages/module/src/DataView/DataView.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Stack, StackItem } from '@patternfly/react-core'; +import { Stack, StackItem, StackProps } from '@patternfly/react-core'; import { DataViewSelection, InternalContextProvider } from '../InternalContext'; export const DataViewState = { @@ -10,7 +10,8 @@ export const DataViewState = { export type DataViewState = typeof DataViewState[keyof typeof DataViewState]; -export interface DataViewProps { +/** extends StackProps */ +export interface DataViewProps extends StackProps { /** Content rendered inside the data view */ children: React.ReactNode; /** Custom OUIA ID */ From 756ed012c993af57ce495bbf29309edd365abd6b Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Fri, 1 Nov 2024 14:12:32 +0100 Subject: [PATCH 04/23] fix(DataView): Update example to not trigger rowClick on select/action --- .../examples/EventsContext/EventsContext.md | 1 + .../examples/EventsContext/EventsExample.tsx | 32 ++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md index 2c0cfe4..293a1a0 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md @@ -18,6 +18,7 @@ import { Table, Tbody, Th, Thead, Tr, Td } from '@patternfly/react-table'; import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { useDataViewEventsContext, DataViewEventsContext, DataViewEventsProvider, EventTypes } from '@patternfly/react-data-view/dist/dynamic/DataViewEventsContext'; +import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks'; import { Drawer, DrawerContent, DrawerContentBody } from '@patternfly/react-core'; The **data view events context** provides a way of listening to the data view events from the outside of the component. diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx index 19e09be..d3ccfad 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx @@ -3,6 +3,8 @@ import { Drawer, DrawerActions, DrawerCloseButton, DrawerContent, DrawerContentB import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { DataViewEventsProvider, EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view/dist/dynamic/DataViewEventsContext'; +import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { ActionsColumn } from '@patternfly/react-table'; interface Repository { name: string; @@ -64,25 +66,45 @@ interface RepositoriesTableProps { selectedRepo?: Repository; } +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 RepositoriesTable: React.FunctionComponent = ({ selectedRepo = undefined }) => { + const selection = useDataViewSelection({ matchOption: (a, b) => a.row[0] === b.row[0] }); const { trigger } = useDataViewEventsContext(); const rows = useMemo(() => { - const handleRowClick = (repo: Repository | undefined) => { - trigger(EventTypes.rowClick, repo); + const handleRowClick = (event, repo: Repository | undefined) => { + // prevents drawer toggle on actions or checkbox click + event.target.matches('td') && trigger(EventTypes.rowClick, repo); }; return repositories.map(repo => ({ - row: Object.values(repo), + row: [ ...Object.values(repo), { cell: , props: { isActionCell: true } } ], props: { isClickable: true, - onRowClick: () => handleRowClick(selectedRepo?.name === repo.name ? undefined : repo), + onRowClick: (event) => handleRowClick(event, selectedRepo?.name === repo.name ? undefined : repo), isRowSelected: selectedRepo?.name === repo.name } })); }, [ selectedRepo?.name, trigger ]); return ( - + ); From 5a6ae92874d9f5c0c07672cb47eba1e0c664c5d7 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Fri, 1 Nov 2024 14:27:36 +0100 Subject: [PATCH 05/23] fix(DataView): Allow rowClick on Tr as well --- .../data-view/examples/EventsContext/EventsExample.tsx | 2 +- packages/module/patternfly-docs/generated/index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx index d3ccfad..2afd6fb 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx @@ -90,7 +90,7 @@ const RepositoriesTable: React.FunctionComponent = ({ se const rows = useMemo(() => { const handleRowClick = (event, repo: Repository | undefined) => { // prevents drawer toggle on actions or checkbox click - event.target.matches('td') && trigger(EventTypes.rowClick, repo); + (event.target.matches('td') || event.target.matches('tr')) && trigger(EventTypes.rowClick, repo); }; return repositories.map(repo => ({ diff --git a/packages/module/patternfly-docs/generated/index.js b/packages/module/patternfly-docs/generated/index.js index 851e24a..278a0b3 100644 --- a/packages/module/patternfly-docs/generated/index.js +++ b/packages/module/patternfly-docs/generated/index.js @@ -49,8 +49,8 @@ module.exports = { '/extensions/data-view/components/react': { id: "Components", title: "Components", - toc: [{"text":"Data view toolbar"},[{"text":"Basic toolbar example"}],{"text":"Data view table"},[{"text":"Rows and columns customization"},{"text":"Tree table example"},{"text":"Empty state example"}]], - examples: ["Basic toolbar example","Rows and columns customization","Tree table example","Empty state example"], + toc: [{"text":"Data view toolbar"},[{"text":"Basic toolbar example"},{"text":"Actions configuration"},{"text":"Actions example"}],{"text":"Data view table"},[{"text":"Rows and columns customization"},{"text":"Tree table example"},{"text":"Empty state example"},{"text":"Error state example"},{"text":"Loading state example"}]], + examples: ["Basic toolbar example","Actions example","Rows and columns customization","Tree table example","Empty state example","Error state example","Loading state example"], section: "extensions", subsection: "Data view", source: "react", From f9bfa0607462f05010d46c47108bb0f274f739e0 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Tue, 29 Oct 2024 13:47:38 +0100 Subject: [PATCH 06/23] fix(docs): Display extended interfaces in the docs --- .../src/DataViewTable/DataViewTable.tsx | 26 ++++++++++++++++--- .../DataViewTableBasic/DataViewTableBasic.tsx | 1 + .../DataViewTableHead/DataViewTableHead.tsx | 1 + .../DataViewTableTree/DataViewTableTree.tsx | 1 + packages/module/src/Hooks/pagination.ts | 1 + packages/module/src/Hooks/selection.ts | 5 ++-- .../src/InternalContext/InternalContext.tsx | 1 + 7 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/module/src/DataViewTable/DataViewTable.tsx b/packages/module/src/DataViewTable/DataViewTable.tsx index f7a0697..97a4260 100644 --- a/packages/module/src/DataViewTable/DataViewTable.tsx +++ b/packages/module/src/DataViewTable/DataViewTable.tsx @@ -8,18 +8,38 @@ import { DataViewTableTree, DataViewTableTreeProps } from '../DataViewTableTree' import { DataViewTableBasic, DataViewTableBasicProps } from '../DataViewTableBasic'; // Table head typings -export type DataViewTh = ReactNode | { cell: ReactNode; props?: ThProps }; +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; // Basic table typings -export interface DataViewTrObject { row: DataViewTd[], id?: string, props?: TrProps } -export type DataViewTd = ReactNode | { cell: ReactNode; props?: TdProps }; +export interface DataViewTrObject { + /** Array of rows */ + row: DataViewTd[], + /** Unique identifier of a row */ + id?: string, + /** Props passed to Tr */ + props?: TrProps +} + +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; // Tree table typings +/** extends DataViewTrObject */ export interface DataViewTrTree extends DataViewTrObject { id: string, children?: DataViewTrTree[] } export type DataViewTableProps = diff --git a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx index d2f1ac8..2b32cae 100644 --- a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx +++ b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx @@ -11,6 +11,7 @@ import { DataViewTableHead } from '../DataViewTableHead'; import { DataViewTh, DataViewTr, isDataViewTdObject, isDataViewTrObject } from '../DataViewTable'; import { DataViewState } from '../DataView/DataView'; +/** extends TableProps */ export interface DataViewTableBasicProps extends Omit { /** Columns definition */ columns: DataViewTh[]; diff --git a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx index 8466389..d1073d4 100644 --- a/packages/module/src/DataViewTableHead/DataViewTableHead.tsx +++ b/packages/module/src/DataViewTableHead/DataViewTableHead.tsx @@ -8,6 +8,7 @@ import { import { useInternalContext } from '../InternalContext'; import { DataViewTh, isDataViewThObject } from '../DataViewTable'; +/** extends TheadProps */ export interface DataViewTableHeadProps extends TheadProps { /** Indicates whether table is a tree */ isTreeTable?: boolean; diff --git a/packages/module/src/DataViewTableTree/DataViewTableTree.tsx b/packages/module/src/DataViewTableTree/DataViewTableTree.tsx index a5643f8..a743295 100644 --- a/packages/module/src/DataViewTableTree/DataViewTableTree.tsx +++ b/packages/module/src/DataViewTableTree/DataViewTableTree.tsx @@ -30,6 +30,7 @@ const isNodeChecked = (node: DataViewTrTree, isSelected: (node: DataViewTrTree) return allSelected; }; +/** extends TableProps */ export interface DataViewTableTreeProps extends Omit { /** Columns definition */ columns: DataViewTh[]; diff --git a/packages/module/src/Hooks/pagination.ts b/packages/module/src/Hooks/pagination.ts index 9f48c52..4209f66 100644 --- a/packages/module/src/Hooks/pagination.ts +++ b/packages/module/src/Hooks/pagination.ts @@ -15,6 +15,7 @@ export interface UseDataViewPaginationProps { perPageParam?: string; } +/** extends UseDataViewPaginationProps */ export interface DataViewPaginationProps extends UseDataViewPaginationProps { /** Current page number */ page: number; diff --git a/packages/module/src/Hooks/selection.ts b/packages/module/src/Hooks/selection.ts index d5858ae..2ba443f 100644 --- a/packages/module/src/Hooks/selection.ts +++ b/packages/module/src/Hooks/selection.ts @@ -8,8 +8,9 @@ export interface UseDataViewSelectionProps { initialSelected?: (any)[]; } -export const useDataViewSelection = ({ matchOption, initialSelected = [] }: UseDataViewSelectionProps) => { - const [ selected, setSelected ] = useState(initialSelected); +export const useDataViewSelection = (props?: UseDataViewSelectionProps) => { + const [ selected, setSelected ] = useState(props?.initialSelected ?? []); + const matchOption = props?.matchOption ? props.matchOption : (option, another) => (option === another); const onSelect = (isSelecting: boolean, items?: any[] | any) => { isSelecting && items ? diff --git a/packages/module/src/InternalContext/InternalContext.tsx b/packages/module/src/InternalContext/InternalContext.tsx index bef1b22..740c1a8 100644 --- a/packages/module/src/InternalContext/InternalContext.tsx +++ b/packages/module/src/InternalContext/InternalContext.tsx @@ -17,6 +17,7 @@ export interface InternalContextProps { activeState?: DataViewState | string; } +/** extends InternalContextProps */ export interface InternalContextValue extends InternalContextProps { /** Flag indicating if data view is selectable (auto-calculated) */ isSelectable: boolean; From ec73d468fff55d8e0a3747db07832c693612a1f3 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Tue, 5 Nov 2024 00:30:15 +0100 Subject: [PATCH 07/23] feat(filters): Implement DataViewFilters and text filter --- .../src/DataViewFilters/DataViewFilters.tsx | 119 ++++++++++++++++++ packages/module/src/DataViewFilters/index.tsx | 2 + .../DataViewTextFilter/DataViewTextFilter.tsx | 53 ++++++++ .../module/src/DataViewTextFilter/index.ts | 2 + .../src/DataViewToolbar/DataViewToolbar.tsx | 75 ++++++----- packages/module/src/Hooks/filters.ts | 96 ++++++++++++++ packages/module/src/Hooks/index.ts | 1 + packages/module/src/index.ts | 3 + 8 files changed, 323 insertions(+), 28 deletions(-) create mode 100644 packages/module/src/DataViewFilters/DataViewFilters.tsx create mode 100644 packages/module/src/DataViewFilters/index.tsx create mode 100644 packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx create mode 100644 packages/module/src/DataViewTextFilter/index.ts create mode 100644 packages/module/src/Hooks/filters.ts diff --git a/packages/module/src/DataViewFilters/DataViewFilters.tsx b/packages/module/src/DataViewFilters/DataViewFilters.tsx new file mode 100644 index 0000000..1dc9958 --- /dev/null +++ b/packages/module/src/DataViewFilters/DataViewFilters.tsx @@ -0,0 +1,119 @@ +import React, { useMemo, useState, useRef, useEffect, ReactElement } from 'react'; +import { + Menu, MenuContent, MenuItem, MenuList, MenuToggle, Popper, ToolbarGroup, ToolbarToggleGroup, ToolbarToggleGroupProps, +} from '@patternfly/react-core'; +import { FilterIcon } from '@patternfly/react-icons'; + +// helper interface to generate attribute menu +interface DataViewFilterIdentifiers { + filterId: string; + title: string; +} + +/** extends ToolbarToggleGroupProps */ +export interface DataViewFiltersProps extends Omit { + /** Content rendered inside the data view */ + children: React.ReactNode; + /** Optional onChange callback shared across filters */ + onChange?: (key: string, newValues: Partial) => void; + /** Optional values shared across filters */ + values?: T; + /** Icon for the toolbar toggle group */ + toggleIcon?: ToolbarToggleGroupProps['toggleIcon']; + /** Breakpoint for the toolbar toggle group */ + breakpoint?: ToolbarToggleGroupProps['breakpoint']; + /** Custom OUIA ID */ + ouiaId?: string; +}; + + +export const DataViewFilters = ({ + children, + ouiaId = 'DataViewFilters', + toggleIcon = , + breakpoint = 'xl', + onChange, + values, + ...props +}: DataViewFiltersProps) => { + const [ activeAttributeMenu, setActiveAttributeMenu ] = useState(''); + const [ isAttributeMenuOpen, setIsAttributeMenuOpen ] = useState(false); + const attributeToggleRef = useRef(null); + const attributeMenuRef = useRef(null); + const attributeContainerRef = useRef(null); + + const filterItems: DataViewFilterIdentifiers[] = useMemo(() => React.Children.toArray(children) + .map(child => + React.isValidElement(child) ? { filterId: String(child.props.filterId), title: String(child.props.title) } : undefined + ).filter((item): item is DataViewFilterIdentifiers => !!item), []); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title); + }, [ filterItems ]); + + const attributeToggle = ( + setIsAttributeMenuOpen(!isAttributeMenuOpen)} + isExpanded={isAttributeMenuOpen} + icon={toggleIcon} + > + {activeAttributeMenu} + + ); + + const attributeMenu = ( + { + const selectedItem = filterItems.find(item => item.filterId === itemId); + selectedItem && setActiveAttributeMenu(selectedItem.title); + setIsAttributeMenuOpen(false); + }} + > + + + {filterItems.map(item => ( + + {item.title} + + ))} + + + + ); + + return ( + + +
+ +
+ {React.Children.map(children, (child) => ( + React.isValidElement(child) ? ( + React.cloneElement(child as ReactElement<{ + showToolbarItem: boolean; + onChange: (_e: unknown, values: unknown) => void; + value: unknown; + }>, { + showToolbarItem: activeAttributeMenu === child.props.title, + onChange: (event, value) => onChange?.(event, { [child.props.filterId]: value } as Partial), + value: values?.[child.props.filterId], + ...child.props + }) + ) : child + ))} + +
+
+ ); +}; + +export default DataViewFilters; diff --git a/packages/module/src/DataViewFilters/index.tsx b/packages/module/src/DataViewFilters/index.tsx new file mode 100644 index 0000000..ba0ff37 --- /dev/null +++ b/packages/module/src/DataViewFilters/index.tsx @@ -0,0 +1,2 @@ +export { default } from './DataViewFilters'; +export * from './DataViewFilters'; diff --git a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx new file mode 100644 index 0000000..e2d1e43 --- /dev/null +++ b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { SearchInput, SearchInputProps, ToolbarFilter, ToolbarFilterProps } from '@patternfly/react-core'; + +/** extends SearchInputProps */ +export interface DataViewTextFilterProps extends SearchInputProps { + /** Unique key for the filter attribute */ + filterId: string; + /** Current filter value */ + value?: string; + /** Filter title displayed in the toolbar */ + title: string; + /** Callback for when the input value changes */ + onChange?: (event: React.FormEvent | undefined, value: string) => void; + /** Controls visibility of the filter in the toolbar */ + showToolbarItem?: ToolbarFilterProps['showToolbarItem']; + /** Trims input value on change */ + trimValue?: boolean; + /** Custom OUIA ID */ + ouiaId?: string; +} + +export const DataViewTextFilter: React.FC = ({ + filterId, + title, + value = '', + onChange, + onClear = () => onChange?.(undefined, ''), + showToolbarItem, + trimValue = true, + ouiaId = 'DataViewTextFilter', + ...props +}: DataViewTextFilterProps) => ( + 0 ? [ { key: title, node: value } ] : []} + deleteChip={() => onChange?.(undefined, '')} + categoryName={title} + showToolbarItem={showToolbarItem} + > + onChange?.(e, trimValue ? inputValue.trim() : inputValue)} + onClear={onClear} + placeholder={`Filter by ${title}`} + aria-label={`${title ?? filterId} filter`} + data-ouia-component-id={`${ouiaId}-input`} + {...props} + /> + +); + +export default DataViewTextFilter; diff --git a/packages/module/src/DataViewTextFilter/index.ts b/packages/module/src/DataViewTextFilter/index.ts new file mode 100644 index 0000000..fd2e65a --- /dev/null +++ b/packages/module/src/DataViewTextFilter/index.ts @@ -0,0 +1,2 @@ +export { default } from './DataViewTextFilter'; +export * from './DataViewTextFilter'; diff --git a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx index c2f6f1d..bdb962d 100644 --- a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx +++ b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx @@ -1,41 +1,60 @@ -import React, { PropsWithChildren } from 'react'; -import { Toolbar, ToolbarContent, ToolbarItem, ToolbarItemVariant } from '@patternfly/react-core'; +import React, { PropsWithChildren, useRef } from 'react'; +import { Button, Toolbar, ToolbarContent, ToolbarItem, ToolbarItemVariant, ToolbarProps } from '@patternfly/react-core'; -export interface DataViewToolbarProps extends PropsWithChildren { +/** extends ToolbarProps */ +export interface DataViewToolbarProps extends Omit, 'ref'> { /** Toolbar className */ className?: string; /** Custom OUIA ID */ ouiaId?: string; - /** React component to display bulk select */ + /** React node to display bulk select */ bulkSelect?: React.ReactNode; - /** React component to display pagination */ + /** React node to display pagination */ pagination?: React.ReactNode; - /** React component to display actions */ + /** React node to display actions */ actions?: React.ReactNode; + /** React node to display filters */ + filters?: React.ReactNode; + /** React node to display custom filter chips */ + customChipGroupContent?: React.ReactNode; } -export const DataViewToolbar: React.FC = ({ className, ouiaId = 'DataViewToolbar', bulkSelect, actions, pagination, children, ...props }: DataViewToolbarProps) => ( - - - {bulkSelect && ( - - {bulkSelect} - - )} - {actions && ( - - {actions} - - )} - {pagination && ( - - {pagination} - - )} - {children} - - -) +export const DataViewToolbar: React.FC = ({ className, ouiaId = 'DataViewToolbar', bulkSelect, actions, pagination, filters, customChipGroupContent, clearAllFilters, children, ...props }: DataViewToolbarProps) => { + const defaultClearFilters = useRef( + + + + ); + return ( + + + {bulkSelect && ( + + {bulkSelect} + + )} + {actions && ( + + {actions} + + )} + {filters && ( + + {filters} + + )} + {pagination && ( + + {pagination} + + )} + {children} + + + ) +}; export default DataViewToolbar; diff --git a/packages/module/src/Hooks/filters.ts b/packages/module/src/Hooks/filters.ts new file mode 100644 index 0000000..4f81e17 --- /dev/null +++ b/packages/module/src/Hooks/filters.ts @@ -0,0 +1,96 @@ +import { useState, useCallback, useEffect, useMemo } from "react"; + +export interface UseDataViewFiltersProps { + /** Initial filters object */ + initialFilters?: T; + /** Current search parameters as a string */ + searchParams?: URLSearchParams; + /** Function to set search parameters */ + setSearchParams?: (params: URLSearchParams) => void; +}; + +export const useDataViewFilters = ({ + initialFilters = {} as T, + searchParams, + setSearchParams, +}: UseDataViewFiltersProps) => { + const isUrlSyncEnabled = useMemo(() => searchParams && !!setSearchParams, [ searchParams, setSearchParams ]); + + const getInitialFilters = useCallback((): T => isUrlSyncEnabled ? Object.keys(initialFilters).reduce((loadedFilters, key) => { + const urlValue = searchParams?.get(key); + loadedFilters[key as keyof T] = urlValue + ? (urlValue as T[keyof T] | T[keyof T]) + : initialFilters[key as keyof T]; + return loadedFilters; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, { ...initialFilters }) : initialFilters, [ isUrlSyncEnabled, JSON.stringify(initialFilters), searchParams?.toString() ]); + + const [ filters, setFilters ] = useState(getInitialFilters()); + + const updateSearchParams = useCallback( + (newFilters: T) => { + if (isUrlSyncEnabled) { + const params = new URLSearchParams(searchParams); + Object.entries(newFilters).forEach(([ key, value ]) => { + if (value) { + params.set(key, Array.isArray(value) ? value.join(',') : value); + } else { + params.delete(key); + } + }); + setSearchParams?.(params); + } + }, + [ isUrlSyncEnabled, searchParams, setSearchParams ] + ); + + useEffect(() => { + isUrlSyncEnabled && setFilters(getInitialFilters()) + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const onSetFilters = useCallback( + (newFilters: Partial) => { + setFilters(prevFilters => { + const updatedFilters = { ...prevFilters, ...newFilters }; + isUrlSyncEnabled && updateSearchParams(updatedFilters); + return updatedFilters; + }); + }, + [ isUrlSyncEnabled, updateSearchParams ] + ); + + // helper function to reset filters + const resetFilterValues = useCallback((filters: Partial): Partial => Object.entries(filters).reduce((acc, [ key, value ]) => { + if (Array.isArray(value)) { + acc[key as keyof T] = [] as T[keyof T]; + } else { + acc[key as keyof T] = '' as T[keyof T]; + } + return acc; + }, {} as Partial), []); + + const onDeleteFilters = useCallback( + (filtersToDelete: Partial) => { + setFilters(prevFilters => { + const updatedFilters = { ...prevFilters,...resetFilterValues(filtersToDelete) }; + isUrlSyncEnabled && updateSearchParams(updatedFilters); + return updatedFilters; + }); + }, + [ isUrlSyncEnabled, updateSearchParams, resetFilterValues ] + ); + + const clearAllFilters = useCallback(() => { + const clearedFilters = resetFilterValues(filters) as T; + + setFilters(clearedFilters); + isUrlSyncEnabled && updateSearchParams(clearedFilters); + }, [ filters, isUrlSyncEnabled, updateSearchParams, resetFilterValues ]); + + return { + filters, + onSetFilters, + onDeleteFilters, + clearAllFilters, + }; +}; diff --git a/packages/module/src/Hooks/index.ts b/packages/module/src/Hooks/index.ts index 4ed29ea..546a0da 100644 --- a/packages/module/src/Hooks/index.ts +++ b/packages/module/src/Hooks/index.ts @@ -1,2 +1,3 @@ export * from './pagination'; export * from './selection'; +export * from './filters'; diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index 1ce1801..a34586c 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -7,6 +7,9 @@ export * from './Hooks'; export { default as DataViewToolbar } from './DataViewToolbar'; export * from './DataViewToolbar'; +export { default as DataViewTextFilter } from './DataViewTextFilter'; +export * from './DataViewTextFilter'; + export { default as DataViewTableTree } from './DataViewTableTree'; export * from './DataViewTableTree'; From f51f1a106885c07bb44f9f4bab96316fb1b92ffa Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Tue, 5 Nov 2024 00:30:49 +0100 Subject: [PATCH 08/23] feat(filters): Test DataView filters --- cypress/component/DataViewFilters.cy.tsx | 111 ++++++++++ cypress/component/DataViewTextFilter.cy.tsx | 75 +++++++ .../DataViewFilters/DataViewFilters.test.tsx | 21 ++ .../DataViewFilters.test.tsx.snap | 182 ++++++++++++++++ .../DataViewTextFilter.test.tsx | 24 +++ .../DataViewTextFilter.test.tsx.snap | 196 ++++++++++++++++++ .../DataViewToolbar.test.tsx.snap | 14 ++ packages/module/src/Hooks/filters.test.tsx | 62 ++++++ 8 files changed, 685 insertions(+) create mode 100644 cypress/component/DataViewFilters.cy.tsx create mode 100644 cypress/component/DataViewTextFilter.cy.tsx create mode 100644 packages/module/src/DataViewFilters/DataViewFilters.test.tsx create mode 100644 packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap create mode 100644 packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx create mode 100644 packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap create mode 100644 packages/module/src/Hooks/filters.test.tsx diff --git a/cypress/component/DataViewFilters.cy.tsx b/cypress/component/DataViewFilters.cy.tsx new file mode 100644 index 0000000..cd6d058 --- /dev/null +++ b/cypress/component/DataViewFilters.cy.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters'; +import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter'; +import { DataViewToolbar } from '@patternfly/react-data-view/dist/esm/DataViewToolbar'; +import { FilterIcon } from '@patternfly/react-icons'; + +const filtersProps = { + ouiaId: 'DataViewFilters', + toggleIcon: , + values: { name: '', branch: '' } +}; + +interface RepositoryFilters { + name: string, + branch: string +}; + +const DataViewToolbarWithState = (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any + const { filters, onSetFilters, clearAllFilters } = useDataViewFilters({ initialFilters: { name: '', branch: '' } }); + + return ( + onSetFilters(values)} values={filters} {...props}> + + + + } + /> + ); +}; + +describe('DataViewFilters', () => { + it('renders DataViewFilters with menu and filter items', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewFilters"]').should('exist'); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + + cy.contains('Name').should('exist'); + cy.contains('Branch').should('exist'); + }); + + it('can select a filter option', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Name'); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Branch').click(); + + cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Branch'); + }); + + it('responds to input and clears the filters', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Name').click(); + + cy.get('input[placeholder="Filter by name"]').type('Repository one'); + cy.get('.pf-v5-c-chip__text').should('have.length', 1); + cy.get('input[placeholder="Filter by name"]').clear(); + cy.get('.pf-v5-c-chip__text').should('have.length', 0); + }); + + it('displays chips for selected filters', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Name').click(); + cy.get('input[placeholder="Filter by name"]').type('Repository one'); + + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Branch').click(); + cy.get('input[placeholder="Filter by branch"]').type('Main branch'); + + cy.get('.pf-v5-c-chip__text').should('have.length', 2); + cy.get('.pf-v5-c-chip__text').eq(0).should('contain.text', 'Repository one'); + cy.get('.pf-v5-c-chip__text').eq(1).should('contain.text', 'Main branch'); + }); + + it('removes filters by clicking individual chips', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Name').click(); + cy.get('input[placeholder="Filter by name"]').type('Repository one'); + + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Branch').click(); + cy.get('input[placeholder="Filter by branch"]').type('Main branch'); + + cy.get('[data-ouia-component-id="close"]').should('have.length', 2); + + cy.get('[data-ouia-component-id="close"]').first().click(); + cy.get('[data-ouia-component-id="close"]').last().click(); + + cy.get('[data-ouia-component-id="close"]').should('have.length', 0); + }); + + it('clears all filters using the clear-all button', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Name').click(); + cy.get('input[placeholder="Filter by name"]').type('Repository one'); + + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.contains('Branch').click(); + cy.get('input[placeholder="Filter by branch"]').type('Main branch'); + + cy.get('[data-ouia-component-id="FiltersExampleHeader-clear-all-filters"]').should('exist').click(); + }); +}); diff --git a/cypress/component/DataViewTextFilter.cy.tsx b/cypress/component/DataViewTextFilter.cy.tsx new file mode 100644 index 0000000..8d67ca4 --- /dev/null +++ b/cypress/component/DataViewTextFilter.cy.tsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; +import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter'; +import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; + +const defaultProps = { + filterId: 'name', + title: 'Name', + value: '', + ouiaId: 'DataViewTextFilter', + placeholder: 'Filter by name' +}; + +const DataViewToolbarWithState = (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any + const [ value, setValue ] = useState('Repository one'); + + return ( + setValue('')}> + setValue('')} {...props} /> + + ); +}; + +describe('DataViewTextFilter', () => { + + it('renders DataViewTextFilter with correct initial values', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('exist'); + cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input') + .should('have.attr', 'placeholder', 'Filter by name') + .and('have.value', ''); + }); + + it('accepts input when passed', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input') + .type('Repository one') + .should('have.value', 'Repository one'); + }); + + it('displays a chip when value is present and removes it on delete', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', 'Repository one'); + + cy.get('.pf-v5-c-chip__text').contains('Repository one'); + cy.get('.pf-m-chip-group button.pf-v5-c-button.pf-m-plain').click(); + + cy.get('.pf-v5-c-chip__text').should('not.exist'); + cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', ''); + }); + + it('clears input when the clear button is clicked', () => { + cy.mount(); + cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', 'Repository one'); + + cy.get('[data-ouia-component-id="DataViewToolbar-clear-all-filters"]').click(); + + cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', ''); + }); + + it('hides or shows the toolbar item based on showToolbarItem prop', () => { + cy.mount( + + + + ); + cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('not.exist'); + + cy.mount( + + + + ); + cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('exist'); + }); +}); diff --git a/packages/module/src/DataViewFilters/DataViewFilters.test.tsx b/packages/module/src/DataViewFilters/DataViewFilters.test.tsx new file mode 100644 index 0000000..9f9cb5a --- /dev/null +++ b/packages/module/src/DataViewFilters/DataViewFilters.test.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import DataViewFilters from './DataViewFilters'; +import DataViewToolbar from '../DataViewToolbar'; +import DataViewTextFilter from '../DataViewTextFilter'; + +describe('DataViewFilters component', () => { + const mockOnChange = jest.fn(); + + it('should render correctly', () => { + const { container } = render( + + + + } + />); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap b/packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap new file mode 100644 index 0000000..b8861d0 --- /dev/null +++ b/packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap @@ -0,0 +1,182 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DataViewFilters component should render correctly 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+ + + + + + +
+
+
+
+
+
+
+
+ +
+`; diff --git a/packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx b/packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx new file mode 100644 index 0000000..5630735 --- /dev/null +++ b/packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import DataViewTextFilter, { DataViewTextFilterProps } from './DataViewTextFilter'; +import DataViewToolbar from '../DataViewToolbar'; + +describe('DataViewTextFilter component', () => { + const mockOnChange = jest.fn(); + + const defaultProps: DataViewTextFilterProps = { + filterId: 'test-filter', + title: 'Test Filter', + value: 'initial value', + onChange: mockOnChange, + }; + + it('should render correctly', () => { + const { container } = render( + } + />); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap b/packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap new file mode 100644 index 0000000..2091670 --- /dev/null +++ b/packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap @@ -0,0 +1,196 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DataViewTextFilter component should render correctly 1`] = ` +
+
+
+
+
+
+
+
+ + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + Test Filter + +
    +
  • +
    + + + initial value + + + + + +
    +
  • +
+
+
+
+
+
+ +
+
+
+
+`; diff --git a/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap b/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap index 0573355..deb8cc9 100644 --- a/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +++ b/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap @@ -526,6 +526,20 @@ exports[`DataViewToolbar component should render correctly 1`] = `
+
+ +
, diff --git a/packages/module/src/Hooks/filters.test.tsx b/packages/module/src/Hooks/filters.test.tsx new file mode 100644 index 0000000..d3ea7a8 --- /dev/null +++ b/packages/module/src/Hooks/filters.test.tsx @@ -0,0 +1,62 @@ +import '@testing-library/jest-dom'; +import { renderHook, act } from '@testing-library/react'; +import { useDataViewFilters, UseDataViewFiltersProps } from './filters'; + +describe('useDataViewFilters', () => { + const initialFilters = { search: 'test', tags: [ 'tag1', 'tag2' ] }; + + it('should initialize with provided initial filters', () => { + const { result } = renderHook(() => useDataViewFilters({ initialFilters })); + expect(result.current.filters).toEqual(initialFilters); + }); + + it('should initialize with empty filters if no initialFilters provided', () => { + const { result } = renderHook(() => useDataViewFilters({})); + expect(result.current.filters).toEqual({}); + }); + + it('should set filters correctly', () => { + const { result } = renderHook(() => useDataViewFilters({ initialFilters })); + const newFilters = { search: 'new search' }; + act(() => result.current.onSetFilters(newFilters)); + + expect(result.current.filters).toEqual({ ...initialFilters, ...newFilters }); + }); + + it('should delete specific filters without removing keys', () => { + const { result } = renderHook(() => useDataViewFilters({ initialFilters })); + const filtersToDelete = { search: 'test' }; + act(() => result.current.onDeleteFilters(filtersToDelete)); + + expect(result.current.filters).toEqual({ search: '', tags: [ 'tag1', 'tag2' ] }); + }); + + it('should clear all filters', () => { + const { result } = renderHook(() => useDataViewFilters({ initialFilters })); + act(() => result.current.clearAllFilters()); + + expect(result.current.filters).toEqual({ search: '', tags: [] }); + }); + + it('should sync with URL search params if isUrlSyncEnabled', () => { + const searchParams = new URLSearchParams(); + const setSearchParams = jest.fn(); + const props: UseDataViewFiltersProps = { + initialFilters, + searchParams, + setSearchParams, + }; + + const { result } = renderHook(() => useDataViewFilters(props)); + act(() => result.current.onSetFilters({ search: 'new search' })); + + expect(setSearchParams).toHaveBeenCalled(); + }); + + it('should reset filters to default values when clearAllFilters is called', () => { + const { result } = renderHook(() => useDataViewFilters({ initialFilters })); + act(() => result.current.clearAllFilters()); + + expect(result.current.filters).toEqual({ search: '', tags: [] }); + }); +}); From bdd75fcc619d9739bad0bec45e320818cc3eab39 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Tue, 5 Nov 2024 00:31:01 +0100 Subject: [PATCH 09/23] feat(filters): Document DataView filters --- .../examples/Components/Components.md | 4 +- .../examples/Functionality/FiltersExample.tsx | 92 +++++++++++++++++++ .../examples/Functionality/Functionality.md | 37 +++++++- 3 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md index ab240ed..628ecd3 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md @@ -11,7 +11,7 @@ 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: 4 -propComponents: ['DataViewToolbar', 'DataViewTableBasic', 'DataViewTableTree'] +propComponents: ['DataViewToolbar', 'DataViewTableBasic', 'DataViewTableTree', 'DataViewTrTree', 'DataViewTrObject'] sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/Components.md --- import { Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter } from '@patternfly/react-core'; @@ -26,7 +26,7 @@ import { DataView, DataViewState } from '@patternfly/react-data-view/dist/dynami The **data view toolbar** component renders a default opinionated data view toolbar above or below the data section. -Data view toolbar can contain a `pagination`, `bulkSelect`, `actions` or other children content passed. The preffered way of passing children toolbar items is using the [toolbar item](/components/toolbar#toolbar-items) component. +Data view toolbar can contain a `pagination`, `bulkSelect`, `filters`, `actions` or other children content passed. The preffered way of passing children toolbar items is using the [toolbar item](/components/toolbar#toolbar-items) component. ### Basic toolbar example diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx new file mode 100644 index 0000000..46cec1e --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx @@ -0,0 +1,92 @@ +import React, { useMemo } from 'react'; +import { Pagination } from '@patternfly/react-core'; +import { BrowserRouter, useSearchParams } from 'react-router-dom'; +import { useDataViewFilters, useDataViewPagination } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; +import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; +import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters'; +import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter'; + +const perPageOptions = [ + { title: '5', value: 5 }, + { title: '10', value: 10 } +]; + +interface Repository { + name: string; + branch: string | null; + prs: string | null; + workspaces: string; + lastCommit: string; +} + +interface RepositoryFilters { + name: string, + branch: string +} + +const repositories: Repository[] = [ + { name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' }, + { name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' }, + { name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' }, + { name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' }, + { name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' }, + { name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' } +]; + +const columns = [ 'Name', 'Branch', 'Pull requests', 'Workspaces', 'Last commit' ]; + +const ouiaId = 'LayoutExample'; + +const MyTable: React.FunctionComponent = () => { + const [ searchParams, setSearchParams ] = useSearchParams(); + const pagination = useDataViewPagination({ perPage: 5 }); + const { page, perPage } = pagination; + const { filters, onSetFilters, clearAllFilters } = useDataViewFilters({ initialFilters: { name: '', branch: '' }, searchParams, setSearchParams }); + + const pageRows = useMemo(() => repositories + .filter(item => (!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) && (!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase()))) + .slice((page - 1) * perPage, ((page - 1) * perPage) + perPage) + .map(item => Object.values(item)), [ page, perPage, filters ]); + + return ( + + + } + filters={ + onSetFilters(values)} values={filters}> + + + + } + /> + + + } + /> + + ); +} + +export const BasicExample: React.FunctionComponent = () => ( + + + +) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md index c788309..1ec4901 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md @@ -11,16 +11,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: ['DataViewFilters', 'DataViewTextFilter'] sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md --- import { useMemo } from 'react'; import { BrowserRouter, useSearchParams } from 'react-router-dom'; -import { useDataViewPagination, useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { useDataViewPagination, useDataViewSelection, useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks'; import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect'; import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; - +import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters'; +import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter'; This is a list of functionality you can use to manage data displayed in the **data view**. @@ -83,3 +85,34 @@ The `useDataViewSelection` hook manages the selection state of the data view. ```js file="./SelectionExample.tsx" ``` + +# Filters +Enables filtering of data records in the data view and displays the applied filter chips. + +### Toolbar usage +The data view toolbar can include a set of filters by passing a React node to the `filters` property. You can use predefined components `DataViewFilters` and `DataViewTextFilter` to customize and handle filtering directly in the toolbar. The `DataViewFilters` is a wrapper allowing conditional filtering using multiple attributes. If you need just a single filter, you can use `DataViewTextFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page. + +You can decide between passing `value` and `onChange` event to every filter separately or pass `values` and `onChange` to the `DataViewFilters` wrapper which make them available to its children. Props directly passed to child filters have a higher priority than the "inherited" ones. + +### Filters state + +The `useDataViewFilters` hook manages the filter state of the data view. It allows you to define default filter values, synchronize filter state with URL parameters, and handle filter changes efficiently. + +**Initial values:** +- `initialFilters` object with default filter values +- optional `searchParams` object for managing URL-based filter state +- optional `setSearchParams` function to update the URL when filters are modified + +The `useDataViewFilters` hook works well with the React Router library to support URL-based filtering. Alternatively, you can manage filter state in the URL using `URLSearchParams` and `window.history.pushState` APIs, or other routing libraries. If no URL parameters are provided, the filter state is managed internally. + +**Return values:** +- `filters` object representing the current filter values +- `onSetFilters` function to update the filter state +- `clearAllFilters` function to reset all filters to their initial values + +### Filtering example +This example demonstrates the setup and usage of filters within the data view. It includes text filters for different attributes, the ability to clear all filters, and persistence of filter state in the URL. + +```js file="./FiltersExample.tsx" + +``` From bd99c199e8d2965fef7d0eb1e783a25e9834a7fc Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Tue, 5 Nov 2024 16:56:02 +0100 Subject: [PATCH 10/23] fix(filters): Support changing filters dynamically --- packages/module/src/DataViewFilters/DataViewFilters.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/module/src/DataViewFilters/DataViewFilters.tsx b/packages/module/src/DataViewFilters/DataViewFilters.tsx index 1dc9958..29e950e 100644 --- a/packages/module/src/DataViewFilters/DataViewFilters.tsx +++ b/packages/module/src/DataViewFilters/DataViewFilters.tsx @@ -42,10 +42,16 @@ export const DataViewFilters = ({ const attributeMenuRef = useRef(null); const attributeContainerRef = useRef(null); + const childrenHash = useMemo(() => JSON.stringify( + React.Children.map(children, (child) => + React.isValidElement(child) ? { type: child.type, key: child.key, props: child.props } : child + ) + ), [ children ]); + const filterItems: DataViewFilterIdentifiers[] = useMemo(() => React.Children.toArray(children) .map(child => React.isValidElement(child) ? { filterId: String(child.props.filterId), title: String(child.props.title) } : undefined - ).filter((item): item is DataViewFilterIdentifiers => !!item), []); // eslint-disable-line react-hooks/exhaustive-deps + ).filter((item): item is DataViewFilterIdentifiers => !!item), [ childrenHash ]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title); From 2b4e9fca0fa3e754d91bae13750de9f053a91020 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Fri, 8 Nov 2024 20:34:24 +0100 Subject: [PATCH 11/23] fix: fix tree table data selection --- .../DataViewTableTree/DataViewTableTree.tsx | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/module/src/DataViewTableTree/DataViewTableTree.tsx b/packages/module/src/DataViewTableTree/DataViewTableTree.tsx index a743295..d4aaab8 100644 --- a/packages/module/src/DataViewTableTree/DataViewTableTree.tsx +++ b/packages/module/src/DataViewTableTree/DataViewTableTree.tsx @@ -12,24 +12,45 @@ import { DataViewTableHead } from '../DataViewTableHead'; import { DataViewTh, DataViewTrTree, isDataViewTdObject } from '../DataViewTable'; import { DataViewState } from '../DataView/DataView'; -const getDescendants = (node: DataViewTrTree): DataViewTrTree[] => (!node.children || !node.children.length) ? [ node ] : node.children.flatMap(getDescendants); - -const isNodeChecked = (node: DataViewTrTree, isSelected: (node: DataViewTrTree) => boolean) => { - let allSelected = true; - let someSelected = false; - - for (const descendant of getDescendants(node)) { - const selected = !!isSelected?.(descendant); - - someSelected ||= selected; - allSelected &&= selected; - - if (!allSelected && someSelected) { return null } - } - - return allSelected; +const getNodesAffectedBySelection = ( + allRows: DataViewTrTree[], + node: DataViewTrTree, + isChecking: boolean, + isSelected?: (item: DataViewTrTree) => boolean +): DataViewTrTree[] => { + + const getDescendants = (node: DataViewTrTree): DataViewTrTree[] => + node.children ? node.children.flatMap(getDescendants).concat(node) : [ node ]; + + const findParent = (child: DataViewTrTree, rows: DataViewTrTree[]): DataViewTrTree | undefined => + rows.find(row => row.children?.some(c => c === child)) ?? + rows.flatMap(row => row.children ?? []).map(c => findParent(child, [ c ])).find(p => p); + + const getAncestors = (node: DataViewTrTree): DataViewTrTree[] => { + const ancestors: DataViewTrTree[] = []; + let parent = findParent(node, allRows); + while (parent) { + ancestors.push(parent); + parent = findParent(parent, allRows); + } + return ancestors; + }; + + const affectedNodes = new Set([ node, ...getDescendants(node) ]); + + getAncestors(node).forEach(ancestor => { + const allChildrenSelected = ancestor.children?.every(child => isSelected?.(child) || affectedNodes.has(child)); + const anyChildAffected = ancestor.children?.some(child => affectedNodes.has(child) || child.id === node.id); + + if (isChecking ? !isSelected?.(ancestor) && allChildrenSelected : isSelected?.(ancestor) && anyChildAffected) { + affectedNodes.add(ancestor); + } + }); + + return Array.from(affectedNodes); }; + /** extends TableProps */ export interface DataViewTableTreeProps extends Omit { /** Columns definition */ @@ -83,7 +104,7 @@ export const DataViewTableTree: React.FC = ({ } const isExpanded = expandedNodeIds.includes(node.id); const isDetailsExpanded = expandedDetailsNodeNames.includes(node.id); - const isChecked = isSelected && isNodeChecked(node, isSelected); + const isChecked = isSelected?.(node); let icon = leafIcon; if (node.children) { icon = isExpanded ? expandedIcon : collapsedIcon; @@ -100,7 +121,7 @@ export const DataViewTableTree: React.FC = ({ const otherDetailsExpandedNodeIds = prevDetailsExpanded.filter(id => id !== node.id); return isDetailsExpanded ? otherDetailsExpandedNodeIds : [ ...otherDetailsExpandedNodeIds, node.id ]; }), - onCheckChange: (isSelectDisabled?.(node) || !onSelect) ? undefined : (_event, isChecking) => onSelect?.(isChecking, getDescendants(node)), + onCheckChange: (isSelectDisabled?.(node) || !onSelect) ? undefined : (_event, isChecking) => onSelect?.(isChecking, getNodesAffectedBySelection(rows, node, isChecking, isSelected)), rowIndex, props: { isExpanded, From f83558beea1ddcc7cf018813bc51aeef0d4b4a37 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Sat, 16 Nov 2024 21:51:09 +0100 Subject: [PATCH 12/23] fix: update hook for filters --- packages/module/src/Hooks/filters.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/module/src/Hooks/filters.ts b/packages/module/src/Hooks/filters.ts index 4f81e17..177e5f3 100644 --- a/packages/module/src/Hooks/filters.ts +++ b/packages/module/src/Hooks/filters.ts @@ -16,15 +16,19 @@ export const useDataViewFilters = ({ }: UseDataViewFiltersProps) => { const isUrlSyncEnabled = useMemo(() => searchParams && !!setSearchParams, [ searchParams, setSearchParams ]); - const getInitialFilters = useCallback((): T => isUrlSyncEnabled ? Object.keys(initialFilters).reduce((loadedFilters, key) => { - const urlValue = searchParams?.get(key); - loadedFilters[key as keyof T] = urlValue - ? (urlValue as T[keyof T] | T[keyof T]) - : initialFilters[key as keyof T]; - return loadedFilters; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, { ...initialFilters }) : initialFilters, [ isUrlSyncEnabled, JSON.stringify(initialFilters), searchParams?.toString() ]); + const getInitialFilters = useCallback((): T => isUrlSyncEnabled + ? Object.keys(initialFilters).reduce((loadedFilters, key) => { + const urlValue = searchParams?.get(key); + const isArrayFilter = Array.isArray(initialFilters[key]); + // eslint-disable-next-line no-nested-ternary + loadedFilters[key] = urlValue + ? (isArrayFilter && !Array.isArray(urlValue) ? [ urlValue ] : urlValue) + : initialFilters[key]; + + return loadedFilters; + }, { ...initialFilters }) + : initialFilters, [ isUrlSyncEnabled, initialFilters, searchParams ]); const [ filters, setFilters ] = useState(getInitialFilters()); const updateSearchParams = useCallback( @@ -32,11 +36,8 @@ export const useDataViewFilters = ({ if (isUrlSyncEnabled) { const params = new URLSearchParams(searchParams); Object.entries(newFilters).forEach(([ key, value ]) => { - if (value) { - params.set(key, Array.isArray(value) ? value.join(',') : value); - } else { - params.delete(key); - } + params.delete(key); + (Array.isArray(value) ? value : [ value ]).forEach((val) => value && params.append(key, val)); }); setSearchParams?.(params); } From db235556a58a52165e8caa920629ccdf6debc8fb Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Sat, 16 Nov 2024 21:51:51 +0100 Subject: [PATCH 13/23] fix: close filter dropdown after click outside --- .../src/DataViewFilters/DataViewFilters.tsx | 33 +++++++++++++++---- .../DataViewTextFilter/DataViewTextFilter.tsx | 1 + 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/module/src/DataViewFilters/DataViewFilters.tsx b/packages/module/src/DataViewFilters/DataViewFilters.tsx index 29e950e..addc63f 100644 --- a/packages/module/src/DataViewFilters/DataViewFilters.tsx +++ b/packages/module/src/DataViewFilters/DataViewFilters.tsx @@ -1,9 +1,16 @@ -import React, { useMemo, useState, useRef, useEffect, ReactElement } from 'react'; +import React, { useMemo, useState, useRef, useEffect, ReactElement, ReactNode } from 'react'; import { Menu, MenuContent, MenuItem, MenuList, MenuToggle, Popper, ToolbarGroup, ToolbarToggleGroup, ToolbarToggleGroupProps, } from '@patternfly/react-core'; import { FilterIcon } from '@patternfly/react-icons'; +export interface DataViewFilterOption { + /** Filter option label */ + label: ReactNode; + /** Filter option value */ + value: string; +} + // helper interface to generate attribute menu interface DataViewFilterIdentifiers { filterId: string; @@ -57,6 +64,19 @@ export const DataViewFilters = ({ filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title); }, [ filterItems ]); + const handleClickOutside = (event: MouseEvent) => + isAttributeMenuOpen && + !attributeMenuRef.current?.contains(event.target as Node) && + !attributeToggleRef.current?.contains(event.target as Node) + && setIsAttributeMenuOpen(false); + + useEffect(() => { + window.addEventListener('click', handleClickOutside); + return () => { + window.removeEventListener('click', handleClickOutside); + }; + }, [ isAttributeMenuOpen ]); // eslint-disable-line react-hooks/exhaustive-deps + const attributeToggle = ( ({ isVisible={isAttributeMenuOpen} /> - {React.Children.map(children, (child) => ( - React.isValidElement(child) ? ( - React.cloneElement(child as ReactElement<{ + {React.Children.map(children, (child) => + React.isValidElement(child) + ? React.cloneElement(child as ReactElement<{ showToolbarItem: boolean; onChange: (_e: unknown, values: unknown) => void; value: unknown; @@ -114,9 +134,8 @@ export const DataViewFilters = ({ value: values?.[child.props.filterId], ...child.props }) - ) : child - ))} - + : child + )} ); diff --git a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx index e2d1e43..ce49a3d 100644 --- a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx +++ b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx @@ -31,6 +31,7 @@ export const DataViewTextFilter: React.FC = ({ ...props }: DataViewTextFilterProps) => ( 0 ? [ { key: title, node: value } ] : []} deleteChip={() => onChange?.(undefined, '')} From 99a2f5c39ef3a74303225cec08c5126e95c21cc0 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Sat, 16 Nov 2024 21:52:11 +0100 Subject: [PATCH 14/23] feat: add checkbox filter --- .../examples/Functionality/FiltersExample.tsx | 45 +++-- .../DataViewCheckboxFilter.tsx | 175 ++++++++++++++++++ .../src/DataViewCheckboxFilter/index.ts | 2 + 3 files changed, 207 insertions(+), 15 deletions(-) create mode 100644 packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx create mode 100644 packages/module/src/DataViewCheckboxFilter/index.ts diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx index 46cec1e..14497a6 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx @@ -5,8 +5,9 @@ import { useDataViewFilters, useDataViewPagination } from '@patternfly/react-dat import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; -import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters'; +import { DataViewFilterOption, DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters'; import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter'; +import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter'; const perPageOptions = [ { title: '5', value: 5 }, @@ -17,38 +18,51 @@ interface Repository { name: string; branch: string | null; prs: string | null; - workspaces: string; + workspace: string; lastCommit: string; } interface RepositoryFilters { name: string, - branch: string + branch: string, + workspace: string[] } const repositories: Repository[] = [ - { name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' }, - { name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' }, - { name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' }, - { name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' }, - { name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' }, - { name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' } + { name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspace: 'Workspace one', lastCommit: 'Timestamp one' }, + { name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspace: 'Workspace two', lastCommit: 'Timestamp two' }, + { name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspace: 'Workspace one', lastCommit: 'Timestamp three' }, + { name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspace: 'Workspace one', lastCommit: 'Timestamp four' }, + { name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspace: 'Workspace two', lastCommit: 'Timestamp five' }, + { name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspace: 'Workspace three', lastCommit: 'Timestamp six' } ]; -const columns = [ 'Name', 'Branch', 'Pull requests', 'Workspaces', 'Last commit' ]; +const filterOptions: DataViewFilterOption[] = [ + { label: 'Workspace one', value: 'workspace-one' }, + { label: 'Workspace two', value: 'workspace-two' }, + { label: 'Workspace three', value: 'workspace-three' } +]; + +const columns = [ 'Name', 'Branch', 'Pull requests', 'Workspace', 'Last commit' ]; const ouiaId = 'LayoutExample'; const MyTable: React.FunctionComponent = () => { const [ searchParams, setSearchParams ] = useSearchParams(); + const { filters, onSetFilters, clearAllFilters } = useDataViewFilters({ initialFilters: { name: '', branch: '', workspace: [] }, searchParams, setSearchParams }); const pagination = useDataViewPagination({ perPage: 5 }); const { page, perPage } = pagination; - const { filters, onSetFilters, clearAllFilters } = useDataViewFilters({ initialFilters: { name: '', branch: '' }, searchParams, setSearchParams }); - const pageRows = useMemo(() => repositories - .filter(item => (!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) && (!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase()))) + const filteredData = useMemo(() => repositories.filter(item => + (!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) && + (!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase())) && + (!filters.workspace || filters.workspace.length === 0 || filters.workspace.includes(String(filterOptions.find(option => option.label === item.workspace)?.value))) + ), [ filters ]); + + const pageRows = useMemo(() => filteredData .slice((page - 1) * perPage, ((page - 1) * perPage) + perPage) - .map(item => Object.values(item)), [ page, perPage, filters ]); + .map(item => Object.values(item)), + [ page, perPage, filteredData ]); return ( @@ -66,6 +80,7 @@ const MyTable: React.FunctionComponent = () => { onSetFilters(values)} values={filters}> + } /> @@ -76,7 +91,7 @@ const MyTable: React.FunctionComponent = () => { } diff --git a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx new file mode 100644 index 0000000..7235165 --- /dev/null +++ b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { + Badge, + Menu, + MenuContent, + MenuItem, + MenuList, + MenuProps, + MenuToggle, + Popper, + ToolbarChip, + ToolbarFilter, +} from '@patternfly/react-core'; +import { FilterIcon } from '@patternfly/react-icons'; +import { DataViewFilterOption } from '../DataViewFilters'; + +const isToolbarChip = (chip: string | ToolbarChip): chip is ToolbarChip => + typeof chip === 'object' && 'key' in chip; + +export const isDataViewFilterOption = (obj: unknown): obj is DataViewFilterOption => + !!obj && + typeof obj === 'object' && + 'label' in obj && + 'value' in obj && + typeof (obj as DataViewFilterOption).value === 'string'; + +/** extends MenuProps */ +export interface DataViewCheckboxFilterProps extends Omit { + /** Unique key for the filter attribute */ + filterId: string; + /** Array of current filter values */ + value?: string[]; + /** Filter title displayed in the toolbar */ + title: string; + /** Placeholder text of the menu */ + placeholder?: string; + /** Filter options displayed */ + options: (DataViewFilterOption | string)[]; + /** Callback for updating when item selection changes. */ + onChange?: (event?: React.MouseEvent, values?: string[]) => void; + /** Controls visibility of the filter in the toolbar */ + showToolbarItem?: boolean; + /** Controls visibility of the filter icon */ + showIcon?: boolean; + /** Controls visibility of the selected items badge */ + showBadge?: boolean; + /** Custom OUIA ID */ + ouiaId?: string; +} + +export const DataViewCheckboxFilter: React.FC = ({ + filterId, + title, + value = [], + onChange, + placeholder, + options = [], + showToolbarItem, + showIcon = !placeholder, + showBadge = !placeholder, + ouiaId = 'DataViewCheckboxFilter', + ...props +}: DataViewCheckboxFilterProps) => { + const [ isOpen, setIsOpen ] = React.useState(false); + const toggleRef = React.useRef(null); + const menuRef = React.useRef(null); + const containerRef = React.useRef(null); + + const normalizeOptions = React.useMemo( + () => + options.map(option => + typeof option === 'string' + ? { label: option, value: option } + : option + ), + [ options ] + ); + + const handleToggleClick = (event: React.MouseEvent) => { + event.stopPropagation(); + setTimeout(() => { + const firstElement = menuRef.current?.querySelector('li > button:not(:disabled)') as HTMLElement; + firstElement?.focus(); + }, 0); + setIsOpen(prev => !prev); + }; + + const handleSelect = (event?: React.MouseEvent, itemId?: string | number) => { + const activeItem = String(itemId); + const isSelected = value.includes(activeItem); + + onChange?.( + event, + isSelected ? value.filter(item => item !== activeItem) : [ activeItem, ...value ] + ); + }; + + const handleClickOutside = (event: MouseEvent) => + isOpen && + menuRef.current && toggleRef.current && + !menuRef.current.contains(event.target as Node) && !toggleRef.current.contains(event.target as Node) + && setIsOpen(false); + + + React.useEffect(() => { + window.addEventListener('click', handleClickOutside); + return () => { + window.removeEventListener('click', handleClickOutside); + }; + }, [ isOpen ]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + { + const activeOption = normalizeOptions.find(option => option.value === item); + return ({ key: activeOption?.value as string, node: activeOption?.label }) + })} + deleteChip={(_, chip) => + onChange?.(undefined, value.filter(item => item !== (isToolbarChip(chip) ? chip.key : chip))) + } + categoryName={title} + showToolbarItem={showToolbarItem} + > + : undefined} + badge={value.length > 0 && showBadge ? {value.length} : undefined} + style={{ width: '200px' }} + > + {placeholder ?? title} + + } + triggerRef={toggleRef} + popper={ + + + + {normalizeOptions.map(option => ( + + {option.label} + + ))} + + + + } + popperRef={menuRef} + appendTo={containerRef.current || undefined} + aria-label={`${title ?? filterId} filter`} + isVisible={isOpen} + /> + + ); +}; + +export default DataViewCheckboxFilter; diff --git a/packages/module/src/DataViewCheckboxFilter/index.ts b/packages/module/src/DataViewCheckboxFilter/index.ts new file mode 100644 index 0000000..218b16f --- /dev/null +++ b/packages/module/src/DataViewCheckboxFilter/index.ts @@ -0,0 +1,2 @@ +export { default } from './DataViewCheckboxFilter'; +export * from './DataViewCheckboxFilter'; From c19beddd9a6655db67d87c285f02cfb9818d8358 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Sat, 16 Nov 2024 21:52:44 +0100 Subject: [PATCH 15/23] feat: enhance documentation with checkbox filter --- .../data-view/examples/Functionality/Functionality.md | 7 ++++--- packages/module/src/index.ts | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md index 1ec4901..05dcebd 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md @@ -11,7 +11,7 @@ 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: ['DataViewFilters', 'DataViewTextFilter'] +propComponents: ['DataViewFilters', 'DataViewTextFilter', 'DataViewCheckboxFilter'] sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md --- import { useMemo } from 'react'; @@ -23,6 +23,7 @@ import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataVi import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters'; import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter'; +import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter'; This is a list of functionality you can use to manage data displayed in the **data view**. @@ -90,7 +91,7 @@ The `useDataViewSelection` hook manages the selection state of the data view. Enables filtering of data records in the data view and displays the applied filter chips. ### Toolbar usage -The data view toolbar can include a set of filters by passing a React node to the `filters` property. You can use predefined components `DataViewFilters` and `DataViewTextFilter` to customize and handle filtering directly in the toolbar. The `DataViewFilters` is a wrapper allowing conditional filtering using multiple attributes. If you need just a single filter, you can use `DataViewTextFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page. +The data view toolbar can include a set of filters by passing a React node to the `filters` property. You can use predefined components `DataViewFilters`, `DataViewTextFilter` and `DataViewCheckboxFilter` to customize and handle filtering directly in the toolbar. The `DataViewFilters` is a wrapper allowing conditional filtering using multiple attributes. If you need just a single filter, you can use `DataViewTextFilter`, `DataViewCheckboxFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page. You can decide between passing `value` and `onChange` event to every filter separately or pass `values` and `onChange` to the `DataViewFilters` wrapper which make them available to its children. Props directly passed to child filters have a higher priority than the "inherited" ones. @@ -99,7 +100,7 @@ You can decide between passing `value` and `onChange` event to every filter sepa The `useDataViewFilters` hook manages the filter state of the data view. It allows you to define default filter values, synchronize filter state with URL parameters, and handle filter changes efficiently. **Initial values:** -- `initialFilters` object with default filter values +- `initialFilters` object with default filter values (if the filter param allows multiple values, pass an array) - optional `searchParams` object for managing URL-based filter state - optional `setSearchParams` function to update the URL when filters are modified diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index a34586c..aca7f76 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -25,5 +25,8 @@ export * from './DataViewTable'; export { default as DataViewEventsContext } from './DataViewEventsContext'; export * from './DataViewEventsContext'; +export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter'; +export * from './DataViewCheckboxFilter'; + export { default as DataView } from './DataView'; export * from './DataView'; From 9e73530fb8efc49499754f803192946b5ff79157 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Sat, 16 Nov 2024 21:53:02 +0100 Subject: [PATCH 16/23] feat: add tests for checkbox filter --- .../component/DataViewCheckboxFilter.cy.tsx | 66 ++++++ .../DataViewCheckboxFilter.test.tsx | 24 +++ .../DataViewCheckboxFilter.test.tsx.snap | 194 ++++++++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 cypress/component/DataViewCheckboxFilter.cy.tsx create mode 100644 packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx create mode 100644 packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap diff --git a/cypress/component/DataViewCheckboxFilter.cy.tsx b/cypress/component/DataViewCheckboxFilter.cy.tsx new file mode 100644 index 0000000..eab26fb --- /dev/null +++ b/cypress/component/DataViewCheckboxFilter.cy.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { DataViewCheckboxFilter, DataViewCheckboxFilterProps } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter'; +import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; + +describe('DataViewCheckboxFilter component', () => { + const defaultProps: DataViewCheckboxFilterProps = { + filterId: 'test-checkbox-filter', + title: 'Test checkbox filter', + value: [ 'workspace-one' ], + options: [ + { label: 'Workspace one', value: 'workspace-one' }, + { label: 'Workspace two', value: 'workspace-two' }, + { label: 'Workspace three', value: 'workspace-three' }, + ], + }; + + it('renders a checkbox filter with options', () => { + const onChange = cy.stub().as('onChange'); + + cy.mount( + } /> + ); + + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]') + .contains('Test checkbox filter') + .should('be.visible'); + + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]') + .should('exist') + .contains('1'); + + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').click(); + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]').should('be.visible'); + + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]') + .find('li') + .should('have.length', 3) + .first() + .contains('Workspace one'); + + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]') + .find('li') + .first() + .find('input[type="checkbox"]') + .should('be.checked'); + + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]') + .find('li') + .eq(1) + .find('input[type="checkbox"]') + .click(); + + cy.get('@onChange').should('have.been.calledWith', Cypress.sinon.match.object, [ 'workspace-two', 'workspace-one' ]); + }); + + it('renders a checkbox filter with no options selected', () => { + const emptyProps = { ...defaultProps, value: [] }; + + cy.mount( + } /> + ); + + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').contains('Test checkbox filter'); + cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]').should('not.exist'); + }); +}); diff --git a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx new file mode 100644 index 0000000..2290651 --- /dev/null +++ b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import DataViewCheckboxFilter, { DataViewCheckboxFilterProps } from './DataViewCheckboxFilter'; +import DataViewToolbar from '../DataViewToolbar'; + +describe('DataViewCheckboxFilter component', () => { + const defaultProps: DataViewCheckboxFilterProps = { + filterId: 'test-checkbox-filter', + title: 'Test Checkbox Filter', + value: [ 'workspace-one' ], + options: [ + { label: 'Workspace one', value: 'workspace-one' }, + { label: 'Workspace two', value: 'workspace-two' }, + { label: 'Workspace three', value: 'workspace-three' }, + ], + }; + + it('should render correctly', () => { + const { container } = render( + } /> + ); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap b/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap new file mode 100644 index 0000000..e6cede5 --- /dev/null +++ b/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap @@ -0,0 +1,194 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DataViewCheckboxFilter component should render correctly 1`] = ` +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + Test Checkbox Filter + +
    +
  • +
    + + + Workspace one + + + + + +
    +
  • +
+
+
+
+
+
+ +
+
+
+
+`; From 8a31667d57f933b9d507a72b06c3d8d08a5ab890 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Sat, 16 Nov 2024 21:58:30 +0100 Subject: [PATCH 17/23] fix: fix pagination in filtering example --- .../data-view/examples/Functionality/FiltersExample.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx index 14497a6..afdbcac 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx @@ -72,7 +72,7 @@ const MyTable: React.FunctionComponent = () => { pagination={ } From fe4882fa75e1382c79e20d1354a44a183aac34ae Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Wed, 20 Nov 2024 18:06:52 +0100 Subject: [PATCH 18/23] feat: add helper hook for sorting --- packages/module/src/Hooks/index.ts | 1 + packages/module/src/Hooks/sort.ts | 87 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 packages/module/src/Hooks/sort.ts diff --git a/packages/module/src/Hooks/index.ts b/packages/module/src/Hooks/index.ts index 546a0da..3ce609c 100644 --- a/packages/module/src/Hooks/index.ts +++ b/packages/module/src/Hooks/index.ts @@ -1,3 +1,4 @@ export * from './pagination'; export * from './selection'; export * from './filters'; +export * from './sort'; diff --git a/packages/module/src/Hooks/sort.ts b/packages/module/src/Hooks/sort.ts new file mode 100644 index 0000000..ed06f22 --- /dev/null +++ b/packages/module/src/Hooks/sort.ts @@ -0,0 +1,87 @@ +import { ISortBy } from "@patternfly/react-table"; +import { useState, useEffect, useMemo } from "react"; + +export enum DataViewSortParams { + SORT_BY = 'sortBy', + DIRECTION = 'direction' +}; + +const validateDirection = (direction: string | null | undefined, defaultDirection: ISortBy['direction']): ISortBy['direction'] => ( + direction === 'asc' || direction === 'desc' ? direction : defaultDirection +); + +export interface DataViewSortConfig { + /** Attribute to sort the entries by */ + sortBy: string | undefined; + /** Sort direction */ + direction: ISortBy['direction']; +}; + +export interface UseDataViewSortProps { + /** Initial sort config */ + initialSort?: DataViewSortConfig; + /** Current search parameters as a string */ + searchParams?: URLSearchParams; + /** Function to set search parameters */ + setSearchParams?: (params: URLSearchParams) => void; + /** Default direction */ + defaultDirection?: ISortBy['direction']; + /** Sort by URL param name */ + sortByParam?: string; + /** Direction URL param name */ + directionParam?: string; +}; + +export const useDataViewSort = (props?: UseDataViewSortProps) => { + const { + initialSort, + searchParams, + setSearchParams, + defaultDirection = 'asc', + sortByParam = DataViewSortParams.SORT_BY, + directionParam = DataViewSortParams.DIRECTION + } = props ?? {}; + + const isUrlSyncEnabled = useMemo(() => searchParams && !!setSearchParams, [ searchParams, setSearchParams ]); + + const [ state, setState ] = useState({ + sortBy: searchParams?.get(sortByParam) ?? initialSort?.sortBy, + direction: validateDirection(searchParams?.get(directionParam) as ISortBy['direction'], initialSort?.direction), + }); + + const updateSearchParams = (sortBy: string, direction: ISortBy['direction']) => { + if (isUrlSyncEnabled && sortBy) { + const params = new URLSearchParams(searchParams); + params.set(sortByParam, `${sortBy}`); + params.set(directionParam, `${direction}`); + setSearchParams?.(params); + } + }; + + useEffect(() => { + state.sortBy && state.direction && updateSearchParams(state.sortBy, state.direction); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const currentSortBy = searchParams?.get(sortByParam) || state.sortBy; + const currentDirection = searchParams?.get(directionParam) as ISortBy['direction'] || state.direction; + const validDirection = validateDirection(currentDirection, defaultDirection); + currentSortBy !== state.sortBy || validDirection !== state.direction && setState({ sortBy: currentSortBy, direction: validDirection }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ searchParams?.toString() ]); + + const onSort = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent | undefined, + newSortBy: string, + newSortDirection: ISortBy['direction'] + ) => { + setState({ sortBy: newSortBy, direction: newSortDirection }); + updateSearchParams(newSortBy, newSortDirection); + }; + + return { + ...state, + onSort + }; +}; From c6a8cc56e024a71f7c762fc711a7a2e3aed94f5d Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Wed, 20 Nov 2024 18:07:20 +0100 Subject: [PATCH 19/23] feat: add example and docs for sorting hook --- .../examples/Functionality/Functionality.md | 33 ++++++- .../examples/Functionality/SortingExample.tsx | 87 +++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md index 05dcebd..066c0e1 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md @@ -16,7 +16,7 @@ sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/mod --- import { useMemo } from 'react'; import { BrowserRouter, useSearchParams } from 'react-router-dom'; -import { useDataViewPagination, useDataViewSelection, useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { useDataViewPagination, useDataViewSelection, useDataViewFilters, useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks'; import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect'; import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; @@ -117,3 +117,34 @@ This example demonstrates the setup and usage of filters within the data view. I ```js file="./FiltersExample.tsx" ``` + +### Sort state + +The `useDataViewSort` hook manages the sorting state of a data view. It provides an easy way to handle sorting logic, including synchronization with URL parameters and defining 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`). +- Optional `searchParams` object to manage URL-based synchronization of sort state. +- Optional `setSearchParams` function to update the URL parameters when sorting changes. +- `defaultDirection` to set the default direction when no direction is specified. +- 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 to manage 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. + +### Sorting example + +This example demonstrates how to set up and use sorting functionality within a data view. The implementation includes dynamic sorting by column with persistence of sort state in the URL using React Router. + + +```js file="./SortingExample.tsx" + +``` diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx new file mode 100644 index 0000000..879f1cd --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx @@ -0,0 +1,87 @@ +/* eslint-disable no-nested-ternary */ +import React, { useMemo } from 'react'; +import { useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { ThProps } from '@patternfly/react-table'; +import { BrowserRouter, useSearchParams } from 'react-router-dom'; + +interface Repository { + name: string; + branches: string; + prs: string; + workspaces: string; + lastCommit: string; +}; + +const COLUMNS = [ + { label: 'Repository', key: 'name', index: 0 }, + { label: 'Branch', key: 'branches', index: 1 }, + { label: 'Pull request', key: 'prs', index: 2 }, + { label: 'Workspace', key: 'workspaces', index: 3 }, + { label: 'Last commit', key: 'lastCommit', index: 4 } +]; + +const repositories: Repository[] = [ + { name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' }, + { name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' }, + { name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' }, + { name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' }, + { name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' }, + { name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' } +]; + +const sortData = (data: Repository[], sortBy: string | undefined, direction: 'asc' | 'desc' | undefined) => + sortBy && direction + ? [ ...data ].sort((a, b) => + direction === 'asc' + ? a[sortBy] < b[sortBy] ? -1 : a[sortBy] > b[sortBy] ? 1 : 0 + : a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0 + ) + : data; + +const ouiaId = 'TableExample'; + +export const MyTable: React.FunctionComponent = () => { + const [ searchParams, setSearchParams ] = useSearchParams(); + const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams }); + const sortByIndex = useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]); + + const getSortParams = (columnIndex: number): ThProps['sort'] => ({ + sortBy: { + index: sortByIndex, + direction, + defaultDirection: 'asc' + }, + onSort: (_event, index, direction) => onSort(_event, COLUMNS[index].key, direction), + columnIndex + }); + + const columns: DataViewTh[] = COLUMNS.map((column, index) => ({ + cell: column.label, + props: { sort: getSortParams(index) } + })); + + const rows: DataViewTr[] = useMemo(() => sortData(repositories, sortBy, direction).map(({ name, branches, prs, workspaces, lastCommit }) => [ + name, + branches, + prs, + workspaces, + lastCommit, + ]), [ sortBy, direction ]); + + return ( + + ); +}; + +export const BasicExample: React.FunctionComponent = () => ( + + + +) + From 6f95550542edaa3c009908dca2b00a12cd6e9126 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Wed, 20 Nov 2024 18:08:30 +0100 Subject: [PATCH 20/23] feat: add unit and component tests for sorting --- cypress/component/DataViewTableSorting.cy.tsx | 109 ++++++++++++++++++ packages/module/src/Hooks/sort.test.tsx | 84 ++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 cypress/component/DataViewTableSorting.cy.tsx create mode 100644 packages/module/src/Hooks/sort.test.tsx diff --git a/cypress/component/DataViewTableSorting.cy.tsx b/cypress/component/DataViewTableSorting.cy.tsx new file mode 100644 index 0000000..4b27d3e --- /dev/null +++ b/cypress/component/DataViewTableSorting.cy.tsx @@ -0,0 +1,109 @@ +/* eslint-disable no-nested-ternary */ +import React from 'react'; +import { useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { BrowserRouter, useSearchParams } from 'react-router-dom'; +import { ThProps } from '@patternfly/react-table'; + +interface Repository { + name: string; + branches: string; + prs: string; + workspaces: string; + lastCommit: string; +} + +const COLUMNS = [ + { label: 'Repository', key: 'name', index: 0 }, + { label: 'Branch', key: 'branches', index: 1 }, + { label: 'Pull request', key: 'prs', index: 2 }, + { label: 'Workspace', key: 'workspaces', index: 3 }, + { label: 'Last commit', key: 'lastCommit', index: 4 }, +]; + +const repositories: Repository[] = [ + { name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: '2023-11-01' }, + { name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: '2023-11-06' }, + { name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: '2023-11-02' }, + { name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: '2023-11-05' }, + { name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: '2023-11-03' }, + { name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: '2023-11-04' }, +]; + +const sortData = (data: Repository[], sortBy: keyof Repository | undefined, direction: 'asc' | 'desc' | undefined) => + sortBy && direction + ? [ ...data ].sort((a, b) => + direction === 'asc' + ? a[sortBy] < b[sortBy] ? -1 : a[sortBy] > b[sortBy] ? 1 : 0 + : a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0 + ) + : data; + +const TestableTable: React.FunctionComponent = () => { + const [ searchParams, setSearchParams ] = useSearchParams(); + const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams }); + const sortByIndex = React.useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]); + + const getSortParams = (columnIndex: number): ThProps['sort'] => ({ + sortBy: { + index: sortByIndex, + direction, + defaultDirection: 'asc', + }, + onSort: (_event, index, direction) => onSort(_event, COLUMNS[index].key, direction), + columnIndex, + }); + + const columns: DataViewTh[] = COLUMNS.map((column, index) => ({ + cell: column.label, + props: { sort: getSortParams(index) }, + })); + + const rows: DataViewTr[] = React.useMemo( + () => + sortData(repositories, sortBy ? sortBy as keyof Repository : undefined, direction).map(({ name, branches, prs, workspaces, lastCommit }) => [ + name, + branches, + prs, + workspaces, + lastCommit, + ]), + [ sortBy, direction ] + ); + + return ; +}; + +describe('DataViewTable Sorting with Hook', () => { + it('sorts by repository name in ascending and descending order', () => { + cy.mount( + + + + ); + + cy.get('[data-ouia-component-id="test-table-th-0"]').click(); + cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository five'); + cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository two'); + + cy.get('[data-ouia-component-id="test-table-th-0"]').click(); + cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository two'); + cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository five'); + }); + + it('sorts by last commit date in ascending and descending order', () => { + cy.mount( + + + + ); + + cy.get('[data-ouia-component-id="test-table-th-4"]').click(); + cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-01'); + cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-06'); + + cy.get('[data-ouia-component-id="test-table-th-4"]').click(); + cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-06'); + cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-01'); + }); +}); diff --git a/packages/module/src/Hooks/sort.test.tsx b/packages/module/src/Hooks/sort.test.tsx new file mode 100644 index 0000000..473924d --- /dev/null +++ b/packages/module/src/Hooks/sort.test.tsx @@ -0,0 +1,84 @@ +import '@testing-library/jest-dom'; +import { renderHook, act } from '@testing-library/react'; +import { useDataViewSort, UseDataViewSortProps, DataViewSortConfig, DataViewSortParams } from './sort'; + +describe('useDataViewSort', () => { + const initialSort: DataViewSortConfig = { sortBy: 'name', direction: 'asc' }; + + it('should initialize with provided initial sort config', () => { + const { result } = renderHook(() => useDataViewSort({ initialSort })); + expect(result.current).toEqual(expect.objectContaining(initialSort)); + }); + + it('should initialize with empty sort config if no initialSort is provided', () => { + const { result } = renderHook(() => useDataViewSort()); + expect(result.current).toEqual(expect.objectContaining({ sortBy: undefined, direction: 'asc' })); + }); + + it('should update sort state when onSort is called', () => { + const { result } = renderHook(() => useDataViewSort({ initialSort })); + act(() => { + result.current.onSort(undefined, 'age', 'desc'); + }); + expect(result.current).toEqual(expect.objectContaining({ sortBy: 'age', direction: 'desc' })); + }); + + it('should sync with URL search params if isUrlSyncEnabled', () => { + const searchParams = new URLSearchParams(); + const setSearchParams = jest.fn(); + const props: UseDataViewSortProps = { + initialSort, + searchParams, + setSearchParams, + }; + + const { result } = renderHook(() => useDataViewSort(props)); + + expect(setSearchParams).toHaveBeenCalledTimes(1); + expect(result.current).toEqual(expect.objectContaining(initialSort)); + }); + + it('should validate direction and fallback to default direction if invalid direction is provided', () => { + const searchParams = new URLSearchParams(); + searchParams.set(DataViewSortParams.SORT_BY, 'name'); + searchParams.set(DataViewSortParams.DIRECTION, 'invalid-direction'); + const { result } = renderHook(() => useDataViewSort({ searchParams, defaultDirection: 'desc' })); + + expect(result.current).toEqual(expect.objectContaining({ sortBy: 'name', direction: 'desc' })); + }); + + it('should update search params when URL sync is enabled and sort changes', () => { + const searchParams = new URLSearchParams(); + const setSearchParams = jest.fn(); + const props: UseDataViewSortProps = { + initialSort, + searchParams, + setSearchParams, + }; + + const { result } = renderHook(() => useDataViewSort(props)); + act(() => { + expect(setSearchParams).toHaveBeenCalledTimes(1); + result.current.onSort(undefined, 'priority', 'desc'); + }); + + expect(setSearchParams).toHaveBeenCalledTimes(2); + expect(result.current).toEqual(expect.objectContaining({ sortBy: 'priority', direction: 'desc' })); + }); + + it('should prioritize searchParams values', () => { + const searchParams = new URLSearchParams(); + searchParams.set(DataViewSortParams.SORT_BY, 'category'); + searchParams.set(DataViewSortParams.DIRECTION, 'desc'); + + const { result } = renderHook( + (props: UseDataViewSortProps) => useDataViewSort(props), + { initialProps: { initialSort, searchParams } } + ); + + expect(result.current).toEqual(expect.objectContaining({ + sortBy: 'category', + direction: 'desc', + })); + }); +}); From 8de74b498374ee72023cfd7ebe9acb4f7ae6baf0 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Wed, 20 Nov 2024 18:10:40 +0100 Subject: [PATCH 21/23] fix: clean the code --- cypress/component/DataViewTableSorting.cy.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/component/DataViewTableSorting.cy.tsx b/cypress/component/DataViewTableSorting.cy.tsx index 4b27d3e..79cae0f 100644 --- a/cypress/component/DataViewTableSorting.cy.tsx +++ b/cypress/component/DataViewTableSorting.cy.tsx @@ -39,7 +39,7 @@ const sortData = (data: Repository[], sortBy: keyof Repository | undefined, dire ) : data; -const TestableTable: React.FunctionComponent = () => { +const TestTable: React.FunctionComponent = () => { const [ searchParams, setSearchParams ] = useSearchParams(); const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams }); const sortByIndex = React.useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]); @@ -78,7 +78,7 @@ describe('DataViewTable Sorting with Hook', () => { it('sorts by repository name in ascending and descending order', () => { cy.mount( - + ); @@ -94,7 +94,7 @@ describe('DataViewTable Sorting with Hook', () => { it('sorts by last commit date in ascending and descending order', () => { cy.mount( - + ); From 317462fc860d0aa4a832217d1a52ba32b3707b20 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Fri, 22 Nov 2024 09:38:18 +0100 Subject: [PATCH 22/23] fix: make the changes work with v6 --- package-lock.json | 1848 ++++++++++++----- package.json | 2 +- .../DataViewCheckboxFilter.tsx | 8 +- .../DataViewCheckboxFilter.test.tsx.snap | 135 +- .../DataViewFilters.test.tsx.snap | 110 +- .../DataViewTextFilter/DataViewTextFilter.tsx | 4 +- .../DataViewTextFilter.test.tsx.snap | 161 +- .../src/DataViewToolbar/DataViewToolbar.tsx | 6 +- .../DataViewToolbar.test.tsx.snap | 50 +- 9 files changed, 1622 insertions(+), 702 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0480dd..0cb7794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,10 +19,10 @@ "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@octokit/rest": "^20.1.1", - "@patternfly/documentation-framework": "6.0.0-alpha.117", - "@swc/core": "1.6.1", + "@patternfly/documentation-framework": "6.0.6", + "@swc/core": "1.9.1", "@testing-library/dom": "^10.1.0", - "@testing-library/jest-dom": "^6.4.6", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "14.5.2", "@types/jest": "^29.5.13", @@ -31,18 +31,18 @@ "babel-jest": "^29.7.0", "babel-polyfill": "6.26.0", "chokidar": "^3.6.0", - "concurrently": "^8.2.2", - "cypress": "^13.11.0", + "concurrently": "^9.1.0", + "cypress": "^13.15.2", "eslint": "^8.57.0", "eslint-config-prettier": "9.1.0", "eslint-config-standard-with-typescript": "^23.0.0", - "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-markdown": "^1.0.2", "eslint-plugin-n": "^15.7.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.2.0", "eslint-plugin-react": "^7.34.3", - "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-hooks": "^5.0.0", "fs-extra": "^11.2.0", "glob": "^10.4.1", "identity-obj-proxy": "^3.0.0", @@ -53,7 +53,7 @@ "react": "^18", "react-dom": "^18", "rimraf": "^5.0.7", - "sass": "^1.77.6", + "sass": "^1.80.7", "sass-loader": "^14.2.1", "serve": "^14.2.3", "start-server-and-test": "^2.0.4", @@ -2068,9 +2068,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz", - "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2089,7 +2089,7 @@ "performance-now": "^2.1.0", "qs": "6.13.0", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -2097,6 +2097,19 @@ "node": ">= 6" } }, + "node_modules/@cypress/request/node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -2144,17 +2157,20 @@ "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } @@ -3517,6 +3533,7 @@ "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -3798,9 +3815,9 @@ } }, "node_modules/@patternfly/ast-helpers": { - "version": "1.4.0-alpha.106", - "resolved": "https://registry.npmjs.org/@patternfly/ast-helpers/-/ast-helpers-1.4.0-alpha.106.tgz", - "integrity": "sha512-5Xfi2r2oudTvPYjoMGlJKSDlAf95GCTr8ely/F/pNiyV28EdNPJ9cKIVWstZex1wxvyxtvp5wOfpRh2fWE1Khg==", + "version": "1.4.0-alpha.125", + "resolved": "https://registry.npmjs.org/@patternfly/ast-helpers/-/ast-helpers-1.4.0-alpha.125.tgz", + "integrity": "sha512-WvN5CYFmTYOztt3wMidJq8xLdu6jhpHzjPvYxMN2wlh7v0QJEHaplvJGZH/E8ZUcri/ni2OoLVIe2KcuRwrxng==", "dev": true, "license": "MIT", "dependencies": { @@ -3812,9 +3829,9 @@ } }, "node_modules/@patternfly/documentation-framework": { - "version": "6.0.0-alpha.117", - "resolved": "https://registry.npmjs.org/@patternfly/documentation-framework/-/documentation-framework-6.0.0-alpha.117.tgz", - "integrity": "sha512-npVzrFElVEuDBSvu4yiF5BK3c1x9RwjD1G+bbtLbbb8uffPul88zeThuA7vpi02sQc1z1cV6XGJo6ZZ8OQ1OjQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@patternfly/documentation-framework/-/documentation-framework-6.0.6.tgz", + "integrity": "sha512-YXXzj7K2lmAZbvgTDSTd4AJTW6xpJ6G/C3jCMxGGMXevYJ1wGHQQnBVbGQvQcZPgIwU5nv3+FmnyfldJhRwQtw==", "dev": true, "license": "MIT", "dependencies": { @@ -3822,7 +3839,7 @@ "@babel/preset-env": "^7.24.3", "@babel/preset-react": "^7.24.1", "@mdx-js/util": "1.6.16", - "@patternfly/ast-helpers": "^1.4.0-alpha.106", + "@patternfly/ast-helpers": "^1.4.0-alpha.122", "@reach/router": "npm:@gatsbyjs/reach-router@1.3.9", "autoprefixer": "9.8.6", "babel-loader": "^9.1.3", @@ -3885,10 +3902,10 @@ "pf-docs-framework": "scripts/cli/cli.js" }, "peerDependencies": { - "@patternfly/patternfly": "6.0.0-prerelease.15", - "@patternfly/react-code-editor": "6.0.0-prerelease.21", - "@patternfly/react-core": "6.0.0-prerelease.21", - "@patternfly/react-table": "6.0.0-prerelease.22", + "@patternfly/patternfly": "^6.0.0", + "@patternfly/react-code-editor": "^6.0.0", + "@patternfly/react-core": "^6.0.0", + "@patternfly/react-table": "^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" } @@ -4296,265 +4313,260 @@ } }, "node_modules/@patternfly/patternfly": { - "version": "6.0.0-prerelease.15", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.0.0-prerelease.15.tgz", - "integrity": "sha512-7/tRjaWBMX9bvMhIexdAONgxKiVbOCuFffLEKq+p+Q4t8Qq8fqGM7eAznzOx3YbLKIzqVtmbRrgTAIkpsSe1aw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.0.0.tgz", + "integrity": "sha512-Mn92Tt/4okSj1COGCJrgUgh390OOaFCWf0tL0WmigDNUecSHNn1D6Vhpd1hxHQBXvre9eWorzxV2b9yhSEl79Q==", "dev": true, "license": "MIT" }, "node_modules/@patternfly/patternfly-a11y": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly-a11y/-/patternfly-a11y-4.3.1.tgz", - "integrity": "sha512-WBdiCJsfEo+cLgtLPDyvOtvRewzWqUvymLOx+Hj/jFoMI7hbotoZSBl4GUZDtFDOHXN4/vfu16eZy9DQHTY/Ew==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly-a11y/-/patternfly-a11y-5.0.0.tgz", + "integrity": "sha512-2GSzO6rSo/8Qg5IBvZeRtJp9tLj6XZGMD0HkENMBkHKDXBdUvCLftsu91E9408qAqgxDx1DrO3nhB3spo5LfiA==", "dev": true, "license": "MIT", "dependencies": { - "axe-core": "^4.4.1", - "chromedriver": "^101.0.0", - "commander": "^5.1.0", - "fs-extra": "^10.0.0", + "axe-core": "^4.10.2", + "chromedriver": "^130.0.1", + "commander": "^12.1.0", + "fs-extra": "^11.2.0", "junit-xml": "^1.2.0", - "puppeteer": "^14.2.0", - "puppeteer-cluster": "^0.23.0", - "xmldoc": "^1.1.2" + "puppeteer": "^23.6.1", + "puppeteer-cluster": "^0.24.0", + "xmldoc": "^1.3.0" }, "bin": { "patternfly-a11y": "cli.js" + }, + "peerDependencies": { + "victory-bar": "^37.1.1", + "victory-core": "^37.1.1", + "victory-create-container": "^37.1.1", + "victory-cursor-container": "^37.1.1", + "victory-group": "^37.1.1", + "victory-legend": "^37.1.1", + "victory-line": "^37.1.1", + "victory-pie": "^37.1.1", + "victory-scatter": "^37.1.1", + "victory-stack": "^37.1.1", + "victory-tooltip": "^37.1.1", + "victory-voronoi-container": "^37.1.1" + } + }, + "node_modules/@patternfly/patternfly-a11y/node_modules/@puppeteer/browsers": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.1.tgz", + "integrity": "sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@patternfly/patternfly-a11y/node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/@patternfly/patternfly-a11y/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "MIT", + "license": "Python-2.0" + }, + "node_modules/@patternfly/patternfly-a11y/node_modules/chromium-bidi": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" } }, "node_modules/@patternfly/patternfly-a11y/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/@patternfly/patternfly-a11y/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@patternfly/patternfly-a11y/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=6.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" }, "peerDependenciesMeta": { - "supports-color": { + "typescript": { "optional": true } } }, "node_modules/@patternfly/patternfly-a11y/node_modules/devtools-protocol": { - "version": "0.0.1001819", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1001819.tgz", - "integrity": "sha512-G6OsIFnv/rDyxSqBa2lDLR6thp9oJioLsb2Gl+LbQlyoA9/OBAkrTU9jiCcQ8Pnh7z4d6slDiLaogR5hzgJLmQ==", + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@patternfly/patternfly-a11y/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "node_modules/@patternfly/patternfly-a11y/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@patternfly/patternfly-a11y/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@patternfly/patternfly-a11y/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/@patternfly/patternfly-a11y/node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true, "license": "MIT" }, - "node_modules/@patternfly/patternfly-a11y/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/@patternfly/patternfly-a11y/node_modules/puppeteer": { + "version": "23.9.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.9.0.tgz", + "integrity": "sha512-WfB8jGwFV+qrD9dcJJVvWPFJBU6kxeu2wxJz9WooDGfM3vIiKLgzImEDBxUQnCBK/2cXB3d4dV6gs/LLpgfLDg==", "dev": true, - "license": "MIT", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "find-up": "^4.0.0" + "@puppeteer/browsers": "2.4.1", + "chromium-bidi": "0.8.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1367902", + "puppeteer-core": "23.9.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@patternfly/patternfly-a11y/node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@patternfly/patternfly-a11y/node_modules/puppeteer": { - "version": "14.4.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-14.4.1.tgz", - "integrity": "sha512-+H0Gm84aXUvSLdSiDROtLlOofftClgw2TdceMvvCU9UvMryappoeS3+eOLfKvoy4sm8B8MWnYmPhWxVFudAOFQ==", - "deprecated": "< 22.8.2 is no longer supported", + "node_modules/@patternfly/patternfly-a11y/node_modules/puppeteer-cluster": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/puppeteer-cluster/-/puppeteer-cluster-0.24.0.tgz", + "integrity": "sha512-zHPoNsrwkFLKFtgJJv2aC13EbMASQsE048uZd7CyikEXcl+sc1Nf6PMFb9kMoZI7/zMYxvuP658o2mw079ZZyQ==", "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1001819", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.7.0" + "debug": "^4.3.4" }, - "engines": { - "node": ">=14.1.0" + "peerDependencies": { + "puppeteer": ">=22.0.0" } }, - "node_modules/@patternfly/patternfly-a11y/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/@patternfly/patternfly-a11y/node_modules/puppeteer-core": { + "version": "23.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.9.0.tgz", + "integrity": "sha512-hLVrav2HYMVdK0YILtfJwtnkBAwNOztUdR4aJ5YKDvgsbtagNr6urUJk9HyjRA9e+PaLI3jzJ0wM7A4jSZ7Qxw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@puppeteer/browsers": "2.4.1", + "chromium-bidi": "0.8.0", + "debug": "^4.3.7", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" }, "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/@patternfly/patternfly-a11y/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/@patternfly/patternfly-a11y/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, "bin": { - "rimraf": "bin.js" + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=10" } }, "node_modules/@patternfly/patternfly-a11y/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/@patternfly/patternfly-a11y/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@patternfly/patternfly-a11y/node_modules/ws": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz", - "integrity": "sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/@patternfly/react-code-editor": { - "version": "6.0.0-prerelease.21", - "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.0.0-prerelease.21.tgz", - "integrity": "sha512-t9/8Uk3sbPaXasZXHaIxvcAGRWAlep9L0Gsy1vA+vzmpU8Igk1GO2JNMVr9ux4ScLEuMnzp0Rbq++VbxtDNdwA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.0.0.tgz", + "integrity": "sha512-TnI/NNkizzWTzdVZWmpyEPKXgsOoUeklk8Xlgtl7II/+5juLjlt0wXTMhL35F59Rzd0YohGs251zXAwJbn6vIQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@monaco-editor/react": "^4.6.0", - "@patternfly/react-core": "^6.0.0-prerelease.21", - "@patternfly/react-icons": "^6.0.0-prerelease.7", - "@patternfly/react-styles": "^6.0.0-prerelease.6", + "@patternfly/react-core": "^6.0.0", + "@patternfly/react-icons": "^6.0.0", + "@patternfly/react-styles": "^6.0.0", "react-dropzone": "14.2.3", "tslib": "^2.7.0" }, @@ -4564,14 +4576,14 @@ } }, "node_modules/@patternfly/react-component-groups": { - "version": "6.0.0-prerelease.7", - "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-6.0.0-prerelease.7.tgz", - "integrity": "sha512-+JYabo0L8ASWSiYHA6mzDrAdPABdcmaEk2Ts06PuGb/nrZdOLvdAHRFpzcnjFva9+tkeItHvAXAT7bK5WVAEEg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-6.0.0.tgz", + "integrity": "sha512-q4dQj4b/Yf8fW3ZXWZhXrX8xE73JpRAFF5wydDx6OZe3oZXucUgldghupavbyhY/oYM2V89/P67w24poH6iOqA==", "license": "MIT", "dependencies": { - "@patternfly/react-core": "^6.0.0-prerelease.21", - "@patternfly/react-icons": "^6.0.0-prerelease.7", - "@patternfly/react-table": "^6.0.0-prerelease.22", + "@patternfly/react-core": "^6.0.0", + "@patternfly/react-icons": "^6.0.0", + "@patternfly/react-table": "^6.0.0", "clsx": "^2.1.1", "react-jss": "^10.10.0" }, @@ -4581,14 +4593,14 @@ } }, "node_modules/@patternfly/react-core": { - "version": "6.0.0-prerelease.21", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.0.0-prerelease.21.tgz", - "integrity": "sha512-EaGcKUPeeR253vY4N0Ahm9oOVtltoI6JycfclwmzjevOzpYvuLj1jcsVwL8wqgWYQVpURoBm1yxIdx34fo5UHA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.0.0.tgz", + "integrity": "sha512-UKFj9+YzBY+FfEDsLONgOM4N0e8SPV/27/UzNRiJ0gpgqbw2POuXwLpjGSRTTIUuCaLaGGM5PeTSj7mMB73ykw==", "license": "MIT", "dependencies": { - "@patternfly/react-icons": "^6.0.0-prerelease.7", - "@patternfly/react-styles": "^6.0.0-prerelease.6", - "@patternfly/react-tokens": "^6.0.0-prerelease.7", + "@patternfly/react-icons": "^6.0.0", + "@patternfly/react-styles": "^6.0.0", + "@patternfly/react-tokens": "^6.0.0", "focus-trap": "7.6.0", "react-dropzone": "^14.2.3", "tslib": "^2.7.0" @@ -4603,9 +4615,9 @@ "link": true }, "node_modules/@patternfly/react-icons": { - "version": "6.0.0-prerelease.7", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.0.0-prerelease.7.tgz", - "integrity": "sha512-DQmecVgXRIiD3ww4KUuJ0qO76TmYMDEJ1ao1+DYuTSP+FzeJLJKuE9QxvL8qn3anyKtuORBuHdTIjM52mVq5Vg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.0.0.tgz", + "integrity": "sha512-ZFrsBVKrAp0DZrPOss98OA/EVUL4F0frXhR1uBId9+3ZrRArdKTgYgmQUCeSzMbxnSlxpmm3a2L05XQ36VUVbw==", "license": "MIT", "peerDependencies": { "react": "^17 || ^18", @@ -4613,21 +4625,21 @@ } }, "node_modules/@patternfly/react-styles": { - "version": "6.0.0-prerelease.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.0.0-prerelease.6.tgz", - "integrity": "sha512-tI28gIJFgbgVQs7Xj705csfl6T92dr5Bh7ynR5gN4+QdTWCUWmSctp46G2ZewXdrIN+C+2zUPE86o77aFp4CWw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.0.0.tgz", + "integrity": "sha512-fJFMB89sTRGlZTzTLmpRmthgOXqcN078scHMFJ3ttfi2D2btnem5oZrxmQ/gPZkZOxR+9MqwKDB6l3F5x1SqLQ==", "license": "MIT" }, "node_modules/@patternfly/react-table": { - "version": "6.0.0-prerelease.22", - "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-6.0.0-prerelease.22.tgz", - "integrity": "sha512-vGDWr14YATIY9RnxaOpyjkPlw4aKBM7dhHJRPXY0cwCFehSGwwzQpialZyi+92I2WfBF1Cb75doDAtxTSi8UZQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-6.0.0.tgz", + "integrity": "sha512-LvWMzjcQZHdFUpK8fjj5EAFrNxqB8/MFd7gUUZu7AgYt6rmS2im4xk6yb7h0K7cAhY085oPeRF9lkYSCgzlRDg==", "license": "MIT", "dependencies": { - "@patternfly/react-core": "^6.0.0-prerelease.21", - "@patternfly/react-icons": "^6.0.0-prerelease.7", - "@patternfly/react-styles": "^6.0.0-prerelease.6", - "@patternfly/react-tokens": "^6.0.0-prerelease.7", + "@patternfly/react-core": "^6.0.0", + "@patternfly/react-icons": "^6.0.0", + "@patternfly/react-styles": "^6.0.0", + "@patternfly/react-tokens": "^6.0.0", "lodash": "^4.17.21", "tslib": "^2.7.0" }, @@ -4637,9 +4649,9 @@ } }, "node_modules/@patternfly/react-tokens": { - "version": "6.0.0-prerelease.7", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.0.0-prerelease.7.tgz", - "integrity": "sha512-SLgVwbIgVx26LCjaXkpNlPIZYqWpHJkw3QX/n3URLmIcRlCw536/rKO1PzXaeuCCqhuSq66J6R125zM2eJjM6A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.0.0.tgz", + "integrity": "sha512-xd0ynDkiIW2rp8jz4TNvR4Dyaw9kSMkZdsuYcLlFXCVmvX//Mnl4rhBnid/2j2TaqK0NbkyTTPnPY/BU7SfLVQ==", "license": "MIT" }, "node_modules/@pkgjs/parseargs": { @@ -4840,9 +4852,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", - "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", "dev": true, "license": "MIT", "engines": { @@ -4908,15 +4920,15 @@ } }, "node_modules/@swc/core": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.6.1.tgz", - "integrity": "sha512-Yz5uj5hNZpS5brLtBvKY0L4s2tBAbQ4TjmW8xF1EC3YLFxQRrUjMP49Zm1kp/KYyYvTkSaG48Ffj2YWLu9nChw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.9.1.tgz", + "integrity": "sha512-OnPc+Kt5oy3xTvr/KCUOqE9ptJcWbyQgAUr1ydh9EmbBcmJTaO1kfQCxm/axzJi6sKeDTxL9rX5zvLOhoYIaQw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.8" + "@swc/types": "^0.1.14" }, "engines": { "node": ">=10" @@ -4926,16 +4938,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.6.1", - "@swc/core-darwin-x64": "1.6.1", - "@swc/core-linux-arm-gnueabihf": "1.6.1", - "@swc/core-linux-arm64-gnu": "1.6.1", - "@swc/core-linux-arm64-musl": "1.6.1", - "@swc/core-linux-x64-gnu": "1.6.1", - "@swc/core-linux-x64-musl": "1.6.1", - "@swc/core-win32-arm64-msvc": "1.6.1", - "@swc/core-win32-ia32-msvc": "1.6.1", - "@swc/core-win32-x64-msvc": "1.6.1" + "@swc/core-darwin-arm64": "1.9.1", + "@swc/core-darwin-x64": "1.9.1", + "@swc/core-linux-arm-gnueabihf": "1.9.1", + "@swc/core-linux-arm64-gnu": "1.9.1", + "@swc/core-linux-arm64-musl": "1.9.1", + "@swc/core-linux-x64-gnu": "1.9.1", + "@swc/core-linux-x64-musl": "1.9.1", + "@swc/core-win32-arm64-msvc": "1.9.1", + "@swc/core-win32-ia32-msvc": "1.9.1", + "@swc/core-win32-x64-msvc": "1.9.1" }, "peerDependencies": { "@swc/helpers": "*" @@ -4947,9 +4959,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.1.tgz", - "integrity": "sha512-u6GdwOXsOEdNAdSI6nWq6G2BQw5HiSNIZVcBaH1iSvBnxZvWbnIKyDiZKaYnDwTLHLzig2GuUjjE2NaCJPy4jg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.1.tgz", + "integrity": "sha512-2/ncHSCdAh5OHem1fMITrWEzzl97OdMK1PHc9CkxSJnphLjRubfxB5sbc5tDhcO68a5tVy+DxwaBgDec3PXnOg==", "cpu": [ "arm64" ], @@ -4964,9 +4976,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.6.1.tgz", - "integrity": "sha512-/tXwQibkDNLVbAtr7PUQI0iQjoB708fjhDDDfJ6WILSBVZ3+qs/LHjJ7jHwumEYxVq1XA7Fv2Q7SE/ZSQoWHcQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.9.1.tgz", + "integrity": "sha512-4MDOFC5zmNqRJ9RGFOH95oYf27J9HniLVpB1pYm2gGeNHdl2QvDMtx2QTuMHQ6+OTn/3y1BHYuhBGp7d405oLA==", "cpu": [ "x64" ], @@ -4981,9 +4993,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.1.tgz", - "integrity": "sha512-aDgipxhJTms8iH78emHVutFR2c16LNhO+NTRCdYi+X4PyIn58/DyYTH6VDZ0AeEcS5f132ZFldU5AEgExwihXA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.1.tgz", + "integrity": "sha512-eVW/BjRW8/HpLe3+1jRU7w7PdRLBgnEEYTkHJISU8805/EKT03xNZn6CfaBpKfeAloY4043hbGzE/NP9IahdpQ==", "cpu": [ "arm" ], @@ -4998,9 +5010,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.1.tgz", - "integrity": "sha512-XkJ+eO4zUKG5g458RyhmKPyBGxI0FwfWFgpfIj5eDybxYJ6s4HBT5MoxyBLorB5kMlZ0XoY/usUMobPVY3nL0g==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.1.tgz", + "integrity": "sha512-8m3u1v8R8NgI/9+cHMkzk14w87blSy3OsQPWPfhOL+XPwhyLPvat+ahQJb2nZmltjTgkB4IbzKFSfbuA34LmNA==", "cpu": [ "arm64" ], @@ -5015,9 +5027,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.1.tgz", - "integrity": "sha512-dr6YbLBg/SsNxs1hDqJhxdcrS8dGMlOXJwXIrUvACiA8jAd6S5BxYCaqsCefLYXtaOmu0bbx1FB/evfodqB70Q==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.1.tgz", + "integrity": "sha512-hpT0sQAZnW8l02I289yeyFfT9llGO9PzKDxUq8pocKtioEHiElRqR53juCWoSmzuWi+6KX7zUJ0NKCBrc8pmDg==", "cpu": [ "arm64" ], @@ -5032,9 +5044,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.1.tgz", - "integrity": "sha512-A0b/3V+yFy4LXh3O9umIE7LXPC7NBWdjl6AQYqymSMcMu0EOb1/iygA6s6uWhz9y3e172Hpb9b/CGsuD8Px/bg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.1.tgz", + "integrity": "sha512-sGFdpdAYusk/ropHiwtXom2JrdaKPxl8MqemRv6dvxZq1Gm/GdmOowxdXIPjCgBGMgoXVcgNviH6CgiO5q+UtA==", "cpu": [ "x64" ], @@ -5049,9 +5061,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.1.tgz", - "integrity": "sha512-5dJjlzZXhC87nZZZWbpiDP8kBIO0ibis893F/rtPIQBI5poH+iJuA32EU3wN4/WFHeK4et8z6SGSVghPtWyk4g==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.1.tgz", + "integrity": "sha512-YtNLNwIWs0Z2+XgBs6+LrCIGtfCDtNr4S4b6Q5HDOreEIGzSvhkef8eyBI5L+fJ2eGov4b7iEo61C4izDJS5RA==", "cpu": [ "x64" ], @@ -5066,9 +5078,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.1.tgz", - "integrity": "sha512-HBi1ZlwvfcUibLtT3g/lP57FaDPC799AD6InolB2KSgkqyBbZJ9wAXM8/CcH67GLIP0tZ7FqblrJTzGXxetTJQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.1.tgz", + "integrity": "sha512-qSxD3uZW2vSiHqUt30vUi0PB92zDh9bjqh5YKpfhhVa7h1vt/xXhlid8yMvSNToTfzhRrTEffOAPUr7WVoyQUA==", "cpu": [ "arm64" ], @@ -5083,9 +5095,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.1.tgz", - "integrity": "sha512-AKqHohlWERclexar5y6ux4sQ8yaMejEXNxeKXm7xPhXrp13/1p4/I3E5bPVX/jMnvpm4HpcKSP0ee2WsqmhhPw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.1.tgz", + "integrity": "sha512-C3fPEwyX/WRPlX6zIToNykJuz1JkZX0sk8H1QH2vpnKuySUkt/Ur5K2FzLgSWzJdbfxstpgS151/es0VGAD+ZA==", "cpu": [ "ia32" ], @@ -5100,9 +5112,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.1.tgz", - "integrity": "sha512-0dLdTLd+ONve8kgC5T6VQ2Y5G+OZ7y0ujjapnK66wpvCBM6BKYGdT/OKhZKZydrC5gUKaxFN6Y5oOt9JOFUrOQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.1.tgz", + "integrity": "sha512-2XZ+U1AyVsOAXeH6WK1syDm7+gwTjA8fShs93WcbxnK7HV+NigDlvr4124CeJLTHyh3fMh1o7+CnQnaBJhlysQ==", "cpu": [ "x64" ], @@ -5124,9 +5136,9 @@ "license": "Apache-2.0" }, "node_modules/@swc/types": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", - "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", + "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5237,9 +5249,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", - "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", "dev": true, "license": "MIT", "dependencies": { @@ -5389,6 +5401,13 @@ "node": ">= 10" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -5483,6 +5502,87 @@ "@types/node": "*" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -7159,9 +7259,9 @@ "license": "MIT" }, "node_modules/axe-core": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", - "integrity": "sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", "dev": true, "license": "MPL-2.0", "engines": { @@ -7550,6 +7650,16 @@ ], "license": "MIT" }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -8411,60 +8521,38 @@ } }, "node_modules/chromedriver": { - "version": "101.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz", - "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==", - "deprecated": "Chromedriver download url has changed. Use version 114.0.2 or newer.", + "version": "130.0.4", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-130.0.4.tgz", + "integrity": "sha512-lpR+PWXszij1k4Ig3t338Zvll9HtCTiwoLM7n4pCCswALHxzmgwaaIFBh3rt9+5wRk9D07oFblrazrBxwaYYAQ==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@testim/chrome-version": "^1.1.2", - "axios": "^0.24.0", - "del": "^6.0.0", + "@testim/chrome-version": "^1.1.4", + "axios": "^1.7.4", + "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.0", + "proxy-agent": "^6.4.0", "proxy-from-env": "^1.1.0", - "tcp-port-used": "^1.0.1" + "tcp-port-used": "^1.0.2" }, "bin": { "chromedriver": "bin/chromedriver" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/chromedriver/node_modules/axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.4" - } - }, - "node_modules/chromedriver/node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, "license": "MIT", "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/chromedriver/node_modules/follow-redirects": { @@ -8488,44 +8576,6 @@ } } }, - "node_modules/chromedriver/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/chromedriver/node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/chromedriver/node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -8533,23 +8583,6 @@ "dev": true, "license": "MIT" }, - "node_modules/chromedriver/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/chromium-bidi": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz", @@ -9127,6 +9160,13 @@ "dev": true, "license": "MIT" }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -9217,18 +9257,16 @@ } }, "node_modules/concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz", + "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", - "date-fns": "^2.30.0", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" @@ -9238,7 +9276,7 @@ "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": "^14.13.0 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" @@ -9964,14 +10002,14 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz", - "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.0.tgz", + "integrity": "sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.4", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -9982,6 +10020,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -9996,7 +10035,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -10011,6 +10049,7 @@ "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -10067,6 +10106,22 @@ "node": ">=8" } }, + "node_modules/cypress/node_modules/ci-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cypress/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -10120,36 +10175,179 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cypress/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/cypress/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "peer": true, + "dependencies": { + "d3-array": "2 - 3" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/cypress/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "dev": true, - "license": "MIT", + "license": "ISC", + "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "d3-time": "1 - 3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" } }, "node_modules/dashdash": { @@ -10165,6 +10363,16 @@ "node": ">=0.10" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -10260,23 +10468,6 @@ "semver": "bin/semver" } }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -10473,6 +10664,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/del": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", @@ -10568,6 +10787,25 @@ "rimraf": "bin.js" } }, + "node_modules/delaunator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", + "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/delaunay-find": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/delaunay-find/-/delaunay-find-0.0.6.tgz", + "integrity": "sha512-1+almjfrnR7ZamBk0q3Nhg6lqSe6Le4vL0WJDSMx4IDbQwTpUTXPjxC00lqLBT8MYsJpPCbI16sIkw9cPsbi7Q==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "delaunator": "^4.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -10636,6 +10874,7 @@ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "dev": true, "license": "Apache-2.0", + "optional": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -11030,6 +11269,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", @@ -11383,7 +11632,27 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-config-standard": { + "node_modules/eslint-config-standard-with-typescript": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-23.0.0.tgz", + "integrity": "sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==", + "deprecated": "Please use eslint-config-love, instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "typescript": "*" + } + }, + "node_modules/eslint-config-standard-with-typescript/node_modules/eslint-config-standard": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", @@ -11410,26 +11679,6 @@ "eslint-plugin-promise": "^6.0.0" } }, - "node_modules/eslint-config-standard-with-typescript": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-23.0.0.tgz", - "integrity": "sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==", - "deprecated": "Please use eslint-config-love, instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint-config-standard": "17.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0", - "eslint-plugin-promise": "^6.0.0", - "typescript": "*" - } - }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -11871,16 +12120,16 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", + "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "node_modules/eslint-plugin-react/node_modules/doctrine": { @@ -13392,6 +13641,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/getos": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", @@ -14416,9 +14681,9 @@ } }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", "dev": true, "license": "MIT" }, @@ -14741,6 +15006,17 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -14768,6 +15044,34 @@ "dev": true, "license": "MIT" }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -14944,19 +15248,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -19671,6 +19962,16 @@ "dev": true, "license": "MIT" }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/netrc": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz", @@ -19720,7 +20021,8 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/node-dir": { "version": "0.1.17", @@ -20427,7 +20729,123 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/pac-proxy-agent/node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" } }, "node_modules/package-json": { @@ -21454,6 +21872,125 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-agent/node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/proxy-agent/node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/proxy-agent/node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", @@ -21965,6 +22502,14 @@ "react": ">= 16.8 || 18.0.0" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -22002,13 +22547,13 @@ "license": "MIT" }, "node_modules/react-router": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", - "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", "dev": true, "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0" + "@remix-run/router": "1.21.0" }, "engines": { "node": ">=14.0.0" @@ -22018,14 +22563,14 @@ } }, "node_modules/react-router-dom": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", - "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", "dev": true, "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0", - "react-router": "6.27.0" + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" }, "engines": { "node": ">=14.0.0" @@ -23066,15 +23611,14 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.79.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz", - "integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==", + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.81.0.tgz", + "integrity": "sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==", "dev": true, "license": "MIT", "dependencies": { - "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", - "immutable": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -23082,6 +23626,9 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-loader": { @@ -24081,12 +24628,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -25468,6 +26009,26 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, + "node_modules/tldts": { + "version": "6.1.62", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.62.tgz", + "integrity": "sha512-TF+wo3MgTLbf37keEwQD0IxvOZO8UZxnpPJDg5iFGAASGxYzbX/Q0y944ATEjrfxG/pF1TWRHCPbFp49Mz1Y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.62" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.62", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.62.tgz", + "integrity": "sha512-ohONqbfobpuaylhqFbtCzc0dFFeNz85FVKSesgT8DS9OV3a25Yj730pTj7/dDtCqmgoCgEj6gDiU9XxgHKQlBw==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -25921,6 +26482,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -26617,6 +27185,13 @@ "node": ">=0.10.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true, + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -26823,6 +27398,289 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/victory-bar": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-bar/-/victory-bar-37.3.2.tgz", + "integrity": "sha512-inqb9HLgxheidOAJw7jTMBBR18I7rCgtfH4WuCSMPPtZtUBAEDYFIJzAKzL/LrpC/sWi91fQC2tzmphTD3DS+Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2", + "victory-vendor": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-brush-container": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-brush-container/-/victory-brush-container-37.3.2.tgz", + "integrity": "sha512-SZ4LuM8l3tpGkcnTaGGYREs8gz9E17/c9k3X8uObnXGpz9M3RXkL5hEP0ewtcCjbTr1nwRK3hB4tc1b5BTDj9w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "react-fast-compare": "^3.2.0", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-core": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-37.3.2.tgz", + "integrity": "sha512-ez/QW9OGltj0uo9EzsRrGEu/hS49dXoraQKblQJO4PEUkDZclV3Gy3CJ2cRW2qBM3ljTsVITiQvKt3urVj+RDw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.0", + "victory-vendor": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-create-container": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-create-container/-/victory-create-container-37.3.2.tgz", + "integrity": "sha512-+hEHeTzeANX1knGOqbeykN1mPUy+zQ8A6LLUSiF8dER+DO2IcF/521LHjBSCRcyjUDik75shfhMc//wryxGBmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-brush-container": "37.3.2", + "victory-core": "37.3.2", + "victory-cursor-container": "37.3.2", + "victory-selection-container": "37.3.2", + "victory-voronoi-container": "37.3.2", + "victory-zoom-container": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-cursor-container": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-cursor-container/-/victory-cursor-container-37.3.2.tgz", + "integrity": "sha512-PpBIexoxV/D9S0JTwgI/AmGC5PEjVFbBg+dxbr0iBskXtIckIdXYg11Tejk4iatvUIuvXrJALMjH+y4Jhl+bGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-group": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-group/-/victory-group-37.3.2.tgz", + "integrity": "sha512-TB0ClboJsCR+ATIwUMgSdkdmau4XUiVaZtc5vehZYB7j50lv3SUtltc4qpztOrilsp1osoKHgUHpj7J8yhRtMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "react-fast-compare": "^3.2.0", + "victory-core": "37.3.2", + "victory-shared-events": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-legend": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-legend/-/victory-legend-37.3.2.tgz", + "integrity": "sha512-NrXnStmdqWtfA2AkwOVVWGwXswN9C9TNnmUKzXY52BMWR4OQfIaeO0etRV3vSrv8yUD/rt+rm0UOAAI7pSb1Tw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-line": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-line/-/victory-line-37.3.2.tgz", + "integrity": "sha512-ZA6A3te2A+egmSPG7jTRu/ZetRE66ggUix11yPaf+7DMZSnRw9f6KXduiUV+fz1otwefJ9pL6vMpXo2SIt9jFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2", + "victory-vendor": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-pie": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-pie/-/victory-pie-37.3.2.tgz", + "integrity": "sha512-et7Z9d1paoqXdSL8yNpdTadhhhpPgQTrqO5n7vISNJsjOmt0FTLe4/VeKIyugIhERirVkU5Va0CMlMInbNIysw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2", + "victory-vendor": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-scatter": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-scatter/-/victory-scatter-37.3.2.tgz", + "integrity": "sha512-mFFIvFC5U+AX1uAgl19HI21L4Mf1rt2YlnjjzPFkJcgy0a2Fv6otjUd4qeqA6pAkVU4e/Dqj8ZZPV2PtfXNeyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-selection-container": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-selection-container/-/victory-selection-container-37.3.2.tgz", + "integrity": "sha512-g/26Xo4Rxco91GPjZ4jgIQDm95AT4zdG2RqXdQTWEgLbWi+Z/Tk1nodhr1jVjwIjZ6RNMeXcAtcVhFjIPLsWzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-shared-events": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-shared-events/-/victory-shared-events-37.3.2.tgz", + "integrity": "sha512-L/p/JzbP7O0x/DrWZicVByyiq0YMsDsvc2uinUCoI+XMAXbp7flxC2lbd7YBq41Sg13BHoPBiEoCXH4nKmo8hw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.19", + "react-fast-compare": "^3.2.0", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-stack": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-stack/-/victory-stack-37.3.2.tgz", + "integrity": "sha512-lD8EyzYKTPyWam7h++6VfPxOw4Soj5kYjHqPlbjFjdxoGWYquHrcbHhJ4ra1p+Il9k6iDhy0lIU2DbLa2Ff0wA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "react-fast-compare": "^3.2.0", + "victory-core": "37.3.2", + "victory-shared-events": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-tooltip": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-tooltip/-/victory-tooltip-37.3.2.tgz", + "integrity": "sha512-UsEIGY5lqf3pOGr3ikB7oHw+QZtS+Qui/vqLPhoAL1YJ2bQ0WkIhVx2jLTlyCLIrj6aCvyvdePppApFF43E7Zg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.2.tgz", + "integrity": "sha512-2N8j0DIHocffTo2UeZbcd8fcB5+CrQq2KMOSbyTIzYuCVHP10XoS0R/ln7YOU5WoNS6/6L3GEdFWBaGYAAMErQ==", + "dev": true, + "license": "MIT AND ISC", + "peer": true, + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/victory-voronoi-container": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-voronoi-container/-/victory-voronoi-container-37.3.2.tgz", + "integrity": "sha512-Y5NiUcVa6SlAMl5yAI5pRRBRCUCDHJHYeUMCiccTOjA+G2B2pUJkJ5NKeFzMLt7hW1LQv0nnRs9ywpMdViF8nQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "delaunay-find": "0.0.6", + "lodash": "^4.17.19", + "react-fast-compare": "^3.2.0", + "victory-core": "37.3.2", + "victory-tooltip": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/victory-zoom-container": { + "version": "37.3.2", + "resolved": "https://registry.npmjs.org/victory-zoom-container/-/victory-zoom-container-37.3.2.tgz", + "integrity": "sha512-znF3L6+LpQ8hN+7aW8RO+dsHPl1XsMAvf52IKc0OAXZzukIwx1+yW2/OLtHU7G0DzAl3Cxzt5BNd7jFLVXWQJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.19", + "victory-core": "37.3.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -28033,29 +28891,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "packages/module": { "name": "@patternfly/react-data-view", "version": "1.0.0-prerelease.0", "license": "MIT", "dependencies": { - "@patternfly/react-component-groups": "6.0.0-prerelease.7", - "@patternfly/react-core": "6.0.0-prerelease.21", - "@patternfly/react-icons": "6.0.0-prerelease.7", - "@patternfly/react-table": "6.0.0-prerelease.22", + "@patternfly/react-component-groups": "6.0.0", + "@patternfly/react-core": "6.0.0", + "@patternfly/react-icons": "6.0.0", + "@patternfly/react-table": "6.0.0", "clsx": "^2.1.1", "react-jss": "^10.10.0" }, "devDependencies": { - "@patternfly/documentation-framework": "6.0.0-alpha.117", - "@patternfly/patternfly": "6.0.0-prerelease.15", - "@patternfly/patternfly-a11y": "^4.3.1", + "@patternfly/documentation-framework": "6.0.6", + "@patternfly/patternfly": "6.0.0", + "@patternfly/patternfly-a11y": "^5.0.0", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.0", "@types/react-router-dom": "^5.3.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router": "^6.23.0", - "react-router-dom": "^6.23.0", + "react-router-dom": "^6.28.0", "rimraf": "^5.0.5", "typescript": "^5.4.5" }, diff --git a/package.json b/package.json index ab5c525..9cf31d7 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "eslint-config-standard-with-typescript": "^23.0.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-markdown": "^1.0.2", - "eslint-plugin-n": "^17.13.1", + "eslint-plugin-n": "^15.7.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.2.0", "eslint-plugin-react": "^7.34.3", diff --git a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx index 7235165..86a68ad 100644 --- a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx +++ b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx @@ -8,13 +8,13 @@ import { MenuProps, MenuToggle, Popper, - ToolbarChip, + ToolbarLabel, ToolbarFilter, } from '@patternfly/react-core'; import { FilterIcon } from '@patternfly/react-icons'; import { DataViewFilterOption } from '../DataViewFilters'; -const isToolbarChip = (chip: string | ToolbarChip): chip is ToolbarChip => +const isToolbarChip = (chip: string | ToolbarLabel): chip is ToolbarLabel => typeof chip === 'object' && 'key' in chip; export const isDataViewFilterOption = (obj: unknown): obj is DataViewFilterOption => @@ -113,11 +113,11 @@ export const DataViewCheckboxFilter: React.FC = ({ { + labels={value.map(item => { const activeOption = normalizeOptions.find(option => option.value === item); return ({ key: activeOption?.value as string, node: activeOption?.label }) })} - deleteChip={(_, chip) => + deleteLabel={(_, chip) => onChange?.(undefined, value.filter(item => item !== (isToolbarChip(chip) ? chip.key : chip))) } categoryName={title} diff --git a/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap b/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap index e6cede5..1087ae1 100644 --- a/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap +++ b/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap @@ -3,40 +3,40 @@ exports[`DataViewCheckboxFilter component should render correctly 1`] = `
  • -
    Workspace one -
    +
@@ -175,18 +170,26 @@ exports[`DataViewCheckboxFilter component should render correctly 1`] = `
- + +
diff --git a/packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap b/packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap index b8861d0..67a9cec 100644 --- a/packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap +++ b/packages/module/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap @@ -3,71 +3,75 @@ exports[`DataViewFilters component should render correctly 1`] = `
diff --git a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx index ce49a3d..9b4ff34 100644 --- a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx +++ b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx @@ -33,8 +33,8 @@ export const DataViewTextFilter: React.FC = ({ 0 ? [ { key: title, node: value } ] : []} - deleteChip={() => onChange?.(undefined, '')} + labels={value.length > 0 ? [ { key: title, node: value } ] : []} + deleteLabel={() => onChange?.(undefined, '')} categoryName={title} showToolbarItem={showToolbarItem} > diff --git a/packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap b/packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap index 2091670..1d8a389 100644 --- a/packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap +++ b/packages/module/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap @@ -3,41 +3,41 @@ exports[`DataViewTextFilter component should render correctly 1`] = `
@@ -92,84 +96,79 @@ exports[`DataViewTextFilter component should render correctly 1`] = `
  • -
    initial value -
    +
@@ -177,18 +176,26 @@ exports[`DataViewTextFilter component should render correctly 1`] = `
- + +
diff --git a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx index bdb962d..b614aae 100644 --- a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx +++ b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx @@ -28,7 +28,7 @@ export const DataViewToolbar: React.FC = ({ className, oui ); return ( - + {bulkSelect && ( @@ -36,12 +36,12 @@ export const DataViewToolbar: React.FC = ({ className, oui )} {actions && ( - + {actions} )} {filters && ( - + {filters} )} diff --git a/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap b/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap index deb8cc9..c7fef59 100644 --- a/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +++ b/packages/module/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap @@ -263,6 +263,28 @@ exports[`DataViewToolbar component should render correctly 1`] = `
+
+
+ +
+
@@ -527,18 +549,26 @@ exports[`DataViewToolbar component should render correctly 1`] = ` class="pf-v6-c-toolbar__group" />
- + +
From ed4300ddcd1b2421d535084b2d66701a39e5b215 Mon Sep 17 00:00:00 2001 From: Filip Hlavac Date: Fri, 22 Nov 2024 13:32:27 +0100 Subject: [PATCH 23/23] fix: fix cypress tests failing because of the PF upgrade --- README.md | 4 +- cypress/component/DataViewFilters.cy.tsx | 42 ++++++++++--------- cypress/component/DataViewTableSorting.cy.tsx | 16 +++++-- cypress/component/DataViewTextFilter.cy.tsx | 8 ++-- .../Components/DataViewTableExample.tsx | 2 +- .../examples/EventsContext/EventsExample.tsx | 2 +- .../examples/Functionality/Functionality.md | 2 +- .../DataViewCheckboxFilter.tsx | 8 ++-- .../src/DataViewToolbar/DataViewToolbar.tsx | 8 ++-- 9 files changed, 51 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 013f955..da2fdd3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ export interface MyComponentProps { const useStyles = createUseStyles({ myText: { fontFamily: 'monospace', - fontSize: 'var(--pf-v5-global--icon--FontSize--md)', + fontSize: 'var(--pf-v6-global--icon--FontSize--md)', }, }) @@ -126,7 +126,7 @@ When adding/making changes to a component, always make sure your code is tested: ### Styling: - for styling always use JSS - new classNames should be named in camelCase starting with the name of a given component and following with more details clarifying its purpose/component's subsection to which the class is applied (`actionMenu`, `actionMenuDropdown`, `actionMenuDropdownToggle`, etc.) -- do not use `pf-v5-u-XXX` classes, use CSS variables in a custom class instead (styles for the utility classes are not bundled with the standard patternfly.css - it would require the consumer to import also addons.css) +- do not use `pf-v6-u-XXX` classes, use CSS variables in a custom class instead (styles for the utility classes are not bundled with the standard patternfly.css - it would require the consumer to import also addons.css) --- diff --git a/cypress/component/DataViewFilters.cy.tsx b/cypress/component/DataViewFilters.cy.tsx index cd6d058..b0cdea1 100644 --- a/cypress/component/DataViewFilters.cy.tsx +++ b/cypress/component/DataViewFilters.cy.tsx @@ -37,7 +37,7 @@ describe('DataViewFilters', () => { it('renders DataViewFilters with menu and filter items', () => { cy.mount(); cy.get('[data-ouia-component-id="DataViewFilters"]').should('exist'); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Name').should('exist'); cy.contains('Branch').should('exist'); @@ -46,7 +46,7 @@ describe('DataViewFilters', () => { it('can select a filter option', () => { cy.mount(); cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Name'); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Branch').click(); cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Branch'); @@ -54,55 +54,57 @@ describe('DataViewFilters', () => { it('responds to input and clears the filters', () => { cy.mount(); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Name').click(); cy.get('input[placeholder="Filter by name"]').type('Repository one'); - cy.get('.pf-v5-c-chip__text').should('have.length', 1); + cy.get('.pf-v6-c-label__text').should('have.length', 1); cy.get('input[placeholder="Filter by name"]').clear(); - cy.get('.pf-v5-c-chip__text').should('have.length', 0); + cy.get('.pf-v6-c-label__text').should('have.length', 0); }); - it('displays chips for selected filters', () => { + it('displays labels for selected filters', () => { cy.mount(); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Name').click(); cy.get('input[placeholder="Filter by name"]').type('Repository one'); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Branch').click(); cy.get('input[placeholder="Filter by branch"]').type('Main branch'); - cy.get('.pf-v5-c-chip__text').should('have.length', 2); - cy.get('.pf-v5-c-chip__text').eq(0).should('contain.text', 'Repository one'); - cy.get('.pf-v5-c-chip__text').eq(1).should('contain.text', 'Main branch'); + cy.get('.pf-v6-c-label__text').should('have.length', 2); + cy.get('.pf-v6-c-label__text').eq(0).should('contain.text', 'Repository one'); + cy.get('.pf-v6-c-label__text').eq(1).should('contain.text', 'Main branch'); }); - it('removes filters by clicking individual chips', () => { + it('removes filters by clicking individual labels', () => { cy.mount(); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Name').click(); cy.get('input[placeholder="Filter by name"]').type('Repository one'); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Branch').click(); cy.get('input[placeholder="Filter by branch"]').type('Main branch'); - cy.get('[data-ouia-component-id="close"]').should('have.length', 2); + cy.get('[aria-label="Close Repository one"]').should('have.length', 1); + cy.get('[aria-label="Close Main branch"]').should('have.length', 1); - cy.get('[data-ouia-component-id="close"]').first().click(); - cy.get('[data-ouia-component-id="close"]').last().click(); + cy.get('[aria-label="Close Repository one"]').click(); + cy.get('[aria-label="Close Repository one"]').should('have.length', 0); - cy.get('[data-ouia-component-id="close"]').should('have.length', 0); + cy.get('[aria-label="Close Main branch"]').click(); + cy.get('[aria-label="Close Main branch"]').should('have.length', 0); }); it('clears all filters using the clear-all button', () => { cy.mount(); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Name').click(); cy.get('input[placeholder="Filter by name"]').type('Repository one'); - cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v5-c-menu-toggle').click(); + cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click(); cy.contains('Branch').click(); cy.get('input[placeholder="Filter by branch"]').type('Main branch'); diff --git a/cypress/component/DataViewTableSorting.cy.tsx b/cypress/component/DataViewTableSorting.cy.tsx index 79cae0f..6800055 100644 --- a/cypress/component/DataViewTableSorting.cy.tsx +++ b/cypress/component/DataViewTableSorting.cy.tsx @@ -82,11 +82,15 @@ describe('DataViewTable Sorting with Hook', () => { ); - cy.get('[data-ouia-component-id="test-table-th-0"]').click(); + cy.get('[data-ouia-component-id="test-table-th-0"]') + .find('button') + .click(); cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository five'); cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository two'); - cy.get('[data-ouia-component-id="test-table-th-0"]').click(); + cy.get('[data-ouia-component-id="test-table-th-0"]') + .find('button') + .click(); cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository two'); cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository five'); }); @@ -98,11 +102,15 @@ describe('DataViewTable Sorting with Hook', () => { ); - cy.get('[data-ouia-component-id="test-table-th-4"]').click(); + cy.get('[data-ouia-component-id="test-table-th-4"]') + .find('button') + .click(); cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-01'); cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-06'); - cy.get('[data-ouia-component-id="test-table-th-4"]').click(); + cy.get('[data-ouia-component-id="test-table-th-4"]') + .find('button') + .click(); cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-06'); cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-01'); }); diff --git a/cypress/component/DataViewTextFilter.cy.tsx b/cypress/component/DataViewTextFilter.cy.tsx index 8d67ca4..c8c7215 100644 --- a/cypress/component/DataViewTextFilter.cy.tsx +++ b/cypress/component/DataViewTextFilter.cy.tsx @@ -37,14 +37,14 @@ describe('DataViewTextFilter', () => { .should('have.value', 'Repository one'); }); - it('displays a chip when value is present and removes it on delete', () => { + it('displays a label when value is present and removes it on delete', () => { cy.mount(); cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', 'Repository one'); - cy.get('.pf-v5-c-chip__text').contains('Repository one'); - cy.get('.pf-m-chip-group button.pf-v5-c-button.pf-m-plain').click(); + cy.get('.pf-v6-c-label__text').contains('Repository one'); + cy.get('.pf-m-label-group button.pf-v6-c-button.pf-m-plain').click(); - cy.get('.pf-v5-c-chip__text').should('not.exist'); + cy.get('.pf-v6-c-label__text').should('not.exist'); cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', ''); }); diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/DataViewTableExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/DataViewTableExample.tsx index 8805ba9..b9cda39 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/DataViewTableExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Components/DataViewTableExample.tsx @@ -54,7 +54,7 @@ const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspac const columns: DataViewTh[] = [ null, 'Repositories', - { cell: <>Branches }, + { cell: <>Branches }, 'Pull requests', { cell: 'Workspaces', props: { info: { tooltip: 'More information' } } }, { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } }, diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx index 2afd6fb..1447e00 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx @@ -47,7 +47,7 @@ const RepositoryDetail: React.FunctionComponent = ({ sele return ( - + <Title className="pf-v6-u-mb-md" headingLevel="h2" ouiaId="detail-drawer-title"> Detail of {selectedRepo?.name} Branches: {selectedRepo?.branches} diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md index 066c0e1..3e263c5 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md @@ -88,7 +88,7 @@ The `useDataViewSelection` hook manages the selection state of the data view. ``` # Filters -Enables filtering of data records in the data view and displays the applied filter chips. +Enables filtering of data records in the data view and displays the applied filter labels. ### Toolbar usage The data view toolbar can include a set of filters by passing a React node to the `filters` property. You can use predefined components `DataViewFilters`, `DataViewTextFilter` and `DataViewCheckboxFilter` to customize and handle filtering directly in the toolbar. The `DataViewFilters` is a wrapper allowing conditional filtering using multiple attributes. If you need just a single filter, you can use `DataViewTextFilter`, `DataViewCheckboxFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page. diff --git a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx index 86a68ad..e52ff7e 100644 --- a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx +++ b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx @@ -14,8 +14,8 @@ import { import { FilterIcon } from '@patternfly/react-icons'; import { DataViewFilterOption } from '../DataViewFilters'; -const isToolbarChip = (chip: string | ToolbarLabel): chip is ToolbarLabel => - typeof chip === 'object' && 'key' in chip; +const isToolbarLabel = (label: string | ToolbarLabel): label is ToolbarLabel => + typeof label === 'object' && 'key' in label; export const isDataViewFilterOption = (obj: unknown): obj is DataViewFilterOption => !!obj && @@ -117,8 +117,8 @@ export const DataViewCheckboxFilter: React.FC = ({ const activeOption = normalizeOptions.find(option => option.value === item); return ({ key: activeOption?.value as string, node: activeOption?.label }) })} - deleteLabel={(_, chip) => - onChange?.(undefined, value.filter(item => item !== (isToolbarChip(chip) ? chip.key : chip))) + deleteLabel={(_, label) => + onChange?.(undefined, value.filter(item => item !== (isToolbarLabel(label) ? label.key : label))) } categoryName={title} showToolbarItem={showToolbarItem} diff --git a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx index b614aae..1258528 100644 --- a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx +++ b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx @@ -15,11 +15,11 @@ export interface DataViewToolbarProps extends Omit = ({ className, ouiaId = 'DataViewToolbar', bulkSelect, actions, pagination, filters, customChipGroupContent, clearAllFilters, children, ...props }: DataViewToolbarProps) => { +export const DataViewToolbar: React.FC = ({ className, ouiaId = 'DataViewToolbar', bulkSelect, actions, pagination, filters, customLabelGroupContent, clearAllFilters, children, ...props }: DataViewToolbarProps) => { const defaultClearFilters = useRef(