@patternfly/react-core 4.222.12 → 4.222.15
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/CalendarMonth/CalendarMonth.d.ts +3 -1
- package/dist/esm/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
- package/dist/esm/components/CalendarMonth/CalendarMonth.js +21 -14
- package/dist/esm/components/CalendarMonth/CalendarMonth.js.map +1 -1
- package/dist/esm/components/DatePicker/DatePicker.d.ts +14 -4
- package/dist/esm/components/DatePicker/DatePicker.d.ts.map +1 -1
- package/dist/esm/components/DatePicker/DatePicker.js +20 -8
- package/dist/esm/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/esm/components/Label/Label.js +3 -3
- package/dist/esm/components/Label/Label.js.map +1 -1
- package/dist/esm/components/LabelGroup/LabelGroup.d.ts +2 -0
- package/dist/esm/components/LabelGroup/LabelGroup.d.ts.map +1 -1
- package/dist/esm/components/LabelGroup/LabelGroup.js +4 -3
- package/dist/esm/components/LabelGroup/LabelGroup.js.map +1 -1
- package/dist/esm/components/Popover/Popover.js +2 -2
- package/dist/esm/components/Popover/Popover.js.map +1 -1
- package/dist/esm/components/TimePicker/TimePicker.d.ts +6 -1
- package/dist/esm/components/TimePicker/TimePicker.d.ts.map +1 -1
- package/dist/esm/components/TimePicker/TimePicker.js +32 -19
- package/dist/esm/components/TimePicker/TimePicker.js.map +1 -1
- package/dist/js/components/CalendarMonth/CalendarMonth.d.ts +3 -1
- package/dist/js/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
- package/dist/js/components/CalendarMonth/CalendarMonth.js +21 -14
- package/dist/js/components/CalendarMonth/CalendarMonth.js.map +1 -1
- package/dist/js/components/DatePicker/DatePicker.d.ts +14 -4
- package/dist/js/components/DatePicker/DatePicker.d.ts.map +1 -1
- package/dist/js/components/DatePicker/DatePicker.js +20 -8
- package/dist/js/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/js/components/Label/Label.js +3 -3
- package/dist/js/components/Label/Label.js.map +1 -1
- package/dist/js/components/LabelGroup/LabelGroup.d.ts +2 -0
- package/dist/js/components/LabelGroup/LabelGroup.d.ts.map +1 -1
- package/dist/js/components/LabelGroup/LabelGroup.js +4 -3
- package/dist/js/components/LabelGroup/LabelGroup.js.map +1 -1
- package/dist/js/components/Popover/Popover.js +1 -1
- package/dist/js/components/Popover/Popover.js.map +1 -1
- package/dist/js/components/TimePicker/TimePicker.d.ts +6 -1
- package/dist/js/components/TimePicker/TimePicker.d.ts.map +1 -1
- package/dist/js/components/TimePicker/TimePicker.js +32 -19
- package/dist/js/components/TimePicker/TimePicker.js.map +1 -1
- package/dist/styles/base-no-reset.css +5 -0
- package/dist/styles/base.css +5 -0
- package/dist/umd/react-core.css +255 -28
- package/dist/umd/react-core.js +100 -51
- package/dist/umd/react-core.min.css +255 -28
- package/dist/umd/react-core.min.js +5 -5
- package/package.json +6 -6
- package/src/components/CalendarMonth/CalendarMonth.tsx +21 -13
- package/src/components/DatePicker/DatePicker.tsx +36 -11
- package/src/components/Label/Label.tsx +3 -3
- package/src/components/LabelGroup/LabelGroup.tsx +5 -1
- package/src/components/LabelGroup/examples/LabelGroup.md +7 -0
- package/src/components/LabelGroup/examples/LabelGroupEditableAdd.tsx +74 -0
- package/src/components/LabelGroup/examples/LabelGroupEditableAddDropdown.tsx +138 -0
- package/src/components/LabelGroup/examples/LabelGroupEditableAddModal.tsx +356 -0
- package/src/components/Modal/examples/Modal.md +2 -0
- package/src/components/OverflowMenu/examples/OverflowMenu.md +8 -0
- package/src/components/Popover/Popover.tsx +2 -2
- package/src/components/TimePicker/TimePicker.tsx +36 -19
- package/src/components/Toolbar/examples/Toolbar.md +4 -0
- package/src/demos/Card/Card.md +2 -0
- package/src/demos/{DatePicker.md → DatePicker/DatePicker.md} +8 -0
- package/src/demos/DatePicker/examples/DateTimePickerInModal.tsx +51 -0
- package/src/demos/LabelGroupDemos.md +18 -0
- package/src/demos/PrimaryDetail.md +10 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/react-core",
|
|
3
|
-
"version": "4.222.
|
|
3
|
+
"version": "4.222.15",
|
|
4
4
|
"description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
|
|
5
5
|
"main": "dist/js/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"generate": "node scripts/copyStyles.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@patternfly/react-icons": "^4.73.
|
|
38
|
-
"@patternfly/react-styles": "^4.72.
|
|
39
|
-
"@patternfly/react-tokens": "^4.74.
|
|
37
|
+
"@patternfly/react-icons": "^4.73.15",
|
|
38
|
+
"@patternfly/react-styles": "^4.72.15",
|
|
39
|
+
"@patternfly/react-tokens": "^4.74.15",
|
|
40
40
|
"focus-trap": "6.9.2",
|
|
41
41
|
"react-dropzone": "9.0.0",
|
|
42
42
|
"tippy.js": "5.1.2",
|
|
43
43
|
"tslib": "^2.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@patternfly/patternfly": "4.
|
|
46
|
+
"@patternfly/patternfly": "4.200.0",
|
|
47
47
|
"@rollup/plugin-commonjs": "^21.0.0",
|
|
48
48
|
"@rollup/plugin-node-resolve": "^13.0.0",
|
|
49
49
|
"@rollup/plugin-replace": "^3.0.0",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"react": "^16.8.0 || ^17.0.0",
|
|
61
61
|
"react-dom": "^16.8.0 || ^17.0.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "95acf13e41106a55890dd36b6764104a0b8f4b63"
|
|
64
64
|
}
|
|
@@ -55,6 +55,8 @@ export interface CalendarProps extends CalendarFormat, Omit<React.HTMLProps<HTML
|
|
|
55
55
|
className?: string;
|
|
56
56
|
/** @hide Internal prop to allow pressing escape in select menu to not close popover */
|
|
57
57
|
onSelectToggle?: (open: boolean) => void;
|
|
58
|
+
/** Flag to set browser focus on the passed date **/
|
|
59
|
+
isDateFocused?: boolean;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// Must be numeric given current header design
|
|
@@ -110,19 +112,24 @@ export const CalendarMonth = ({
|
|
|
110
112
|
nextMonthAriaLabel = 'Next month',
|
|
111
113
|
yearInputAriaLabel = 'Select year',
|
|
112
114
|
cellAriaLabel,
|
|
115
|
+
isDateFocused = false,
|
|
113
116
|
...props
|
|
114
117
|
}: CalendarProps) => {
|
|
115
118
|
const longMonths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(monthNum => new Date(1990, monthNum)).map(monthFormat);
|
|
116
119
|
const [isSelectOpen, setIsSelectOpen] = React.useState(false);
|
|
117
120
|
// eslint-disable-next-line prefer-const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (isValidDate(
|
|
121
|
-
|
|
121
|
+
const [focusedDate, setFocusedDate] = React.useState(() => {
|
|
122
|
+
const initDate = new Date(dateProp);
|
|
123
|
+
if (isValidDate(initDate)) {
|
|
124
|
+
return initDate;
|
|
122
125
|
} else {
|
|
123
|
-
|
|
126
|
+
if (isValidDate(rangeStart)) {
|
|
127
|
+
return rangeStart;
|
|
128
|
+
} else {
|
|
129
|
+
return today;
|
|
130
|
+
}
|
|
124
131
|
}
|
|
125
|
-
}
|
|
132
|
+
});
|
|
126
133
|
const [hoveredDate, setHoveredDate] = React.useState(new Date(focusedDate));
|
|
127
134
|
const focusRef = React.useRef<HTMLButtonElement>();
|
|
128
135
|
const [hiddenMonthId] = React.useState(getUniqueId('hidden-month-span'));
|
|
@@ -131,21 +138,22 @@ export const CalendarMonth = ({
|
|
|
131
138
|
const isValidated = (date: Date) => validators.every(validator => validator(date));
|
|
132
139
|
const focusedDateValidated = isValidated(focusedDate);
|
|
133
140
|
useEffect(() => {
|
|
134
|
-
if (
|
|
141
|
+
if (isValidDate(dateProp) && !isSameDate(focusedDate, dateProp)) {
|
|
135
142
|
setFocusedDate(dateProp);
|
|
143
|
+
} else if (!dateProp) {
|
|
144
|
+
setFocusedDate(today);
|
|
136
145
|
}
|
|
137
146
|
}, [dateProp]);
|
|
138
147
|
|
|
139
148
|
useEffect(() => {
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
149
|
+
// Calendar month should not be focused on page load
|
|
150
|
+
// Datepicker should place focus in calendar month when opened
|
|
151
|
+
if ((shouldFocus || isDateFocused) && focusedDateValidated && focusRef.current) {
|
|
152
|
+
focusRef.current.focus();
|
|
145
153
|
} else {
|
|
146
154
|
setShouldFocus(true);
|
|
147
155
|
}
|
|
148
|
-
}, [focusedDate]);
|
|
156
|
+
}, [focusedDate, isDateFocused, focusedDateValidated, focusRef]);
|
|
149
157
|
|
|
150
158
|
const onMonthClick = (newDate: Date) => {
|
|
151
159
|
setFocusedDate(newDate);
|
|
@@ -8,6 +8,7 @@ import { InputGroup } from '../InputGroup/InputGroup';
|
|
|
8
8
|
import OutlinedCalendarAltIcon from '@patternfly/react-icons/dist/esm/icons/outlined-calendar-alt-icon';
|
|
9
9
|
import { CalendarMonth, CalendarFormat, isValidDate } from '../CalendarMonth';
|
|
10
10
|
import { useImperativeHandle } from 'react';
|
|
11
|
+
import { KeyTypes } from '../../helpers';
|
|
11
12
|
|
|
12
13
|
export interface DatePickerProps
|
|
13
14
|
extends CalendarFormat,
|
|
@@ -36,8 +37,13 @@ export interface DatePickerProps
|
|
|
36
37
|
helperText?: React.ReactNode;
|
|
37
38
|
/** Aria label for the button to open the date picker */
|
|
38
39
|
buttonAriaLabel?: string;
|
|
39
|
-
/** The
|
|
40
|
-
|
|
40
|
+
/** The container to append the menu to. Defaults to 'parent'.
|
|
41
|
+
* If your menu is being cut off you can append it to an element higher up the DOM tree.
|
|
42
|
+
* Some examples:
|
|
43
|
+
* menuAppendTo={() => document.body}
|
|
44
|
+
* menuAppendTo={document.getElementById('target')}
|
|
45
|
+
*/
|
|
46
|
+
appendTo?: HTMLElement | ((ref?: HTMLElement) => HTMLElement) | 'parent';
|
|
41
47
|
/** Props to pass to the Popover */
|
|
42
48
|
popoverProps?: Omit<PopoverProps, 'appendTo'>;
|
|
43
49
|
/** Functions that returns an error message if a date is invalid */
|
|
@@ -49,8 +55,13 @@ export interface DatePickerProps
|
|
|
49
55
|
export interface DatePickerRef {
|
|
50
56
|
/** Sets the calendar open status */
|
|
51
57
|
setCalendarOpen: (isOpen: boolean) => void;
|
|
52
|
-
/** Toggles the calendar open status
|
|
53
|
-
|
|
58
|
+
/** Toggles the calendar open status. If no parameters are passed, the calendar will simply toggle its open status.
|
|
59
|
+
* If the isOpen parameter is passed, that will set the calendar open status to the value of the isOpen parameter.
|
|
60
|
+
* If the eventKey parameter is set to 'Escape', that will invoke the date pickers onEscapePress event to toggle the
|
|
61
|
+
* correct control appropriately. */
|
|
62
|
+
toggleCalendar: (isOpen?: boolean, eventKey?: string) => void;
|
|
63
|
+
/** Current calendar open status */
|
|
64
|
+
isCalendarOpen: boolean;
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
export const yyyyMMddFormat = (date: Date) =>
|
|
@@ -74,7 +85,7 @@ const DatePickerBase = (
|
|
|
74
85
|
onBlur = (): any => undefined,
|
|
75
86
|
invalidFormatText = 'Invalid date',
|
|
76
87
|
helperText,
|
|
77
|
-
appendTo,
|
|
88
|
+
appendTo = 'parent',
|
|
78
89
|
popoverProps,
|
|
79
90
|
monthFormat,
|
|
80
91
|
weekdayFormat,
|
|
@@ -98,6 +109,7 @@ const DatePickerBase = (
|
|
|
98
109
|
const widthChars = React.useMemo(() => Math.max(dateFormat(new Date()).length, placeholder.length), [dateFormat]);
|
|
99
110
|
const style = { '--pf-c-date-picker__input--c-form-control--width-chars': widthChars, ...styleProps };
|
|
100
111
|
const buttonRef = React.useRef<HTMLButtonElement>();
|
|
112
|
+
const datePickerWrapperRef = React.useRef<HTMLDivElement>();
|
|
101
113
|
|
|
102
114
|
React.useEffect(() => {
|
|
103
115
|
setValue(valueProp);
|
|
@@ -159,13 +171,21 @@ const DatePickerBase = (
|
|
|
159
171
|
ref,
|
|
160
172
|
() => ({
|
|
161
173
|
setCalendarOpen: (isOpen: boolean) => setPopoverOpen(isOpen),
|
|
162
|
-
toggleCalendar: (
|
|
174
|
+
toggleCalendar: (setOpen?: boolean, eventKey?: string) => {
|
|
175
|
+
if (eventKey === KeyTypes.Escape && popoverOpen && !selectOpen) {
|
|
176
|
+
setPopoverOpen(prev => (setOpen !== undefined ? setOpen : !prev));
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
isCalendarOpen: popoverOpen
|
|
163
180
|
}),
|
|
164
|
-
[setPopoverOpen]
|
|
181
|
+
[setPopoverOpen, popoverOpen, selectOpen]
|
|
165
182
|
);
|
|
166
183
|
|
|
184
|
+
const getParentElement = () =>
|
|
185
|
+
datePickerWrapperRef && datePickerWrapperRef.current ? datePickerWrapperRef.current : null;
|
|
186
|
+
|
|
167
187
|
return (
|
|
168
|
-
<div className={css(styles.datePicker, className)} style={style} {...props}>
|
|
188
|
+
<div className={css(styles.datePicker, className)} ref={datePickerWrapperRef} style={style} {...props}>
|
|
169
189
|
<Popover
|
|
170
190
|
position="bottom"
|
|
171
191
|
bodyContent={
|
|
@@ -182,14 +202,16 @@ const DatePickerBase = (
|
|
|
182
202
|
dayFormat={dayFormat}
|
|
183
203
|
weekStart={weekStart}
|
|
184
204
|
rangeStart={rangeStart}
|
|
205
|
+
isDateFocused
|
|
185
206
|
/>
|
|
186
207
|
}
|
|
187
208
|
showClose={false}
|
|
188
209
|
isVisible={popoverOpen}
|
|
189
210
|
shouldClose={(_1, _2, event) => {
|
|
190
211
|
event = event as KeyboardEvent;
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
if (event.key === KeyTypes.Escape && selectOpen) {
|
|
213
|
+
event.stopPropagation();
|
|
214
|
+
setSelectOpen(false);
|
|
193
215
|
return false;
|
|
194
216
|
}
|
|
195
217
|
// Let our button handle toggling
|
|
@@ -197,12 +219,15 @@ const DatePickerBase = (
|
|
|
197
219
|
return false;
|
|
198
220
|
}
|
|
199
221
|
setPopoverOpen(false);
|
|
222
|
+
if (event.key === KeyTypes.Escape && popoverOpen) {
|
|
223
|
+
event.stopPropagation();
|
|
224
|
+
}
|
|
200
225
|
return true;
|
|
201
226
|
}}
|
|
202
227
|
withFocusTrap
|
|
203
228
|
hasNoPadding
|
|
204
229
|
hasAutoWidth
|
|
205
|
-
appendTo={appendTo}
|
|
230
|
+
appendTo={appendTo === 'parent' ? getParentElement() : appendTo}
|
|
206
231
|
{...popoverProps}
|
|
207
232
|
>
|
|
208
233
|
<div className={styles.datePickerInput}>
|
|
@@ -110,15 +110,15 @@ export const Label: React.FunctionComponent<LabelProps> = ({
|
|
|
110
110
|
const editableInputRef = React.useRef<HTMLInputElement>();
|
|
111
111
|
|
|
112
112
|
React.useEffect(() => {
|
|
113
|
-
document.addEventListener('
|
|
113
|
+
document.addEventListener('mousedown', onDocMouseDown);
|
|
114
114
|
document.addEventListener('keydown', onKeyDown);
|
|
115
115
|
return () => {
|
|
116
|
-
document.removeEventListener('
|
|
116
|
+
document.removeEventListener('mousedown', onDocMouseDown);
|
|
117
117
|
document.removeEventListener('keydown', onKeyDown);
|
|
118
118
|
};
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
const
|
|
121
|
+
const onDocMouseDown = (event: MouseEvent) => {
|
|
122
122
|
if (
|
|
123
123
|
isEditableActive &&
|
|
124
124
|
editableInputRef &&
|
|
@@ -58,6 +58,8 @@ export interface LabelGroupProps extends React.HTMLProps<HTMLUListElement> {
|
|
|
58
58
|
hasEditableTextArea?: boolean;
|
|
59
59
|
/** @beta Additional props passed to the editable textarea. */
|
|
60
60
|
editableTextAreaProps?: any;
|
|
61
|
+
/** @beta Control for adding new labels */
|
|
62
|
+
addLabelControl?: React.ReactNode;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
interface LabelGroupState {
|
|
@@ -149,6 +151,7 @@ export class LabelGroup extends React.Component<LabelGroupProps, LabelGroupState
|
|
|
149
151
|
isEditable,
|
|
150
152
|
hasEditableTextArea,
|
|
151
153
|
editableTextAreaProps,
|
|
154
|
+
addLabelControl,
|
|
152
155
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
153
156
|
...rest
|
|
154
157
|
} = this.props;
|
|
@@ -189,6 +192,7 @@ export class LabelGroup extends React.Component<LabelGroupProps, LabelGroupState
|
|
|
189
192
|
</Label>
|
|
190
193
|
</li>
|
|
191
194
|
)}
|
|
195
|
+
{addLabelControl && <li className={css(styles.labelGroupListItem)}>{addLabelControl}</li>}
|
|
192
196
|
{isEditable && hasEditableTextArea && (
|
|
193
197
|
<li className={css(styles.labelGroupListItem, styles.modifiers.textarea)}>
|
|
194
198
|
<textarea className={css(styles.labelGroupTextarea)} rows={1} tabIndex={0} {...editableTextAreaProps} />
|
|
@@ -228,7 +232,7 @@ export class LabelGroup extends React.Component<LabelGroupProps, LabelGroupState
|
|
|
228
232
|
);
|
|
229
233
|
};
|
|
230
234
|
|
|
231
|
-
return numChildren === 0 ? null : (
|
|
235
|
+
return numChildren === 0 && addLabelControl === undefined ? null : (
|
|
232
236
|
<GenerateId>{randomId => renderLabelGroup(this.props.id || randomId)}</GenerateId>
|
|
233
237
|
);
|
|
234
238
|
}
|
|
@@ -40,3 +40,10 @@ import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-i
|
|
|
40
40
|
|
|
41
41
|
```ts file="LabelGroupEditableLabels.tsx" isBeta
|
|
42
42
|
```
|
|
43
|
+
|
|
44
|
+
### Editable labels with add button
|
|
45
|
+
|
|
46
|
+
For additional documentation that showcases adding a new label, see [label group demos](/components/label-group/react-demos).
|
|
47
|
+
|
|
48
|
+
```ts file="LabelGroupEditableAdd.tsx"
|
|
49
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LabelGroup, Label } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const LabelGroupEditableAdd: React.FunctionComponent = () => {
|
|
5
|
+
const [idIndex, setIdIndex] = React.useState<number>(3);
|
|
6
|
+
const [labels, setLabels] = React.useState<any>([
|
|
7
|
+
{ name: 'Label 1', id: 0 },
|
|
8
|
+
{ name: 'Label 2', id: 1 },
|
|
9
|
+
{
|
|
10
|
+
name: 'Label 3',
|
|
11
|
+
props: {
|
|
12
|
+
isEditable: true,
|
|
13
|
+
editableProps: {
|
|
14
|
+
'aria-label': 'label editable text'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
id: 2
|
|
18
|
+
}
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const onClose = (labelId: string) => {
|
|
22
|
+
setLabels(labels.filter((l: any) => l.id !== labelId));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const onEdit = (nextText: string, index: number) => {
|
|
26
|
+
const copy = [...labels];
|
|
27
|
+
copy[index] = { name: nextText, props: labels[index].props, id: labels[index].id };
|
|
28
|
+
setLabels(copy);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const onAdd = () => {
|
|
32
|
+
setLabels([
|
|
33
|
+
{
|
|
34
|
+
name: 'New Label',
|
|
35
|
+
props: {
|
|
36
|
+
isEditable: true,
|
|
37
|
+
editableProps: {
|
|
38
|
+
'aria-label': 'label editable text'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
id: idIndex
|
|
42
|
+
},
|
|
43
|
+
...labels
|
|
44
|
+
]);
|
|
45
|
+
setIdIndex(idIndex + 1);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<LabelGroup
|
|
50
|
+
categoryName="Label group 1"
|
|
51
|
+
numLabels={5}
|
|
52
|
+
isEditable
|
|
53
|
+
addLabelControl={
|
|
54
|
+
<Label color="blue" variant="outline" isOverflowLabel onClick={onAdd}>
|
|
55
|
+
Add label
|
|
56
|
+
</Label>
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
{labels.map((label, index) => (
|
|
60
|
+
<Label
|
|
61
|
+
key={`${label.name}-${index}`}
|
|
62
|
+
id={`${label.name}-${index}`}
|
|
63
|
+
color="blue"
|
|
64
|
+
onClose={() => onClose(label.id)}
|
|
65
|
+
onEditCancel={prevText => onEdit(prevText, index)}
|
|
66
|
+
onEditComplete={newText => onEdit(newText, index)}
|
|
67
|
+
{...label.props}
|
|
68
|
+
>
|
|
69
|
+
{label.name}
|
|
70
|
+
</Label>
|
|
71
|
+
))}
|
|
72
|
+
</LabelGroup>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LabelGroup, Label, Menu, MenuContent, MenuList, MenuItem, Popper } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const LabelGroupEditableAddDropdown: React.FunctionComponent = () => {
|
|
5
|
+
const toggleRef = React.useRef<HTMLDivElement>();
|
|
6
|
+
const menuRef = React.useRef<HTMLDivElement>();
|
|
7
|
+
const containerRef = React.useRef<HTMLDivElement>();
|
|
8
|
+
|
|
9
|
+
const [idIndex, setIdIndex] = React.useState<number>(3);
|
|
10
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
11
|
+
const [labels, setLabels] = React.useState<any>([
|
|
12
|
+
{ name: 'Label 1', id: 0 },
|
|
13
|
+
{ name: 'Label 2', id: 1 },
|
|
14
|
+
{
|
|
15
|
+
name: 'Label 3',
|
|
16
|
+
props: {
|
|
17
|
+
isEditable: true,
|
|
18
|
+
editableProps: {
|
|
19
|
+
'aria-label': 'label editable text'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
id: 2
|
|
23
|
+
}
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const onClose = (labelId: string) => {
|
|
27
|
+
setLabels(labels.filter((l: any) => l.id !== labelId));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const onEdit = (nextText: string, index: number) => {
|
|
31
|
+
const copy = [...labels];
|
|
32
|
+
copy[index] = { name: nextText, props: labels[index].props, id: labels[index].id };
|
|
33
|
+
setLabels(copy);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const onAdd = (labelText: string) => {
|
|
37
|
+
setLabels([
|
|
38
|
+
{
|
|
39
|
+
name: labelText,
|
|
40
|
+
id: idIndex
|
|
41
|
+
},
|
|
42
|
+
...labels
|
|
43
|
+
]);
|
|
44
|
+
setIdIndex(idIndex + 1);
|
|
45
|
+
setIsOpen(!isOpen);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleMenuKeys = (event: KeyboardEvent) => {
|
|
49
|
+
if (isOpen && menuRef.current.contains(event.target as Node)) {
|
|
50
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
51
|
+
setIsOpen(!isOpen);
|
|
52
|
+
toggleRef.current.focus();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
58
|
+
if (
|
|
59
|
+
isOpen &&
|
|
60
|
+
!(menuRef.current.contains(event.target as Node) || toggleRef.current.contains(event.target as Node))
|
|
61
|
+
) {
|
|
62
|
+
setIsOpen(false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
window.addEventListener('keydown', handleMenuKeys);
|
|
68
|
+
window.addEventListener('click', handleClickOutside);
|
|
69
|
+
return () => {
|
|
70
|
+
window.removeEventListener('keydown', handleMenuKeys);
|
|
71
|
+
window.removeEventListener('click', handleClickOutside);
|
|
72
|
+
};
|
|
73
|
+
}, [isOpen, menuRef]);
|
|
74
|
+
|
|
75
|
+
const onToggleClick = () => {
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
if (menuRef.current) {
|
|
78
|
+
const firstElement = menuRef.current.querySelector('li > button:not(:disabled)');
|
|
79
|
+
firstElement && (firstElement as HTMLElement).focus();
|
|
80
|
+
}
|
|
81
|
+
}, 0);
|
|
82
|
+
setIsOpen(!isOpen);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const menu = (
|
|
86
|
+
<Menu ref={menuRef} onSelect={(_ev, itemId) => onAdd(itemId.toString())}>
|
|
87
|
+
<MenuContent>
|
|
88
|
+
<MenuList>
|
|
89
|
+
<MenuItem itemId="Label text 1">Label text 1</MenuItem>
|
|
90
|
+
<MenuItem itemId="Label text 2">Label text 2</MenuItem>
|
|
91
|
+
<MenuItem itemId="Label text 3">Label text 3</MenuItem>
|
|
92
|
+
<MenuItem itemId="Label text 4">Label text 4</MenuItem>
|
|
93
|
+
</MenuList>
|
|
94
|
+
</MenuContent>
|
|
95
|
+
</Menu>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const toggle = (
|
|
99
|
+
<div ref={toggleRef}>
|
|
100
|
+
<Label color="blue" variant="outline" isOverflowLabel onClick={onToggleClick}>
|
|
101
|
+
Add label
|
|
102
|
+
</Label>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div ref={containerRef}>
|
|
108
|
+
<LabelGroup
|
|
109
|
+
categoryName="Label group 1"
|
|
110
|
+
numLabels={5}
|
|
111
|
+
isEditable
|
|
112
|
+
addLabelControl={
|
|
113
|
+
<Popper
|
|
114
|
+
trigger={toggle}
|
|
115
|
+
popper={menu}
|
|
116
|
+
appendTo={containerRef.current}
|
|
117
|
+
isVisible={isOpen}
|
|
118
|
+
popperMatchesTriggerWidth={false}
|
|
119
|
+
/>
|
|
120
|
+
}
|
|
121
|
+
>
|
|
122
|
+
{labels.map((label, index) => (
|
|
123
|
+
<Label
|
|
124
|
+
key={`${label.name}-${index}`}
|
|
125
|
+
id={`${label.name}-${index}`}
|
|
126
|
+
color="blue"
|
|
127
|
+
onClose={() => onClose(label.id)}
|
|
128
|
+
onEditCancel={prevText => onEdit(prevText, index)}
|
|
129
|
+
onEditComplete={newText => onEdit(newText, index)}
|
|
130
|
+
{...label.props}
|
|
131
|
+
>
|
|
132
|
+
{label.name}
|
|
133
|
+
</Label>
|
|
134
|
+
))}
|
|
135
|
+
</LabelGroup>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
};
|