@patternfly/react-data-view 6.4.0-prerelease.9 → 6.5.0-prerelease.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.d.ts +2 -0
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +29 -1
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.js +83 -0
- package/dist/cjs/DataViewTh/DataViewTh.d.ts +4 -4
- package/dist/cjs/DataViewTh/DataViewTh.js +8 -1
- package/dist/cjs/DataViewToolbar/DataViewToolbar.js +13 -1
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.d.ts +26 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.js +229 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.js +171 -0
- package/dist/cjs/DataViewTreeFilter/index.d.ts +2 -0
- package/dist/cjs/DataViewTreeFilter/index.js +23 -0
- package/dist/cjs/Hooks/selection.d.ts +8 -8
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -1
- package/dist/dynamic/DataViewTreeFilter/package.json +1 -0
- package/dist/dynamic-modules.json +2 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.d.ts +2 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +29 -1
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.js +84 -1
- package/dist/esm/DataViewTh/DataViewTh.d.ts +4 -4
- package/dist/esm/DataViewTh/DataViewTh.js +8 -1
- package/dist/esm/DataViewToolbar/DataViewToolbar.js +13 -1
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.d.ts +26 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.js +225 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.js +166 -0
- package/dist/esm/DataViewTreeFilter/index.d.ts +2 -0
- package/dist/esm/DataViewTreeFilter/index.js +2 -0
- package/dist/esm/Hooks/selection.d.ts +8 -8
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/DataView/PredefinedLayoutFullExample.tsx +2 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/FiltersExample.tsx +2 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/PaginationExample.tsx +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +8 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/TreeFilterExample.tsx +248 -0
- package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +129 -0
- package/src/DataViewTextFilter/DataViewTextFilter.tsx +58 -22
- package/src/DataViewTh/DataViewTh.tsx +15 -7
- package/src/DataViewToolbar/DataViewToolbar.tsx +17 -2
- package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +288 -280
- package/src/DataViewTreeFilter/DataViewTreeFilter.test.tsx +222 -0
- package/src/DataViewTreeFilter/DataViewTreeFilter.tsx +361 -0
- package/src/DataViewTreeFilter/__snapshots__/DataViewTreeFilter.test.tsx.snap +199 -0
- package/src/DataViewTreeFilter/index.ts +2 -0
- package/src/Hooks/selection.ts +8 -8
- package/src/index.ts +3 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { render } from '@testing-library/react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
2
3
|
import DataViewTextFilter, { DataViewTextFilterProps } from './DataViewTextFilter';
|
|
3
4
|
import DataViewToolbar from '../DataViewToolbar';
|
|
4
5
|
|
|
@@ -20,4 +21,132 @@ describe('DataViewTextFilter component', () => {
|
|
|
20
21
|
/>);
|
|
21
22
|
expect(container).toMatchSnapshot();
|
|
22
23
|
});
|
|
24
|
+
|
|
25
|
+
it('should focus the search input when "/" key is pressed and filter is visible', () => {
|
|
26
|
+
render(<DataViewToolbar
|
|
27
|
+
filters={
|
|
28
|
+
<DataViewTextFilter {...defaultProps} showToolbarItem={true} />
|
|
29
|
+
}
|
|
30
|
+
/>);
|
|
31
|
+
|
|
32
|
+
const input = document.getElementById('test-filter') as HTMLInputElement;
|
|
33
|
+
expect(input).toBeInTheDocument();
|
|
34
|
+
|
|
35
|
+
// Simulate pressing "/" key by creating and dispatching a KeyboardEvent
|
|
36
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
37
|
+
key: '/',
|
|
38
|
+
code: 'Slash',
|
|
39
|
+
bubbles: true,
|
|
40
|
+
cancelable: true,
|
|
41
|
+
});
|
|
42
|
+
window.dispatchEvent(keyEvent);
|
|
43
|
+
|
|
44
|
+
// Check that the input has focus
|
|
45
|
+
expect(document.activeElement).toBe(input);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should not focus the search input when "/" key is pressed if filter is not visible', () => {
|
|
49
|
+
render(<DataViewToolbar
|
|
50
|
+
filters={
|
|
51
|
+
<DataViewTextFilter {...defaultProps} showToolbarItem={false} />
|
|
52
|
+
}
|
|
53
|
+
/>);
|
|
54
|
+
|
|
55
|
+
const input = document.getElementById('test-filter') as HTMLInputElement;
|
|
56
|
+
|
|
57
|
+
// Simulate pressing "/" key
|
|
58
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
59
|
+
key: '/',
|
|
60
|
+
code: 'Slash',
|
|
61
|
+
bubbles: true,
|
|
62
|
+
cancelable: true,
|
|
63
|
+
});
|
|
64
|
+
window.dispatchEvent(keyEvent);
|
|
65
|
+
|
|
66
|
+
if (input) {
|
|
67
|
+
expect(document.activeElement).not.toBe(input);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should not focus the search input when "/" key is pressed while typing in another input', () => {
|
|
72
|
+
const { container } = render(
|
|
73
|
+
<div>
|
|
74
|
+
<input data-testid="other-input" />
|
|
75
|
+
<DataViewToolbar
|
|
76
|
+
filters={
|
|
77
|
+
<DataViewTextFilter {...defaultProps} showToolbarItem={true} />
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const otherInput = container.querySelector('[data-testid="other-input"]') as HTMLInputElement;
|
|
84
|
+
|
|
85
|
+
// Focus the other input first
|
|
86
|
+
otherInput.focus();
|
|
87
|
+
expect(document.activeElement).toBe(otherInput);
|
|
88
|
+
|
|
89
|
+
// Simulate pressing "/" key while focused on the other input
|
|
90
|
+
// The event target should be the input element
|
|
91
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
92
|
+
key: '/',
|
|
93
|
+
code: 'Slash',
|
|
94
|
+
bubbles: true,
|
|
95
|
+
cancelable: true,
|
|
96
|
+
});
|
|
97
|
+
Object.defineProperty(keyEvent, 'target', {
|
|
98
|
+
value: otherInput,
|
|
99
|
+
enumerable: true,
|
|
100
|
+
});
|
|
101
|
+
window.dispatchEvent(keyEvent);
|
|
102
|
+
|
|
103
|
+
// The search input should not be focused since we're already in an input field
|
|
104
|
+
expect(document.activeElement).toBe(otherInput);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should not focus the search input when enableShortcut is false', () => {
|
|
108
|
+
render(<DataViewToolbar
|
|
109
|
+
filters={
|
|
110
|
+
<DataViewTextFilter {...defaultProps} showToolbarItem={true} enableShortcut={false} />
|
|
111
|
+
}
|
|
112
|
+
/>);
|
|
113
|
+
|
|
114
|
+
const input = document.getElementById('test-filter') as HTMLInputElement;
|
|
115
|
+
expect(input).toBeInTheDocument();
|
|
116
|
+
|
|
117
|
+
// Simulate pressing "/" key
|
|
118
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
119
|
+
key: '/',
|
|
120
|
+
code: 'Slash',
|
|
121
|
+
bubbles: true,
|
|
122
|
+
cancelable: true,
|
|
123
|
+
});
|
|
124
|
+
window.dispatchEvent(keyEvent);
|
|
125
|
+
|
|
126
|
+
// The input should not be focused since the shortcut is disabled
|
|
127
|
+
expect(document.activeElement).not.toBe(input);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should focus the search input when enableShortcut is true (default)', () => {
|
|
131
|
+
render(<DataViewToolbar
|
|
132
|
+
filters={
|
|
133
|
+
<DataViewTextFilter {...defaultProps} showToolbarItem={true} />
|
|
134
|
+
}
|
|
135
|
+
/>);
|
|
136
|
+
|
|
137
|
+
const input = document.getElementById('test-filter') as HTMLInputElement;
|
|
138
|
+
expect(input).toBeInTheDocument();
|
|
139
|
+
|
|
140
|
+
// Simulate pressing "/" key
|
|
141
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
142
|
+
key: '/',
|
|
143
|
+
code: 'Slash',
|
|
144
|
+
bubbles: true,
|
|
145
|
+
cancelable: true,
|
|
146
|
+
});
|
|
147
|
+
window.dispatchEvent(keyEvent);
|
|
148
|
+
|
|
149
|
+
// The input should be focused since the shortcut is enabled by default
|
|
150
|
+
expect(document.activeElement).toBe(input);
|
|
151
|
+
});
|
|
23
152
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FC } from 'react';
|
|
1
|
+
import { FC, useEffect } from 'react';
|
|
2
2
|
import { SearchInput, SearchInputProps, ToolbarFilter, ToolbarFilterProps } from '@patternfly/react-core';
|
|
3
3
|
|
|
4
4
|
/** extends SearchInputProps */
|
|
@@ -17,6 +17,8 @@ export interface DataViewTextFilterProps extends SearchInputProps {
|
|
|
17
17
|
trimValue?: boolean;
|
|
18
18
|
/** Custom OUIA ID */
|
|
19
19
|
ouiaId?: string;
|
|
20
|
+
/** Enable keyboard shortcut (/) to focus the filter. Defaults to true. */
|
|
21
|
+
enableShortcut?: boolean;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export const DataViewTextFilter: FC<DataViewTextFilterProps> = ({
|
|
@@ -28,27 +30,61 @@ export const DataViewTextFilter: FC<DataViewTextFilterProps> = ({
|
|
|
28
30
|
showToolbarItem,
|
|
29
31
|
trimValue = true,
|
|
30
32
|
ouiaId = 'DataViewTextFilter',
|
|
33
|
+
enableShortcut = true,
|
|
31
34
|
...props
|
|
32
|
-
}: DataViewTextFilterProps) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
);
|
|
35
|
+
}: DataViewTextFilterProps) => {
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!enableShortcut) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
42
|
+
// Only handle "/" key when not typing in an input, textarea, or contenteditable element
|
|
43
|
+
if (event.key === '/' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
44
|
+
const target = event.target as HTMLElement;
|
|
45
|
+
const isInputElement = target.tagName === 'INPUT' ||
|
|
46
|
+
target.tagName === 'TEXTAREA' ||
|
|
47
|
+
target.isContentEditable;
|
|
48
|
+
|
|
49
|
+
// Only focus if the filter is visible and we're not already in an input field
|
|
50
|
+
if (showToolbarItem && !isInputElement) {
|
|
51
|
+
// Find the input element by its ID (searchInputId prop)
|
|
52
|
+
const inputElement = document.getElementById(filterId) as HTMLInputElement;
|
|
53
|
+
if (inputElement) {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
inputElement.focus();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
62
|
+
return () => {
|
|
63
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
64
|
+
};
|
|
65
|
+
}, [showToolbarItem, filterId, enableShortcut]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<ToolbarFilter
|
|
69
|
+
key={ouiaId}
|
|
70
|
+
data-ouia-component-id={ouiaId}
|
|
71
|
+
labels={value.length > 0 ? [ { key: title, node: value } ] : []}
|
|
72
|
+
deleteLabel={() => onChange?.(undefined, '')}
|
|
73
|
+
categoryName={title}
|
|
74
|
+
showToolbarItem={showToolbarItem}
|
|
75
|
+
>
|
|
76
|
+
<SearchInput
|
|
77
|
+
searchInputId={filterId}
|
|
78
|
+
value={value}
|
|
79
|
+
onChange={(e, inputValue) => onChange?.(e, trimValue ? inputValue.trim() : inputValue)}
|
|
80
|
+
onClear={onClear}
|
|
81
|
+
placeholder={`Filter by ${title}`}
|
|
82
|
+
aria-label={`${title ?? filterId} filter`}
|
|
83
|
+
data-ouia-component-id={`${ouiaId}-input`}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
</ToolbarFilter>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
53
89
|
|
|
54
90
|
export default DataViewTextFilter;
|
|
@@ -58,13 +58,13 @@ export interface DataViewThResizableProps {
|
|
|
58
58
|
id: string | number | undefined,
|
|
59
59
|
width: number
|
|
60
60
|
) => void;
|
|
61
|
-
/**
|
|
61
|
+
/** Starting width in pixels of the column */
|
|
62
62
|
width?: number;
|
|
63
|
-
/** Minimum width of the column */
|
|
63
|
+
/** Minimum resize width in pixels of the column */
|
|
64
64
|
minWidth?: number;
|
|
65
|
-
/** Increment for keyboard navigation */
|
|
65
|
+
/** Increment in pixels for keyboard navigation */
|
|
66
66
|
increment?: number;
|
|
67
|
-
/** Increment for keyboard navigation while shift is held */
|
|
67
|
+
/** Increment in pixels for keyboard navigation while shift is held */
|
|
68
68
|
shiftIncrement?: number;
|
|
69
69
|
/** Provides an accessible name for the resizable column via a human readable string. */
|
|
70
70
|
resizeButtonAriaLabel?: string;
|
|
@@ -324,14 +324,22 @@ export const DataViewTh: FC<DataViewThProps> = ({
|
|
|
324
324
|
</Fragment>
|
|
325
325
|
);
|
|
326
326
|
|
|
327
|
+
const classNames: string[] = [];
|
|
328
|
+
if (thProps?.className) {
|
|
329
|
+
classNames.push(thProps.className);
|
|
330
|
+
}
|
|
331
|
+
if (dataViewThClassName) {
|
|
332
|
+
classNames.push(dataViewThClassName);
|
|
333
|
+
}
|
|
334
|
+
|
|
327
335
|
return (
|
|
328
336
|
<Th
|
|
337
|
+
modifier="truncate"
|
|
329
338
|
{...thProps}
|
|
330
339
|
{...props}
|
|
331
|
-
style={width > 0 ? { minWidth: width } : undefined}
|
|
332
340
|
ref={thRef}
|
|
333
|
-
|
|
334
|
-
className={
|
|
341
|
+
style={width > 0 ? { ...thProps?.style, minWidth: width } : thProps?.style}
|
|
342
|
+
className={classNames.length > 0 ? classNames.join(' ') : undefined}
|
|
335
343
|
{...(isResizable && { additionalContent: resizableContent })}
|
|
336
344
|
>
|
|
337
345
|
{content}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FC, PropsWithChildren, useRef } from 'react';
|
|
2
2
|
import { Button, Toolbar, ToolbarContent, ToolbarItem, ToolbarItemVariant, ToolbarProps } from '@patternfly/react-core';
|
|
3
|
+
import { createUseStyles } from 'react-jss';
|
|
3
4
|
|
|
4
5
|
/** extends ToolbarProps */
|
|
5
6
|
export interface DataViewToolbarProps extends Omit<PropsWithChildren<ToolbarProps>, 'ref'> {
|
|
@@ -21,7 +22,19 @@ export interface DataViewToolbarProps extends Omit<PropsWithChildren<ToolbarProp
|
|
|
21
22
|
customLabelGroupContent?: React.ReactNode;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
const useStyles = createUseStyles({
|
|
26
|
+
dataViewToolbarPagination: {
|
|
27
|
+
flexBasis: '100%',
|
|
28
|
+
width: '100%'
|
|
29
|
+
},
|
|
30
|
+
dataViewToolbarPaginationWrapper: {
|
|
31
|
+
flexBasis: '100%',
|
|
32
|
+
width: '100%'
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
24
36
|
export const DataViewToolbar: FC<DataViewToolbarProps> = ({ className, ouiaId = 'DataViewToolbar', bulkSelect, actions, toggleGroup, pagination, filters, customLabelGroupContent, clearAllFilters, children, ...props }: DataViewToolbarProps) => {
|
|
37
|
+
const classes = useStyles();
|
|
25
38
|
const defaultClearFilters = useRef(
|
|
26
39
|
<ToolbarItem>
|
|
27
40
|
<Button ouiaId={`${ouiaId}-clear-all-filters`} variant="link" onClick={clearAllFilters} isInline>
|
|
@@ -53,8 +66,10 @@ export const DataViewToolbar: FC<DataViewToolbarProps> = ({ className, ouiaId =
|
|
|
53
66
|
</ToolbarItem>
|
|
54
67
|
)}
|
|
55
68
|
{pagination && (
|
|
56
|
-
<ToolbarItem variant={ToolbarItemVariant.pagination} data-ouia-component-id={`${ouiaId}-pagination`}>
|
|
57
|
-
{
|
|
69
|
+
<ToolbarItem variant={ToolbarItemVariant.pagination} data-ouia-component-id={`${ouiaId}-pagination`} className={classes.dataViewToolbarPagination}>
|
|
70
|
+
<div className={classes.dataViewToolbarPaginationWrapper}>
|
|
71
|
+
{pagination}
|
|
72
|
+
</div>
|
|
58
73
|
</ToolbarItem>
|
|
59
74
|
)}
|
|
60
75
|
{children}
|