@patternfly/react-core 4.249.0 → 4.250.0
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 +17 -0
- package/dist/esm/next/components/Select/Select.d.ts +28 -0
- package/dist/esm/next/components/Select/Select.d.ts.map +1 -0
- package/dist/esm/next/components/Select/Select.js +62 -0
- package/dist/esm/next/components/Select/Select.js.map +1 -0
- package/dist/esm/next/components/Select/SelectGroup.d.ts +12 -0
- package/dist/esm/next/components/Select/SelectGroup.d.ts.map +1 -0
- package/dist/esm/next/components/Select/SelectGroup.js +10 -0
- package/dist/esm/next/components/Select/SelectGroup.js.map +1 -0
- package/dist/esm/next/components/Select/SelectList.d.ts +10 -0
- package/dist/esm/next/components/Select/SelectList.d.ts.map +1 -0
- package/dist/esm/next/components/Select/SelectList.js +10 -0
- package/dist/esm/next/components/Select/SelectList.js.map +1 -0
- package/dist/esm/next/components/Select/SelectOption.d.ts +20 -0
- package/dist/esm/next/components/Select/SelectOption.d.ts.map +1 -0
- package/dist/esm/next/components/Select/SelectOption.js +10 -0
- package/dist/esm/next/components/Select/SelectOption.js.map +1 -0
- package/dist/esm/next/components/Select/index.d.ts +5 -0
- package/dist/esm/next/components/Select/index.d.ts.map +1 -0
- package/dist/esm/next/components/Select/index.js +5 -0
- package/dist/esm/next/components/Select/index.js.map +1 -0
- package/dist/esm/next/components/Wizard/Wizard.d.ts +9 -7
- package/dist/esm/next/components/Wizard/Wizard.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/Wizard.js +62 -48
- package/dist/esm/next/components/Wizard/Wizard.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardBody.d.ts +2 -2
- package/dist/esm/next/components/Wizard/WizardBody.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardBody.js +1 -1
- package/dist/esm/next/components/Wizard/WizardBody.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardContext.d.ts +21 -20
- package/dist/esm/next/components/Wizard/WizardContext.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardContext.js +63 -32
- package/dist/esm/next/components/Wizard/WizardContext.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardFooter.d.ts +20 -6
- package/dist/esm/next/components/Wizard/WizardFooter.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardFooter.js +13 -4
- package/dist/esm/next/components/Wizard/WizardFooter.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardHeader.d.ts +1 -1
- package/dist/esm/next/components/Wizard/WizardHeader.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardHeader.js +2 -2
- package/dist/esm/next/components/Wizard/WizardHeader.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNav.d.ts +5 -5
- package/dist/esm/next/components/Wizard/WizardNav.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNav.js +3 -4
- package/dist/esm/next/components/Wizard/WizardNav.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNavInternal.d.ts +14 -0
- package/dist/esm/next/components/Wizard/WizardNavInternal.d.ts.map +1 -0
- package/dist/esm/next/components/Wizard/WizardNavInternal.js +49 -0
- package/dist/esm/next/components/Wizard/WizardNavInternal.js.map +1 -0
- package/dist/esm/next/components/Wizard/WizardNavItem.d.ts +12 -8
- package/dist/esm/next/components/Wizard/WizardNavItem.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardNavItem.js +23 -12
- package/dist/esm/next/components/Wizard/WizardNavItem.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardStep.d.ts +22 -6
- package/dist/esm/next/components/Wizard/WizardStep.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardStep.js +20 -1
- package/dist/esm/next/components/Wizard/WizardStep.js.map +1 -1
- package/dist/esm/next/components/Wizard/WizardToggle.d.ts +9 -15
- package/dist/esm/next/components/Wizard/WizardToggle.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/WizardToggle.js +15 -50
- package/dist/esm/next/components/Wizard/WizardToggle.js.map +1 -1
- package/dist/esm/next/components/Wizard/hooks/useWizardFooter.d.ts +2 -2
- package/dist/esm/next/components/Wizard/hooks/useWizardFooter.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/hooks/useWizardFooter.js +3 -3
- package/dist/esm/next/components/Wizard/hooks/useWizardFooter.js.map +1 -1
- package/dist/esm/next/components/Wizard/types.d.ts +37 -46
- package/dist/esm/next/components/Wizard/types.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/types.js +10 -2
- package/dist/esm/next/components/Wizard/types.js.map +1 -1
- package/dist/esm/next/components/Wizard/utils.d.ts +4 -5
- package/dist/esm/next/components/Wizard/utils.d.ts.map +1 -1
- package/dist/esm/next/components/Wizard/utils.js +20 -13
- package/dist/esm/next/components/Wizard/utils.js.map +1 -1
- package/dist/esm/next/components/index.d.ts +2 -1
- package/dist/esm/next/components/index.d.ts.map +1 -1
- package/dist/esm/next/components/index.js +2 -1
- package/dist/esm/next/components/index.js.map +1 -1
- package/dist/js/next/components/Select/Select.d.ts +28 -0
- package/dist/js/next/components/Select/Select.d.ts.map +1 -0
- package/dist/js/next/components/Select/Select.js +65 -0
- package/dist/js/next/components/Select/Select.js.map +1 -0
- package/dist/js/next/components/Select/SelectGroup.d.ts +12 -0
- package/dist/js/next/components/Select/SelectGroup.d.ts.map +1 -0
- package/dist/js/next/components/Select/SelectGroup.js +14 -0
- package/dist/js/next/components/Select/SelectGroup.js.map +1 -0
- package/dist/js/next/components/Select/SelectList.d.ts +10 -0
- package/dist/js/next/components/Select/SelectList.d.ts.map +1 -0
- package/dist/js/next/components/Select/SelectList.js +14 -0
- package/dist/js/next/components/Select/SelectList.js.map +1 -0
- package/dist/js/next/components/Select/SelectOption.d.ts +20 -0
- package/dist/js/next/components/Select/SelectOption.d.ts.map +1 -0
- package/dist/js/next/components/Select/SelectOption.js +14 -0
- package/dist/js/next/components/Select/SelectOption.js.map +1 -0
- package/dist/js/next/components/Select/index.d.ts +5 -0
- package/dist/js/next/components/Select/index.d.ts.map +1 -0
- package/dist/js/next/components/Select/index.js +8 -0
- package/dist/js/next/components/Select/index.js.map +1 -0
- package/dist/js/next/components/Wizard/Wizard.d.ts +9 -7
- package/dist/js/next/components/Wizard/Wizard.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/Wizard.js +59 -45
- package/dist/js/next/components/Wizard/Wizard.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardBody.d.ts +2 -2
- package/dist/js/next/components/Wizard/WizardBody.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardBody.js +1 -1
- package/dist/js/next/components/Wizard/WizardBody.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardContext.d.ts +21 -20
- package/dist/js/next/components/Wizard/WizardContext.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardContext.js +61 -30
- package/dist/js/next/components/Wizard/WizardContext.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardFooter.d.ts +20 -6
- package/dist/js/next/components/Wizard/WizardFooter.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardFooter.js +15 -6
- package/dist/js/next/components/Wizard/WizardFooter.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardHeader.d.ts +1 -1
- package/dist/js/next/components/Wizard/WizardHeader.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardHeader.js +2 -2
- package/dist/js/next/components/Wizard/WizardHeader.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardNav.d.ts +5 -5
- package/dist/js/next/components/Wizard/WizardNav.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardNav.js +3 -4
- package/dist/js/next/components/Wizard/WizardNav.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardNavInternal.d.ts +14 -0
- package/dist/js/next/components/Wizard/WizardNavInternal.d.ts.map +1 -0
- package/dist/js/next/components/Wizard/WizardNavInternal.js +54 -0
- package/dist/js/next/components/Wizard/WizardNavInternal.js.map +1 -0
- package/dist/js/next/components/Wizard/WizardNavItem.d.ts +12 -8
- package/dist/js/next/components/Wizard/WizardNavItem.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardNavItem.js +23 -12
- package/dist/js/next/components/Wizard/WizardNavItem.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardStep.d.ts +22 -6
- package/dist/js/next/components/Wizard/WizardStep.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardStep.js +20 -1
- package/dist/js/next/components/Wizard/WizardStep.js.map +1 -1
- package/dist/js/next/components/Wizard/WizardToggle.d.ts +9 -15
- package/dist/js/next/components/Wizard/WizardToggle.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/WizardToggle.js +15 -51
- package/dist/js/next/components/Wizard/WizardToggle.js.map +1 -1
- package/dist/js/next/components/Wizard/hooks/useWizardFooter.d.ts +2 -2
- package/dist/js/next/components/Wizard/hooks/useWizardFooter.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/hooks/useWizardFooter.js +3 -3
- package/dist/js/next/components/Wizard/hooks/useWizardFooter.js.map +1 -1
- package/dist/js/next/components/Wizard/types.d.ts +37 -46
- package/dist/js/next/components/Wizard/types.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/types.js +12 -3
- package/dist/js/next/components/Wizard/types.js.map +1 -1
- package/dist/js/next/components/Wizard/utils.d.ts +4 -5
- package/dist/js/next/components/Wizard/utils.d.ts.map +1 -1
- package/dist/js/next/components/Wizard/utils.js +24 -15
- package/dist/js/next/components/Wizard/utils.js.map +1 -1
- package/dist/js/next/components/index.d.ts +2 -1
- package/dist/js/next/components/index.d.ts.map +1 -1
- package/dist/js/next/components/index.js +2 -1
- package/dist/js/next/components/index.js.map +1 -1
- package/dist/umd/react-core.min.css +940 -940
- package/package.json +2 -2
- package/src/next/components/Select/Select.tsx +129 -0
- package/src/next/components/Select/SelectGroup.tsx +24 -0
- package/src/next/components/Select/SelectList.tsx +21 -0
- package/src/next/components/Select/SelectOption.tsx +31 -0
- package/src/next/components/Select/examples/Select.md +37 -0
- package/src/next/components/Select/examples/SelectBasic.tsx +54 -0
- package/src/next/components/Select/examples/SelectCheckbox.tsx +67 -0
- package/src/next/components/Select/examples/SelectGrouped.tsx +63 -0
- package/src/next/components/Select/examples/SelectMultiTypeahead.tsx +195 -0
- package/src/next/components/Select/examples/SelectTypeahead.tsx +191 -0
- package/src/next/components/Select/index.ts +4 -0
- package/src/next/components/Wizard/Wizard.tsx +107 -64
- package/src/next/components/Wizard/WizardBody.tsx +4 -4
- package/src/next/components/Wizard/WizardContext.tsx +117 -65
- package/src/next/components/Wizard/WizardFooter.tsx +39 -17
- package/src/next/components/Wizard/WizardHeader.tsx +6 -3
- package/src/next/components/Wizard/WizardNav.tsx +9 -10
- package/src/next/components/Wizard/WizardNavInternal.tsx +137 -0
- package/src/next/components/Wizard/WizardNavItem.tsx +47 -20
- package/src/next/components/Wizard/WizardStep.tsx +52 -7
- package/src/next/components/Wizard/WizardToggle.tsx +24 -142
- package/src/next/components/Wizard/__tests__/Wizard.test.tsx +10 -13
- package/src/next/components/Wizard/__tests__/WizardBody.test.tsx +3 -3
- package/src/next/components/Wizard/__tests__/WizardFooter.test.tsx +11 -14
- package/src/next/components/Wizard/__tests__/WizardStep.test.tsx +100 -0
- package/src/next/components/Wizard/__tests__/WizardToggle.test.tsx +4 -4
- package/src/next/components/Wizard/examples/Wizard.md +40 -15
- package/src/next/components/Wizard/examples/WizardBasic.tsx +2 -2
- package/src/next/components/Wizard/examples/WizardCustomNav.tsx +4 -4
- package/src/next/components/Wizard/examples/WizardKitchenSink.tsx +126 -60
- package/src/next/components/Wizard/hooks/__tests__/useWizardFooter.test.tsx +23 -34
- package/src/next/components/Wizard/hooks/useWizardFooter.tsx +5 -5
- package/src/next/components/Wizard/types.tsx +59 -53
- package/src/next/components/Wizard/utils.ts +25 -16
- package/src/next/components/index.ts +2 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Select, SelectOption, SelectList, SelectOptionProps } from '@patternfly/react-core/next';
|
|
3
|
+
import {
|
|
4
|
+
MenuToggle,
|
|
5
|
+
MenuToggleElement,
|
|
6
|
+
TextInputGroup,
|
|
7
|
+
TextInputGroupMain,
|
|
8
|
+
TextInputGroupUtilities,
|
|
9
|
+
Button
|
|
10
|
+
} from '@patternfly/react-core';
|
|
11
|
+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
12
|
+
|
|
13
|
+
const initialSelectOptions: SelectOptionProps[] = [
|
|
14
|
+
{ itemId: 'Alabama', children: 'Alabama' },
|
|
15
|
+
{ itemId: 'Florida', children: 'Florida' },
|
|
16
|
+
{ itemId: 'New Jersey', children: 'New Jersey' },
|
|
17
|
+
{ itemId: 'New Mexico', children: 'New Mexico' },
|
|
18
|
+
{ itemId: 'New York', children: 'New York' },
|
|
19
|
+
{ itemId: 'North Carolina', children: 'North Carolina' }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const SelectBasic: React.FunctionComponent = () => {
|
|
23
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
24
|
+
const [selected, setSelected] = React.useState<string>('');
|
|
25
|
+
const [inputValue, setInputValue] = React.useState<string>('');
|
|
26
|
+
const [filterValue, setFilterValue] = React.useState<string>('');
|
|
27
|
+
const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(initialSelectOptions);
|
|
28
|
+
const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
|
|
29
|
+
|
|
30
|
+
const menuRef = React.useRef<HTMLDivElement>(null);
|
|
31
|
+
const textInputRef = React.useRef<HTMLInputElement>();
|
|
32
|
+
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
let newSelectOptions: SelectOptionProps[] = initialSelectOptions;
|
|
35
|
+
|
|
36
|
+
// Filter menu items based on the text input value when one exists
|
|
37
|
+
if (filterValue) {
|
|
38
|
+
newSelectOptions = initialSelectOptions.filter(menuItem =>
|
|
39
|
+
String(menuItem.children)
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.includes(filterValue.toLowerCase())
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// When no options are found after filtering, display 'No results found'
|
|
45
|
+
if (!newSelectOptions.length) {
|
|
46
|
+
newSelectOptions = [{ isDisabled: true, children: 'No results found' }];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setSelectOptions(newSelectOptions);
|
|
51
|
+
}, [filterValue]);
|
|
52
|
+
|
|
53
|
+
const onToggleClick = () => {
|
|
54
|
+
setIsOpen(!isOpen);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, itemId: string | number | undefined) => {
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.log('selected', itemId);
|
|
60
|
+
|
|
61
|
+
if (itemId) {
|
|
62
|
+
setInputValue(itemId as string);
|
|
63
|
+
setFilterValue(itemId as string);
|
|
64
|
+
setSelected(itemId as string);
|
|
65
|
+
}
|
|
66
|
+
setIsOpen(false);
|
|
67
|
+
setFocusedItemIndex(null);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const onTextInputChange = (value: string) => {
|
|
71
|
+
setInputValue(value);
|
|
72
|
+
setFilterValue(value);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleMenuArrowKeys = (key: string) => {
|
|
76
|
+
let indexToFocus;
|
|
77
|
+
|
|
78
|
+
if (isOpen) {
|
|
79
|
+
if (key === 'ArrowUp') {
|
|
80
|
+
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
|
|
81
|
+
if (focusedItemIndex === null || focusedItemIndex === 0) {
|
|
82
|
+
indexToFocus = selectOptions.length - 1;
|
|
83
|
+
} else {
|
|
84
|
+
indexToFocus = focusedItemIndex - 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (key === 'ArrowDown') {
|
|
89
|
+
// When no index is set or at the last index, focus to the first, otherwise increment focus index
|
|
90
|
+
if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
|
|
91
|
+
indexToFocus = 0;
|
|
92
|
+
} else {
|
|
93
|
+
indexToFocus = focusedItemIndex + 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setFocusedItemIndex(indexToFocus);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
102
|
+
const enabledMenuItems = selectOptions.filter(menuItem => !menuItem.isDisabled);
|
|
103
|
+
const [firstMenuItem] = enabledMenuItems;
|
|
104
|
+
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
|
|
105
|
+
|
|
106
|
+
switch (event.key) {
|
|
107
|
+
// Select the first available option
|
|
108
|
+
case 'Enter':
|
|
109
|
+
if (isOpen) {
|
|
110
|
+
setInputValue(String(focusedItem.children));
|
|
111
|
+
setSelected(String(focusedItem.children));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setIsOpen(prevIsOpen => !prevIsOpen);
|
|
115
|
+
setFocusedItemIndex(null);
|
|
116
|
+
|
|
117
|
+
break;
|
|
118
|
+
case 'Tab':
|
|
119
|
+
case 'Escape':
|
|
120
|
+
setIsOpen(false);
|
|
121
|
+
break;
|
|
122
|
+
case 'ArrowUp':
|
|
123
|
+
case 'ArrowDown':
|
|
124
|
+
handleMenuArrowKeys(event.key);
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
!isOpen && setIsOpen(true);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
|
132
|
+
<MenuToggle ref={toggleRef} variant="typeahead" onClick={onToggleClick} isExpanded={isOpen} isFullWidth>
|
|
133
|
+
<TextInputGroup isPlain>
|
|
134
|
+
<TextInputGroupMain
|
|
135
|
+
value={inputValue}
|
|
136
|
+
onClick={onToggleClick}
|
|
137
|
+
onChange={onTextInputChange}
|
|
138
|
+
onKeyDown={onInputKeyDown}
|
|
139
|
+
id="typeahead-select-input"
|
|
140
|
+
autoComplete="off"
|
|
141
|
+
innerRef={textInputRef}
|
|
142
|
+
placeholder="Select a state"
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
<TextInputGroupUtilities>
|
|
146
|
+
{!!inputValue && (
|
|
147
|
+
<Button
|
|
148
|
+
variant="plain"
|
|
149
|
+
onClick={() => {
|
|
150
|
+
setSelected('');
|
|
151
|
+
setInputValue('');
|
|
152
|
+
setFilterValue('');
|
|
153
|
+
}}
|
|
154
|
+
aria-label="Clear input value"
|
|
155
|
+
>
|
|
156
|
+
<TimesIcon aria-hidden />
|
|
157
|
+
</Button>
|
|
158
|
+
)}
|
|
159
|
+
</TextInputGroupUtilities>
|
|
160
|
+
</TextInputGroup>
|
|
161
|
+
</MenuToggle>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<Select
|
|
166
|
+
id="typeahead-select"
|
|
167
|
+
ref={menuRef}
|
|
168
|
+
isOpen={isOpen}
|
|
169
|
+
selected={selected}
|
|
170
|
+
onSelect={onSelect}
|
|
171
|
+
onOpenChange={() => {
|
|
172
|
+
setIsOpen(false);
|
|
173
|
+
setFilterValue('');
|
|
174
|
+
}}
|
|
175
|
+
toggle={toggle}
|
|
176
|
+
>
|
|
177
|
+
<SelectList>
|
|
178
|
+
{selectOptions.map((option, index) => (
|
|
179
|
+
<SelectOption
|
|
180
|
+
key={option.itemId || option.children}
|
|
181
|
+
isFocused={focusedItemIndex === index}
|
|
182
|
+
className={option.className}
|
|
183
|
+
onClick={() => setSelected(option.itemId)}
|
|
184
|
+
{...option}
|
|
185
|
+
ref={null}
|
|
186
|
+
/>
|
|
187
|
+
))}
|
|
188
|
+
</SelectList>
|
|
189
|
+
</Select>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import findLastIndex from 'lodash/findLastIndex';
|
|
2
3
|
|
|
3
4
|
import { css } from '@patternfly/react-styles';
|
|
4
5
|
import styles from '@patternfly/react-styles/css/components/Wizard/wizard';
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
|
-
DefaultWizardFooterProps,
|
|
8
|
-
DefaultWizardNavProps,
|
|
9
8
|
isWizardParentStep,
|
|
10
9
|
WizardNavStepFunction,
|
|
11
|
-
|
|
10
|
+
WizardControlStep,
|
|
11
|
+
isCustomWizardNav,
|
|
12
|
+
WizardFooterType,
|
|
13
|
+
WizardNavType
|
|
12
14
|
} from './types';
|
|
13
15
|
import { buildSteps, normalizeNavStep } from './utils';
|
|
14
|
-
import { WizardContextProvider } from './WizardContext';
|
|
16
|
+
import { useWizardContext, WizardContextProvider } from './WizardContext';
|
|
15
17
|
import { WizardStepProps } from './WizardStep';
|
|
16
|
-
import {
|
|
18
|
+
import { WizardToggle } from './WizardToggle';
|
|
19
|
+
import { WizardNavInternal } from './WizardNavInternal';
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Wrapper for all steps and hosts state, including navigation helpers, within context.
|
|
@@ -26,9 +29,9 @@ export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
|
|
|
26
29
|
/** Wizard header */
|
|
27
30
|
header?: React.ReactNode;
|
|
28
31
|
/** Wizard footer */
|
|
29
|
-
footer?:
|
|
30
|
-
/** Wizard
|
|
31
|
-
nav?:
|
|
32
|
+
footer?: WizardFooterType;
|
|
33
|
+
/** Wizard navigation */
|
|
34
|
+
nav?: WizardNavType;
|
|
32
35
|
/** The initial index the wizard is to start on (1 or higher). Defaults to 1. */
|
|
33
36
|
startIndex?: number;
|
|
34
37
|
/** Additional classes spread to the wizard */
|
|
@@ -37,9 +40,11 @@ export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
|
|
|
37
40
|
width?: number | string;
|
|
38
41
|
/** Custom height of the wizard */
|
|
39
42
|
height?: number | string;
|
|
43
|
+
/** Disables navigation items that haven't been visited. Defaults to false */
|
|
44
|
+
isStepVisitRequired?: boolean;
|
|
40
45
|
/** Flag to unmount inactive steps instead of hiding. Defaults to true */
|
|
41
|
-
|
|
42
|
-
/** Callback function when a step in the
|
|
46
|
+
hasUnmountedSteps?: boolean;
|
|
47
|
+
/** Callback function when a step in the navigation is clicked */
|
|
43
48
|
onNavByIndex?: WizardNavStepFunction;
|
|
44
49
|
/** Callback function after next button is clicked */
|
|
45
50
|
onNext?: WizardNavStepFunction;
|
|
@@ -52,7 +57,6 @@ export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
|
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
export const Wizard = ({
|
|
55
|
-
startIndex = 1,
|
|
56
60
|
children,
|
|
57
61
|
footer,
|
|
58
62
|
height,
|
|
@@ -60,7 +64,9 @@ export const Wizard = ({
|
|
|
60
64
|
className,
|
|
61
65
|
header,
|
|
62
66
|
nav,
|
|
63
|
-
|
|
67
|
+
startIndex = 1,
|
|
68
|
+
isStepVisitRequired = false,
|
|
69
|
+
hasUnmountedSteps = true,
|
|
64
70
|
onNavByIndex,
|
|
65
71
|
onNext,
|
|
66
72
|
onBack,
|
|
@@ -69,83 +75,91 @@ export const Wizard = ({
|
|
|
69
75
|
...wrapperProps
|
|
70
76
|
}: WizardProps) => {
|
|
71
77
|
const [currentStepIndex, setCurrentStepIndex] = React.useState(startIndex);
|
|
72
|
-
const
|
|
78
|
+
const initialSteps = buildSteps(children);
|
|
73
79
|
|
|
74
|
-
const
|
|
75
|
-
const
|
|
80
|
+
const goToNextStep = (steps: WizardControlStep[] = initialSteps) => {
|
|
81
|
+
const newStepIndex =
|
|
82
|
+
steps.findIndex((step, index) => index + 1 > currentStepIndex && !step.isHidden && !isWizardParentStep(step)) + 1;
|
|
76
83
|
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
} else if (index > lastStepIndex) {
|
|
80
|
-
index = lastStepIndex;
|
|
84
|
+
if (currentStepIndex >= steps.length || !newStepIndex) {
|
|
85
|
+
return onSave ? onSave() : onClose?.();
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
const currStep = steps[
|
|
88
|
+
const currStep = isWizardParentStep(steps[currentStepIndex])
|
|
89
|
+
? steps[currentStepIndex + 1]
|
|
90
|
+
: steps[currentStepIndex];
|
|
84
91
|
const prevStep = steps[currentStepIndex - 1];
|
|
85
|
-
setCurrentStepIndex(index);
|
|
86
92
|
|
|
87
|
-
|
|
93
|
+
setCurrentStepIndex(newStepIndex);
|
|
94
|
+
|
|
95
|
+
return onNext?.(normalizeNavStep(currStep, steps), normalizeNavStep(prevStep, steps));
|
|
88
96
|
};
|
|
89
97
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
const goToPrevStep = (steps: WizardControlStep[] = initialSteps) => {
|
|
99
|
+
const newStepIndex =
|
|
100
|
+
findLastIndex(
|
|
101
|
+
steps,
|
|
102
|
+
(step: WizardControlStep, index: number) =>
|
|
103
|
+
index + 1 < currentStepIndex && !step.isHidden && !isWizardParentStep(step)
|
|
104
|
+
) + 1;
|
|
105
|
+
const currStep = isWizardParentStep(steps[currentStepIndex - 2])
|
|
106
|
+
? steps[currentStepIndex - 3]
|
|
107
|
+
: steps[currentStepIndex - 2];
|
|
108
|
+
const prevStep = steps[currentStepIndex - 1];
|
|
96
109
|
|
|
97
|
-
|
|
98
|
-
}
|
|
110
|
+
setCurrentStepIndex(newStepIndex);
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
return onBack?.(normalizeNavStep(currStep, steps), normalizeNavStep(prevStep, steps));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const goToStepByIndex = (steps: WizardControlStep[] = initialSteps, index: number) => {
|
|
116
|
+
const lastStepIndex = steps.length + 1;
|
|
103
117
|
|
|
104
|
-
//
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
// Handle index when out of bounds or hidden
|
|
119
|
+
if (index < 1) {
|
|
120
|
+
index = 1;
|
|
121
|
+
} else if (index > lastStepIndex) {
|
|
122
|
+
index = lastStepIndex;
|
|
123
|
+
} else if (steps[index - 1].isHidden) {
|
|
124
|
+
// eslint-disable-next-line no-console
|
|
125
|
+
console.error('Wizard: Unable to navigate to hidden step.');
|
|
108
126
|
}
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
|
|
128
|
+
const currStep = steps[index - 1];
|
|
129
|
+
const prevStep = steps[currentStepIndex - 1];
|
|
130
|
+
setCurrentStepIndex(index);
|
|
131
|
+
|
|
132
|
+
return onNavByIndex?.(normalizeNavStep(currStep, steps), normalizeNavStep(prevStep, steps));
|
|
112
133
|
};
|
|
113
134
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
const goToStepById = (steps: WizardControlStep[] = initialSteps, id: number | string) => {
|
|
136
|
+
const stepIndex = steps.findIndex(step => step.id === id) + 1;
|
|
137
|
+
|
|
138
|
+
if (stepIndex > 0 && stepIndex < steps.length + 1 && !steps[stepIndex].isHidden) {
|
|
139
|
+
setCurrentStepIndex(stepIndex);
|
|
118
140
|
} else {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const prevStep = steps[currentStepIndex - 1];
|
|
122
|
-
|
|
123
|
-
// // Skip parent step and focus on the step prior
|
|
124
|
-
if (isWizardParentStep(currStep)) {
|
|
125
|
-
newStepIndex -= 1;
|
|
126
|
-
currStep = steps[currentStepIndex - 3];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
setCurrentStepIndex(newStepIndex);
|
|
130
|
-
return onBack?.(normalizeNavStep(currStep), normalizeNavStep(prevStep));
|
|
141
|
+
// eslint-disable-next-line no-console
|
|
142
|
+
console.error(`Wizard: Unable to navigate to step with id: ${id}.`);
|
|
131
143
|
}
|
|
132
144
|
};
|
|
133
145
|
|
|
134
|
-
const
|
|
135
|
-
const stepIndex =
|
|
136
|
-
stepIndex > 0 && setCurrentStepIndex(stepIndex);
|
|
137
|
-
};
|
|
146
|
+
const goToStepByName = (steps: WizardControlStep[] = initialSteps, name: string) => {
|
|
147
|
+
const stepIndex = initialSteps.findIndex(step => step.name === name) + 1;
|
|
138
148
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
if (stepIndex > 0 && stepIndex < steps.length + 1 && !steps[stepIndex].isHidden) {
|
|
150
|
+
setCurrentStepIndex(stepIndex);
|
|
151
|
+
} else {
|
|
152
|
+
// eslint-disable-next-line no-console
|
|
153
|
+
console.error(`Wizard: Unable to navigate to step with name: ${name}.`);
|
|
154
|
+
}
|
|
142
155
|
};
|
|
143
156
|
|
|
144
157
|
return (
|
|
145
158
|
<WizardContextProvider
|
|
146
|
-
steps={
|
|
159
|
+
steps={initialSteps}
|
|
147
160
|
currentStepIndex={currentStepIndex}
|
|
148
161
|
footer={footer}
|
|
162
|
+
isStepVisitRequired={isStepVisitRequired}
|
|
149
163
|
onNext={goToNextStep}
|
|
150
164
|
onBack={goToPrevStep}
|
|
151
165
|
onClose={onClose}
|
|
@@ -162,10 +176,39 @@ export const Wizard = ({
|
|
|
162
176
|
{...wrapperProps}
|
|
163
177
|
>
|
|
164
178
|
{header}
|
|
165
|
-
<
|
|
179
|
+
<WizardInternal nav={nav} hasUnmountedSteps={hasUnmountedSteps} isStepVisitRequired={isStepVisitRequired} />
|
|
166
180
|
</div>
|
|
167
181
|
</WizardContextProvider>
|
|
168
182
|
);
|
|
169
183
|
};
|
|
170
184
|
|
|
185
|
+
const WizardInternal = ({
|
|
186
|
+
nav,
|
|
187
|
+
hasUnmountedSteps,
|
|
188
|
+
isStepVisitRequired
|
|
189
|
+
}: Pick<WizardProps, 'nav' | 'hasUnmountedSteps' | 'isStepVisitRequired'>) => {
|
|
190
|
+
const { currentStep, steps, footer, goToStepByIndex } = useWizardContext();
|
|
191
|
+
const [isNavExpanded, setIsNavExpanded] = React.useState(false);
|
|
192
|
+
|
|
193
|
+
const wizardNav = React.useMemo(() => {
|
|
194
|
+
if (isCustomWizardNav(nav)) {
|
|
195
|
+
return typeof nav === 'function' ? nav(isNavExpanded, steps, currentStep, goToStepByIndex) : nav;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return <WizardNavInternal nav={nav} isNavExpanded={isNavExpanded} isStepVisitRequired={isStepVisitRequired} />;
|
|
199
|
+
}, [currentStep, isStepVisitRequired, goToStepByIndex, isNavExpanded, nav, steps]);
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<WizardToggle
|
|
203
|
+
nav={wizardNav}
|
|
204
|
+
footer={footer}
|
|
205
|
+
steps={steps}
|
|
206
|
+
currentStep={currentStep}
|
|
207
|
+
isNavExpanded={isNavExpanded}
|
|
208
|
+
toggleNavExpanded={() => setIsNavExpanded(prevIsExpanded => !prevIsExpanded)}
|
|
209
|
+
hasUnmountedSteps={hasUnmountedSteps}
|
|
210
|
+
/>
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
171
214
|
Wizard.displayName = 'Wizard';
|
|
@@ -16,7 +16,7 @@ export interface WizardBodyProps {
|
|
|
16
16
|
/** Sets the aria-labelledby attribute for the wrapper element */
|
|
17
17
|
'aria-labelledby'?: string;
|
|
18
18
|
/** Component used as the wrapping content container */
|
|
19
|
-
|
|
19
|
+
component?: React.ElementType;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const WizardBody = ({
|
|
@@ -24,11 +24,11 @@ export const WizardBody = ({
|
|
|
24
24
|
hasNoBodyPadding = false,
|
|
25
25
|
'aria-label': ariaLabel,
|
|
26
26
|
'aria-labelledby': ariaLabelledBy,
|
|
27
|
-
|
|
27
|
+
component: WrapperComponent = 'div'
|
|
28
28
|
}: WizardBodyProps) => (
|
|
29
|
-
<
|
|
29
|
+
<WrapperComponent aria-label={ariaLabel} aria-labelledby={ariaLabelledBy} className={css(styles.wizardMain)}>
|
|
30
30
|
<div className={css(styles.wizardMainBody, hasNoBodyPadding && styles.modifiers.noPadding)}>{children}</div>
|
|
31
|
-
</
|
|
31
|
+
</WrapperComponent>
|
|
32
32
|
);
|
|
33
33
|
|
|
34
34
|
WizardBody.displayName = 'WizardBody';
|