@patternfly/react-core 4.227.6 → 4.227.9
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/CHANGELOG.md +24 -0
- package/dist/esm/components/Menu/MenuItem.d.ts +3 -1
- package/dist/esm/components/Menu/MenuItem.d.ts.map +1 -1
- package/dist/esm/components/Menu/MenuItem.js +2 -2
- package/dist/esm/components/Menu/MenuItem.js.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.d.ts +6 -12
- package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js +12 -8
- package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroup.d.ts +2 -0
- package/dist/esm/components/TextInputGroup/TextInputGroup.d.ts.map +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroup.js +2 -2
- package/dist/esm/components/TextInputGroup/TextInputGroup.js.map +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroupMain.d.ts +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroupMain.d.ts.map +1 -1
- package/dist/js/components/Menu/MenuItem.d.ts +3 -1
- package/dist/js/components/Menu/MenuItem.d.ts.map +1 -1
- package/dist/js/components/Menu/MenuItem.js +2 -2
- package/dist/js/components/Menu/MenuItem.js.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.d.ts +6 -12
- package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.js +12 -8
- package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroup.d.ts +2 -0
- package/dist/js/components/TextInputGroup/TextInputGroup.d.ts.map +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroup.js +2 -2
- package/dist/js/components/TextInputGroup/TextInputGroup.js.map +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroupMain.d.ts +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroupMain.d.ts.map +1 -1
- package/dist/umd/react-core.css +46 -49
- package/dist/umd/react-core.js +17 -12
- package/dist/umd/react-core.min.css +84 -87
- package/dist/umd/react-core.min.js +5 -5
- package/package.json +6 -6
- package/src/components/Menu/MenuItem.tsx +4 -0
- package/src/components/MenuToggle/MenuToggle.tsx +63 -25
- package/src/components/Page/examples/Page.md +10 -615
- package/src/components/Page/examples/PageCenteredSection.tsx +76 -0
- package/src/components/Page/examples/PageGroupSection.tsx +106 -0
- package/src/components/Page/examples/PageHorizontalNav.tsx +43 -0
- package/src/components/Page/examples/PageMainSectionPadding.tsx +72 -0
- package/src/components/Page/examples/PageTertiaryNav.tsx +42 -0
- package/src/components/Page/examples/PageUncontrolledNav.tsx +53 -0
- package/src/components/Page/examples/PageVerticalNav.tsx +65 -0
- package/src/components/Page/examples/PageVerticalNavUsingPageHeaderComponent.tsx +44 -0
- package/src/components/Page/examples/PageWithOrWithoutFill.tsx +67 -0
- package/src/components/Pagination/examples/Pagination.md +8 -355
- package/src/components/Pagination/examples/PaginationBottom.tsx +33 -0
- package/src/components/Pagination/examples/PaginationCompact.tsx +33 -0
- package/src/components/Pagination/examples/PaginationDisabled.tsx +33 -0
- package/src/components/Pagination/examples/PaginationIndeterminate.tsx +40 -0
- package/src/components/Pagination/examples/PaginationNoItems.tsx +32 -0
- package/src/components/Pagination/examples/PaginationOnePage.tsx +32 -0
- package/src/components/Pagination/examples/PaginationSticky.tsx +81 -0
- package/src/components/Pagination/examples/PaginationTop.tsx +32 -0
- package/src/components/Switch/examples/Switch.md +6 -153
- package/src/components/Switch/examples/SwitchBasic.tsx +20 -0
- package/src/components/Switch/examples/SwitchCheckedWithLabel.tsx +22 -0
- package/src/components/Switch/examples/SwitchDisabled.tsx +28 -0
- package/src/components/Switch/examples/SwitchReversed.tsx +21 -0
- package/src/components/Switch/examples/SwitchUncontrolled.tsx +14 -0
- package/src/components/Switch/examples/SwitchWithoutLabel.tsx +12 -0
- package/src/components/TextInputGroup/TextInputGroup.tsx +9 -1
- package/src/components/TextInputGroup/TextInputGroupMain.tsx +1 -1
- package/src/demos/ComposableMenu/ComposableMenu.md +8 -0
- package/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx +196 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Pagination } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const PaginationTop: React.FunctionComponent = () => {
|
|
5
|
+
const [page, setPage] = React.useState(1);
|
|
6
|
+
const [perPage, setPerPage] = React.useState(20);
|
|
7
|
+
|
|
8
|
+
const onSetPage = (_event: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => {
|
|
9
|
+
setPage(newPage);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const onPerPageSelect = (
|
|
13
|
+
_event: React.MouseEvent | React.KeyboardEvent | MouseEvent,
|
|
14
|
+
newPerPage: number,
|
|
15
|
+
newPage: number
|
|
16
|
+
) => {
|
|
17
|
+
setPerPage(newPerPage);
|
|
18
|
+
setPage(newPage);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Pagination
|
|
23
|
+
perPageComponent="button"
|
|
24
|
+
itemCount={523}
|
|
25
|
+
perPage={perPage}
|
|
26
|
+
page={page}
|
|
27
|
+
onSetPage={onSetPage}
|
|
28
|
+
widgetId="pagination-options-menu-top"
|
|
29
|
+
onPerPageSelect={onPerPageSelect}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -10,177 +10,30 @@ ouia: true
|
|
|
10
10
|
|
|
11
11
|
### Basic
|
|
12
12
|
|
|
13
|
-
```
|
|
14
|
-
import React from 'react';
|
|
15
|
-
import { Switch } from '@patternfly/react-core';
|
|
16
|
-
|
|
17
|
-
class SimpleSwitch extends React.Component {
|
|
18
|
-
constructor(props) {
|
|
19
|
-
super(props);
|
|
20
|
-
this.state = {
|
|
21
|
-
isChecked: true
|
|
22
|
-
};
|
|
23
|
-
this.handleChange = isChecked => {
|
|
24
|
-
this.setState({ isChecked });
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
render() {
|
|
29
|
-
const { isChecked } = this.state;
|
|
30
|
-
return (
|
|
31
|
-
<Switch
|
|
32
|
-
id="simple-switch"
|
|
33
|
-
label="Message when on"
|
|
34
|
-
labelOff="Message when off"
|
|
35
|
-
isChecked={isChecked}
|
|
36
|
-
onChange={this.handleChange}
|
|
37
|
-
/>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
13
|
+
```ts file="./SwitchBasic.tsx"
|
|
41
14
|
```
|
|
42
15
|
|
|
43
16
|
### Reversed Layout
|
|
44
17
|
|
|
45
|
-
```
|
|
46
|
-
import React from 'react';
|
|
47
|
-
import { Switch } from '@patternfly/react-core';
|
|
48
|
-
|
|
49
|
-
class ReversedSwitch extends React.Component {
|
|
50
|
-
constructor(props) {
|
|
51
|
-
super(props);
|
|
52
|
-
this.state = {
|
|
53
|
-
isChecked: true
|
|
54
|
-
};
|
|
55
|
-
this.handleChange = isChecked => {
|
|
56
|
-
this.setState({ isChecked });
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
render() {
|
|
61
|
-
const { isChecked } = this.state;
|
|
62
|
-
return (
|
|
63
|
-
<Switch
|
|
64
|
-
id="reversed-switch"
|
|
65
|
-
label="Message when on"
|
|
66
|
-
labelOff="Message when off"
|
|
67
|
-
isChecked={isChecked}
|
|
68
|
-
onChange={this.handleChange}
|
|
69
|
-
isReversed
|
|
70
|
-
/>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
18
|
+
```ts file="./SwitchReversed.tsx"
|
|
74
19
|
```
|
|
75
20
|
|
|
76
21
|
### Without label
|
|
77
22
|
|
|
78
|
-
```
|
|
79
|
-
import React from 'react';
|
|
80
|
-
import { Switch } from '@patternfly/react-core';
|
|
81
|
-
|
|
82
|
-
class NoLabelSwitch extends React.Component {
|
|
83
|
-
constructor(props) {
|
|
84
|
-
super(props);
|
|
85
|
-
this.state = {
|
|
86
|
-
isChecked: true
|
|
87
|
-
};
|
|
88
|
-
this.handleChange = isChecked => {
|
|
89
|
-
this.setState({ isChecked });
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
render() {
|
|
94
|
-
const { isChecked } = this.state;
|
|
95
|
-
return (
|
|
96
|
-
<Switch id="no-label-switch-on" aria-label="Message when on" isChecked={isChecked} onChange={this.handleChange} />
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
23
|
+
```ts file="./SwitchWithoutLabel.tsx"
|
|
100
24
|
```
|
|
101
25
|
|
|
102
26
|
### Checked with label
|
|
103
27
|
|
|
104
|
-
```
|
|
105
|
-
import React from 'react';
|
|
106
|
-
import { Switch } from '@patternfly/react-core';
|
|
107
|
-
|
|
108
|
-
class CheckedWithLabelSwitch extends React.Component {
|
|
109
|
-
constructor(props) {
|
|
110
|
-
super(props);
|
|
111
|
-
this.state = {
|
|
112
|
-
isChecked: true
|
|
113
|
-
};
|
|
114
|
-
this.handleChange = isChecked => {
|
|
115
|
-
this.setState({ isChecked });
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
render() {
|
|
120
|
-
const { isChecked } = this.state;
|
|
121
|
-
return (
|
|
122
|
-
<Switch
|
|
123
|
-
label="Message when on"
|
|
124
|
-
labelOff="Message when off"
|
|
125
|
-
id="checked-with-label-switch-on"
|
|
126
|
-
aria-label="Message when on"
|
|
127
|
-
isChecked={isChecked}
|
|
128
|
-
hasCheckIcon
|
|
129
|
-
onChange={this.handleChange}
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
28
|
+
```ts file="./SwitchCheckedWithLabel.tsx"
|
|
134
29
|
```
|
|
135
30
|
|
|
136
31
|
### Disabled
|
|
137
32
|
|
|
138
|
-
```
|
|
139
|
-
import React from 'react';
|
|
140
|
-
import { Switch } from '@patternfly/react-core';
|
|
141
|
-
|
|
142
|
-
<React.Fragment>
|
|
143
|
-
<Switch
|
|
144
|
-
id="disabled-switch-on"
|
|
145
|
-
aria-label="Message when on"
|
|
146
|
-
label="Message when on"
|
|
147
|
-
labelOff="Message when off"
|
|
148
|
-
isChecked
|
|
149
|
-
isDisabled
|
|
150
|
-
/>
|
|
151
|
-
<br />
|
|
152
|
-
<Switch
|
|
153
|
-
id="disabled-switch-off"
|
|
154
|
-
aria-label="Message when on"
|
|
155
|
-
label="Message when on"
|
|
156
|
-
labelOff="Message when off"
|
|
157
|
-
isChecked={false}
|
|
158
|
-
isDisabled
|
|
159
|
-
/>
|
|
160
|
-
<br />
|
|
161
|
-
<Switch id="disabled-no-label-switch-on" aria-label="Message when on" isChecked isDisabled />
|
|
162
|
-
<br />
|
|
163
|
-
<Switch id="disabled-no-label-switch-off" aria-label="Message when on" isChecked={false} isDisabled />
|
|
164
|
-
</React.Fragment>;
|
|
33
|
+
```ts file="./SwitchDisabled.tsx"
|
|
165
34
|
```
|
|
166
35
|
|
|
167
36
|
### Uncontrolled
|
|
168
37
|
|
|
169
|
-
```
|
|
170
|
-
import React from 'react';
|
|
171
|
-
import { Switch } from '@patternfly/react-core';
|
|
172
|
-
|
|
173
|
-
<React.Fragment>
|
|
174
|
-
<Switch
|
|
175
|
-
id="uncontrolled-switch-no-label"
|
|
176
|
-
aria-label="An uncontrolled switch with no label"
|
|
177
|
-
defaultChecked={false}
|
|
178
|
-
/>
|
|
179
|
-
<br />
|
|
180
|
-
<Switch
|
|
181
|
-
id="uncontrolled-switch-with-label"
|
|
182
|
-
label="An uncontrolled switch"
|
|
183
|
-
defaultChecked={false}
|
|
184
|
-
/>
|
|
185
|
-
</React.Fragment>;
|
|
38
|
+
```ts file="./SwitchUncontrolled.tsx"
|
|
186
39
|
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Switch } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const SwitchBasic: React.FunctionComponent = () => {
|
|
5
|
+
const [isChecked, setIsChecked] = React.useState<boolean>(true);
|
|
6
|
+
|
|
7
|
+
const handleChange = (checked: boolean, _event: React.FormEvent<HTMLInputElement>) => {
|
|
8
|
+
setIsChecked(checked);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Switch
|
|
13
|
+
id="simple-switch"
|
|
14
|
+
label="Message when on"
|
|
15
|
+
labelOff="Message when off"
|
|
16
|
+
isChecked={isChecked}
|
|
17
|
+
onChange={handleChange}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Switch } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const SwitchCheckedWithLabel: React.FunctionComponent = () => {
|
|
5
|
+
const [isChecked, setIsChecked] = React.useState<boolean>(true);
|
|
6
|
+
|
|
7
|
+
const handleChange = (checked: boolean, _event: React.FormEvent<HTMLInputElement>) => {
|
|
8
|
+
setIsChecked(checked);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Switch
|
|
13
|
+
label="Message when on"
|
|
14
|
+
labelOff="Message when off"
|
|
15
|
+
id="checked-with-label-switch-on"
|
|
16
|
+
aria-label="Message when on"
|
|
17
|
+
isChecked={isChecked}
|
|
18
|
+
hasCheckIcon
|
|
19
|
+
onChange={handleChange}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Switch } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const SwitchDisabled: React.FunctionComponent = () => (
|
|
5
|
+
<React.Fragment>
|
|
6
|
+
<Switch
|
|
7
|
+
id="disabled-switch-on"
|
|
8
|
+
aria-label="Message when on"
|
|
9
|
+
label="Message when on"
|
|
10
|
+
labelOff="Message when off"
|
|
11
|
+
isChecked
|
|
12
|
+
isDisabled
|
|
13
|
+
/>
|
|
14
|
+
<br />
|
|
15
|
+
<Switch
|
|
16
|
+
id="disabled-switch-off"
|
|
17
|
+
aria-label="Message when on"
|
|
18
|
+
label="Message when on"
|
|
19
|
+
labelOff="Message when off"
|
|
20
|
+
isChecked={false}
|
|
21
|
+
isDisabled
|
|
22
|
+
/>
|
|
23
|
+
<br />
|
|
24
|
+
<Switch id="disabled-no-label-switch-on" aria-label="Message when on" isChecked isDisabled />
|
|
25
|
+
<br />
|
|
26
|
+
<Switch id="disabled-no-label-switch-off" aria-label="Message when on" isChecked={false} isDisabled />
|
|
27
|
+
</React.Fragment>
|
|
28
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Switch } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const SwitchReversed: React.FunctionComponent = () => {
|
|
5
|
+
const [isChecked, setIsChecked] = React.useState<boolean>(true);
|
|
6
|
+
|
|
7
|
+
const handleChange = (checked: boolean, _event: React.FormEvent<HTMLInputElement>) => {
|
|
8
|
+
setIsChecked(checked);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Switch
|
|
13
|
+
id="reversed-switch"
|
|
14
|
+
label="Message when on"
|
|
15
|
+
labelOff="Message when off"
|
|
16
|
+
isChecked={isChecked}
|
|
17
|
+
onChange={handleChange}
|
|
18
|
+
isReversed
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Switch } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const SwitchUncontrolled: React.FunctionComponent = () => (
|
|
5
|
+
<React.Fragment>
|
|
6
|
+
<Switch
|
|
7
|
+
id="uncontrolled-switch-no-label"
|
|
8
|
+
aria-label="An uncontrolled switch with no label"
|
|
9
|
+
defaultChecked={false}
|
|
10
|
+
/>
|
|
11
|
+
<br />
|
|
12
|
+
<Switch id="uncontrolled-switch-with-label" label="An uncontrolled switch" defaultChecked={false} />
|
|
13
|
+
</React.Fragment>
|
|
14
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Switch } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const SwitchWithoutLabel: React.FunctionComponent = () => {
|
|
5
|
+
const [isChecked, setIsChecked] = React.useState<boolean>(true);
|
|
6
|
+
|
|
7
|
+
const handleChange = (checked: boolean, _event: React.FormEvent<HTMLInputElement>) => {
|
|
8
|
+
setIsChecked(checked);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return <Switch id="no-label-switch-on" aria-label="Message when on" isChecked={isChecked} onChange={handleChange} />;
|
|
12
|
+
};
|
|
@@ -9,6 +9,8 @@ export interface TextInputGroupProps extends React.HTMLProps<HTMLDivElement> {
|
|
|
9
9
|
className?: string;
|
|
10
10
|
/** Adds disabled styling and a disabled context value which text input group main hooks into for the input itself */
|
|
11
11
|
isDisabled?: boolean;
|
|
12
|
+
/** Flag to indicate the toggle has no border or background */
|
|
13
|
+
isPlain?: boolean;
|
|
12
14
|
/** @hide A reference object to attach to the input box */
|
|
13
15
|
innerRef?: React.RefObject<any>;
|
|
14
16
|
}
|
|
@@ -21,6 +23,7 @@ export const TextInputGroup: React.FunctionComponent<TextInputGroupProps> = ({
|
|
|
21
23
|
children,
|
|
22
24
|
className,
|
|
23
25
|
isDisabled,
|
|
26
|
+
isPlain,
|
|
24
27
|
innerRef,
|
|
25
28
|
...props
|
|
26
29
|
}: TextInputGroupProps) => {
|
|
@@ -30,7 +33,12 @@ export const TextInputGroup: React.FunctionComponent<TextInputGroupProps> = ({
|
|
|
30
33
|
<TextInputGroupContext.Provider value={{ isDisabled }}>
|
|
31
34
|
<div
|
|
32
35
|
ref={textInputGroupRef}
|
|
33
|
-
className={css(
|
|
36
|
+
className={css(
|
|
37
|
+
styles.textInputGroup,
|
|
38
|
+
isDisabled && styles.modifiers.disabled,
|
|
39
|
+
isPlain && styles.modifiers.plain,
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
34
42
|
{...props}
|
|
35
43
|
>
|
|
36
44
|
{children}
|
|
@@ -37,7 +37,7 @@ export interface TextInputGroupMainProps extends Omit<React.HTMLProps<HTMLDivEle
|
|
|
37
37
|
value?: string | number;
|
|
38
38
|
/** Placeholder value for the input */
|
|
39
39
|
placeholder?: string;
|
|
40
|
-
/**
|
|
40
|
+
/** A reference object to attach to the input box */
|
|
41
41
|
innerRef?: React.RefObject<any>;
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -20,6 +20,9 @@ import pfIcon from './examples/pf-logo-small.svg';
|
|
|
20
20
|
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
|
|
21
21
|
import CaretDownIcon from '@patternfly/react-icons/dist/esm/icons/caret-down-icon';
|
|
22
22
|
import avatarImg from './examples/avatarImg.svg';
|
|
23
|
+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
24
|
+
import { css } from '@patternfly/react-styles';
|
|
25
|
+
import styles from '@patternfly/react-styles/css/components/Menu/menu';
|
|
23
26
|
|
|
24
27
|
## Demos
|
|
25
28
|
|
|
@@ -45,6 +48,11 @@ Composable menus currently require consumer keyboard handling and use of our und
|
|
|
45
48
|
```ts isBeta file="./examples/ComposableSimpleCheckboxSelect.tsx"
|
|
46
49
|
```
|
|
47
50
|
|
|
51
|
+
### Composable typeahead select
|
|
52
|
+
|
|
53
|
+
```ts file="./examples/ComposableTypeaheadSelect.tsx"
|
|
54
|
+
```
|
|
55
|
+
|
|
48
56
|
### Composable drilldown menu
|
|
49
57
|
|
|
50
58
|
```ts isBeta file="./examples/ComposableDrilldownMenu.tsx"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Menu,
|
|
5
|
+
MenuContent,
|
|
6
|
+
MenuList,
|
|
7
|
+
MenuItem,
|
|
8
|
+
Popper,
|
|
9
|
+
MenuToggle,
|
|
10
|
+
TextInputGroup,
|
|
11
|
+
MenuItemProps,
|
|
12
|
+
Button,
|
|
13
|
+
TextInputGroupUtilities,
|
|
14
|
+
TextInputGroupMain,
|
|
15
|
+
MenuToggleElement
|
|
16
|
+
} from '@patternfly/react-core';
|
|
17
|
+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
18
|
+
import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon';
|
|
19
|
+
|
|
20
|
+
const intitalMenuItems: MenuItemProps[] = [
|
|
21
|
+
{ itemId: 'Option 1', children: 'Option 1' },
|
|
22
|
+
{ itemId: 'Option 2', children: 'Option 2' },
|
|
23
|
+
{ itemId: 'Option 3', children: 'Option 3', icon: <TableIcon aria-hidden /> }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const ComposableTypeaheadSelect: React.FunctionComponent = () => {
|
|
27
|
+
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
|
28
|
+
const [inputValue, setInputValue] = React.useState<string>('');
|
|
29
|
+
const [menuItems, setMenuItems] = React.useState<MenuItemProps[]>(intitalMenuItems);
|
|
30
|
+
const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
|
|
31
|
+
const [isSelected, setIsSelected] = React.useState(false);
|
|
32
|
+
|
|
33
|
+
const menuToggleRef = React.useRef<MenuToggleElement>({} as MenuToggleElement);
|
|
34
|
+
const textInputRef = React.useRef<HTMLInputElement>();
|
|
35
|
+
|
|
36
|
+
React.useEffect(() => {
|
|
37
|
+
let newMenuItems: MenuItemProps[] = intitalMenuItems;
|
|
38
|
+
|
|
39
|
+
// Filter menu items based on the text input value when one exists
|
|
40
|
+
if (inputValue) {
|
|
41
|
+
newMenuItems = intitalMenuItems.filter(menuItem =>
|
|
42
|
+
String(menuItem.children)
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.includes(inputValue.toLowerCase())
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// When no options are found after filtering, display 'No results found'
|
|
48
|
+
if (!newMenuItems.length) {
|
|
49
|
+
newMenuItems = [{ isDisabled: true, children: 'No results found' }];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setMenuItems(newMenuItems);
|
|
54
|
+
}, [inputValue]);
|
|
55
|
+
|
|
56
|
+
const focusOnInput = () => menuToggleRef.current?.querySelector('input')?.focus();
|
|
57
|
+
|
|
58
|
+
const onMenuSelect = (_event: React.MouseEvent<Element, MouseEvent>, itemId: string | number) => {
|
|
59
|
+
if (itemId) {
|
|
60
|
+
setInputValue(itemId.toString());
|
|
61
|
+
setIsSelected(true);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setIsMenuOpen(false);
|
|
65
|
+
setFocusedItemIndex(null);
|
|
66
|
+
focusOnInput();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handleMenuArrowKeys = (key: string) => {
|
|
70
|
+
let indexToFocus;
|
|
71
|
+
|
|
72
|
+
if (isMenuOpen) {
|
|
73
|
+
if (key === 'ArrowUp') {
|
|
74
|
+
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
|
|
75
|
+
if (focusedItemIndex === null || focusedItemIndex === 0) {
|
|
76
|
+
indexToFocus = menuItems.length - 1;
|
|
77
|
+
} else {
|
|
78
|
+
indexToFocus = focusedItemIndex - 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (key === 'ArrowDown') {
|
|
83
|
+
// When no index is set or at the last index, focus to the first, otherwise increment focus index
|
|
84
|
+
if (focusedItemIndex === null || focusedItemIndex === menuItems.length - 1) {
|
|
85
|
+
indexToFocus = 0;
|
|
86
|
+
} else {
|
|
87
|
+
indexToFocus = focusedItemIndex + 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setFocusedItemIndex(indexToFocus);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
96
|
+
const enabledMenuItems = menuItems.filter(menuItem => !menuItem.isDisabled);
|
|
97
|
+
const [firstMenuItem] = enabledMenuItems;
|
|
98
|
+
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
|
|
99
|
+
|
|
100
|
+
switch (event.key) {
|
|
101
|
+
// Select the first available option
|
|
102
|
+
case 'Enter':
|
|
103
|
+
if (isMenuOpen) {
|
|
104
|
+
setInputValue(String(focusedItem.children));
|
|
105
|
+
setIsSelected(true);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setIsMenuOpen(prevIsOpen => !prevIsOpen);
|
|
109
|
+
setFocusedItemIndex(null);
|
|
110
|
+
focusOnInput();
|
|
111
|
+
|
|
112
|
+
break;
|
|
113
|
+
case 'Tab':
|
|
114
|
+
case 'Escape':
|
|
115
|
+
setIsMenuOpen(false);
|
|
116
|
+
break;
|
|
117
|
+
case 'ArrowUp':
|
|
118
|
+
case 'ArrowDown':
|
|
119
|
+
handleMenuArrowKeys(event.key);
|
|
120
|
+
break;
|
|
121
|
+
default:
|
|
122
|
+
!isMenuOpen && setIsMenuOpen(true);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Close the menu when a click occurs outside of the menu toggle content
|
|
127
|
+
const onDocumentClick = (event: MouseEvent) => {
|
|
128
|
+
if (!menuToggleRef.current?.contains(event?.target as HTMLElement)) {
|
|
129
|
+
setIsMenuOpen(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const toggleMenuOpen = () => {
|
|
134
|
+
setIsMenuOpen(prevIsOpen => !prevIsOpen);
|
|
135
|
+
textInputRef.current?.focus();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onTextInputChange = (value: string) => {
|
|
139
|
+
setInputValue(value);
|
|
140
|
+
setIsSelected(false);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<Popper
|
|
145
|
+
trigger={
|
|
146
|
+
<MenuToggle
|
|
147
|
+
variant="typeahead"
|
|
148
|
+
onClick={toggleMenuOpen}
|
|
149
|
+
innerRef={menuToggleRef}
|
|
150
|
+
isExpanded={isMenuOpen}
|
|
151
|
+
isFullWidth
|
|
152
|
+
>
|
|
153
|
+
<TextInputGroup isPlain>
|
|
154
|
+
<TextInputGroupMain
|
|
155
|
+
value={inputValue}
|
|
156
|
+
onClick={toggleMenuOpen}
|
|
157
|
+
onChange={onTextInputChange}
|
|
158
|
+
onKeyDown={onInputKeyDown}
|
|
159
|
+
id="typeahead-select-input"
|
|
160
|
+
autoComplete="off"
|
|
161
|
+
innerRef={textInputRef}
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
<TextInputGroupUtilities>
|
|
165
|
+
{!!inputValue && (
|
|
166
|
+
<Button variant="plain" onClick={() => setInputValue('')} aria-label="Clear input value">
|
|
167
|
+
<TimesIcon aria-hidden />
|
|
168
|
+
</Button>
|
|
169
|
+
)}
|
|
170
|
+
</TextInputGroupUtilities>
|
|
171
|
+
</TextInputGroup>
|
|
172
|
+
</MenuToggle>
|
|
173
|
+
}
|
|
174
|
+
popper={
|
|
175
|
+
<Menu id="select-menu" onSelect={onMenuSelect} selected={isSelected && inputValue}>
|
|
176
|
+
<MenuContent>
|
|
177
|
+
<MenuList>
|
|
178
|
+
{menuItems.map((itemProps, index) => (
|
|
179
|
+
<MenuItem
|
|
180
|
+
key={itemProps.itemId || itemProps.children}
|
|
181
|
+
isFocused={focusedItemIndex === index}
|
|
182
|
+
className={itemProps.className}
|
|
183
|
+
onClick={() => setIsSelected(true)}
|
|
184
|
+
{...itemProps}
|
|
185
|
+
ref={null}
|
|
186
|
+
/>
|
|
187
|
+
))}
|
|
188
|
+
</MenuList>
|
|
189
|
+
</MenuContent>
|
|
190
|
+
</Menu>
|
|
191
|
+
}
|
|
192
|
+
isVisible={isMenuOpen}
|
|
193
|
+
onDocumentClick={onDocumentClick}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
};
|