@patternfly/react-core 4.222.12 → 4.222.13

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/esm/components/CalendarMonth/CalendarMonth.d.ts +3 -1
  3. package/dist/esm/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
  4. package/dist/esm/components/CalendarMonth/CalendarMonth.js +21 -14
  5. package/dist/esm/components/CalendarMonth/CalendarMonth.js.map +1 -1
  6. package/dist/esm/components/DatePicker/DatePicker.d.ts +14 -4
  7. package/dist/esm/components/DatePicker/DatePicker.d.ts.map +1 -1
  8. package/dist/esm/components/DatePicker/DatePicker.js +20 -8
  9. package/dist/esm/components/DatePicker/DatePicker.js.map +1 -1
  10. package/dist/esm/components/Popover/Popover.js +2 -2
  11. package/dist/esm/components/Popover/Popover.js.map +1 -1
  12. package/dist/esm/components/TimePicker/TimePicker.d.ts +6 -1
  13. package/dist/esm/components/TimePicker/TimePicker.d.ts.map +1 -1
  14. package/dist/esm/components/TimePicker/TimePicker.js +32 -19
  15. package/dist/esm/components/TimePicker/TimePicker.js.map +1 -1
  16. package/dist/js/components/CalendarMonth/CalendarMonth.d.ts +3 -1
  17. package/dist/js/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
  18. package/dist/js/components/CalendarMonth/CalendarMonth.js +21 -14
  19. package/dist/js/components/CalendarMonth/CalendarMonth.js.map +1 -1
  20. package/dist/js/components/DatePicker/DatePicker.d.ts +14 -4
  21. package/dist/js/components/DatePicker/DatePicker.d.ts.map +1 -1
  22. package/dist/js/components/DatePicker/DatePicker.js +20 -8
  23. package/dist/js/components/DatePicker/DatePicker.js.map +1 -1
  24. package/dist/js/components/Popover/Popover.js +1 -1
  25. package/dist/js/components/Popover/Popover.js.map +1 -1
  26. package/dist/js/components/TimePicker/TimePicker.d.ts +6 -1
  27. package/dist/js/components/TimePicker/TimePicker.d.ts.map +1 -1
  28. package/dist/js/components/TimePicker/TimePicker.js +32 -19
  29. package/dist/js/components/TimePicker/TimePicker.js.map +1 -1
  30. package/dist/umd/react-core.css +20 -0
  31. package/dist/umd/react-core.js +76 -43
  32. package/dist/umd/react-core.min.css +1088 -1068
  33. package/dist/umd/react-core.min.js +2 -2
  34. package/package.json +6 -6
  35. package/src/components/CalendarMonth/CalendarMonth.tsx +21 -13
  36. package/src/components/DatePicker/DatePicker.tsx +36 -11
  37. package/src/components/Modal/examples/Modal.md +2 -0
  38. package/src/components/OverflowMenu/examples/OverflowMenu.md +8 -0
  39. package/src/components/Popover/Popover.tsx +2 -2
  40. package/src/components/TimePicker/TimePicker.tsx +36 -19
  41. package/src/components/Toolbar/examples/Toolbar.md +4 -0
  42. package/src/demos/Card/Card.md +2 -0
  43. package/src/demos/{DatePicker.md → DatePicker/DatePicker.md} +8 -0
  44. package/src/demos/DatePicker/examples/DateTimePickerInModal.tsx +51 -0
  45. 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.12",
3
+ "version": "4.222.13",
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.12",
38
- "@patternfly/react-styles": "^4.72.12",
39
- "@patternfly/react-tokens": "^4.74.12",
37
+ "@patternfly/react-icons": "^4.73.13",
38
+ "@patternfly/react-styles": "^4.72.13",
39
+ "@patternfly/react-tokens": "^4.74.13",
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.197.0",
46
+ "@patternfly/patternfly": "4.198.1",
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": "38768ae0d7afdf586573a16af314bc3e67b0d73f"
63
+ "gitHead": "12411175163290e8c6dd598cfb5ce53ba58688b3"
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
- let [focusedDate, setFocusedDate] = React.useState(new Date(dateProp));
119
- if (!isValidDate(focusedDate)) {
120
- if (isValidDate(rangeStart)) {
121
- focusedDate = rangeStart;
121
+ const [focusedDate, setFocusedDate] = React.useState(() => {
122
+ const initDate = new Date(dateProp);
123
+ if (isValidDate(initDate)) {
124
+ return initDate;
122
125
  } else {
123
- focusedDate = today;
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 (!(isValidDate(dateProp) && isSameDate(focusedDate, dateProp))) {
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
- // When using header controls don't move focus
141
- if (shouldFocus) {
142
- if (focusRef.current && focusedDateValidated) {
143
- focusRef.current.focus();
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 element to append the popover to */
40
- appendTo?: HTMLElement | ((ref?: HTMLElement) => HTMLElement);
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
- toggleCalendar: () => void;
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: () => setPopoverOpen(prev => !prev)
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
- // Let the select menu close
192
- if (event.keyCode && event.keyCode === 27 && selectOpen) {
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}>
@@ -876,3 +876,5 @@ If the content that you're passing to the modal is likely to overflow the modal
876
876
 
877
877
  ```ts file="ModalWithOverflowingContent.tsx"
878
878
  ```
879
+
880
+
@@ -61,6 +61,8 @@ class SimpleOverflowMenu extends React.Component {
61
61
  isOpen={isOpen}
62
62
  isPlain
63
63
  dropdownItems={dropdownItems}
64
+ isFlipEnabled
65
+ menuAppendTo="parent"
64
66
  />
65
67
  </OverflowMenuControl>
66
68
  </OverflowMenu>
@@ -153,6 +155,8 @@ class OverflowMenuGroupTypes extends React.Component {
153
155
  isOpen={isOpen}
154
156
  isPlain
155
157
  dropdownItems={dropdownItems}
158
+ isFlipEnabled
159
+ menuAppendTo="parent"
156
160
  />
157
161
  </OverflowMenuControl>
158
162
  </OverflowMenu>
@@ -239,6 +243,8 @@ class OverflowMenuAdditionalOptions extends React.Component {
239
243
  isOpen={isOpen}
240
244
  isPlain
241
245
  dropdownItems={dropdownItems}
246
+ isFlipEnabled
247
+ menuAppendTo="parent"
242
248
  />
243
249
  </OverflowMenuControl>
244
250
  </OverflowMenu>
@@ -301,6 +307,8 @@ class OverflowMenuPersist extends React.Component {
301
307
  isOpen={isOpen}
302
308
  isPlain
303
309
  dropdownItems={dropdownItems}
310
+ isFlipEnabled
311
+ menuAppendTo="parent"
304
312
  />
305
313
  </OverflowMenuControl>
306
314
  </OverflowMenu>
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-console */
2
2
  import * as React from 'react';
3
- import { KEY_CODES } from '../../helpers/constants';
3
+ import { KeyTypes } from '../../helpers/constants';
4
4
  import styles from '@patternfly/react-styles/css/components/Popover/popover';
5
5
  import { css } from '@patternfly/react-styles';
6
6
  import { PopoverContext } from './PopoverContext';
@@ -330,7 +330,7 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
330
330
  const hasCustomMinWidth = minWidth !== popoverMinWidth.value;
331
331
  const hasCustomMaxWidth = maxWidth !== popoverMaxWidth.value;
332
332
  const onDocumentKeyDown = (event: KeyboardEvent) => {
333
- if (event.keyCode === KEY_CODES.ESCAPE_KEY && visible) {
333
+ if (event.key === KeyTypes.Escape && visible) {
334
334
  if (triggerManually) {
335
335
  shouldClose(null, hide, event);
336
336
  } else {
@@ -67,11 +67,15 @@ export interface TimePickerProps
67
67
  maxTime?: string | Date;
68
68
  /** Includes number of seconds with the chosen time and allows users to manually edit the seconds value. */
69
69
  includeSeconds?: boolean;
70
+ /** Flag to control the opened state of the time picker menu */
71
+ isOpen?: boolean;
72
+ /** Handler invoked each time the open state of time picker updates */
73
+ setIsOpen?: (isOpen?: boolean) => void;
70
74
  }
71
75
 
72
76
  interface TimePickerState {
73
77
  isInvalid: boolean;
74
- isOpen: boolean;
78
+ isTimeOptionsOpen: boolean;
75
79
  timeState: string;
76
80
  focusedIndex: number;
77
81
  scrollIndex: number;
@@ -102,12 +106,13 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
102
106
  stepMinutes: 30,
103
107
  inputProps: {},
104
108
  minTime: '',
105
- maxTime: ''
109
+ maxTime: '',
110
+ setIsOpen: () => {}
106
111
  };
107
112
 
108
113
  constructor(props: TimePickerProps) {
109
114
  super(props);
110
- const { is24Hour, delimiter, time, includeSeconds } = this.props;
115
+ const { is24Hour, delimiter, time, includeSeconds, isOpen } = this.props;
111
116
  let { minTime, maxTime } = this.props;
112
117
  if (minTime === '') {
113
118
  const minSeconds = includeSeconds ? `${delimiter}00` : '';
@@ -120,7 +125,7 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
120
125
  const timeRegex = this.getRegExp();
121
126
  this.state = {
122
127
  isInvalid: false,
123
- isOpen: false,
128
+ isTimeOptionsOpen: isOpen,
124
129
  timeState: parseTime(time, timeRegex, delimiter, !is24Hour, includeSeconds),
125
130
  focusedIndex: null,
126
131
  scrollIndex: 0,
@@ -145,18 +150,18 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
145
150
  onDocClick = (event: MouseEvent | TouchEvent) => {
146
151
  const clickedOnToggle = this.toggleRef?.current?.contains(event.target as Node);
147
152
  const clickedWithinMenu = this.menuRef?.current?.contains(event.target as Node);
148
- if (this.state.isOpen && !(clickedOnToggle || clickedWithinMenu)) {
153
+ if (this.state.isTimeOptionsOpen && !(clickedOnToggle || clickedWithinMenu)) {
149
154
  this.onToggle(false);
150
155
  }
151
156
  };
152
157
 
153
158
  handleGlobalKeys = (event: KeyboardEvent) => {
154
- const { isOpen, focusedIndex, scrollIndex } = this.state;
159
+ const { isTimeOptionsOpen, focusedIndex, scrollIndex } = this.state;
155
160
  // keyboard pressed while focus on toggle
156
161
  if (this.inputRef?.current?.contains(event.target as Node)) {
157
- if (!isOpen && event.key !== KeyTypes.Tab) {
162
+ if (!isTimeOptionsOpen && event.key !== KeyTypes.Tab && event.key !== KeyTypes.Escape) {
158
163
  this.onToggle(true);
159
- } else if (isOpen) {
164
+ } else if (isTimeOptionsOpen) {
160
165
  if (event.key === KeyTypes.Escape || event.key === KeyTypes.Tab) {
161
166
  this.onToggle(false);
162
167
  } else if (event.key === KeyTypes.Enter) {
@@ -188,9 +193,13 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
188
193
  };
189
194
 
190
195
  componentDidUpdate(prevProps: TimePickerProps, prevState: TimePickerState) {
191
- const { timeState, isOpen, isInvalid, timeRegex } = this.state;
192
- const { time, is24Hour, delimiter, includeSeconds } = this.props;
193
- if (isOpen && !prevState.isOpen && timeState && !isInvalid) {
196
+ const { timeState, isTimeOptionsOpen, isInvalid, timeRegex } = this.state;
197
+ const { time, is24Hour, delimiter, includeSeconds, isOpen } = this.props;
198
+ if (prevProps.isOpen !== isOpen) {
199
+ this.onToggle(isOpen);
200
+ }
201
+
202
+ if (isTimeOptionsOpen && !prevState.isTimeOptionsOpen && timeState && !isInvalid) {
194
203
  this.scrollToSelection(timeState);
195
204
  }
196
205
  if (delimiter !== prevProps.delimiter) {
@@ -340,18 +349,21 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
340
349
  const { timeRegex, isInvalid } = prevState;
341
350
  const { delimiter, is24Hour, includeSeconds } = this.props;
342
351
  const time = parseTime(prevState.timeState, timeRegex, delimiter, !is24Hour, includeSeconds);
343
-
344
352
  return {
345
- isOpen,
353
+ isTimeOptionsOpen: isOpen,
346
354
  timeState: time,
347
355
  isInvalid: isOpen ? isInvalid : !this.isValid(time)
348
356
  };
349
357
  });
358
+ this.props.setIsOpen(isOpen);
359
+ if (!isOpen) {
360
+ this.inputRef.current.focus();
361
+ }
350
362
  };
351
363
 
352
364
  onSelect = (e: any) => {
353
365
  const { timeRegex, timeState } = this.state;
354
- const { delimiter, is24Hour, includeSeconds } = this.props;
366
+ const { delimiter, is24Hour, includeSeconds, setIsOpen } = this.props;
355
367
  const time = parseTime(e.target.textContent, timeRegex, delimiter, !is24Hour, includeSeconds);
356
368
  if (time !== timeState) {
357
369
  this.onInputChange(time);
@@ -359,13 +371,14 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
359
371
 
360
372
  this.inputRef.current.focus();
361
373
  this.setState({
362
- isOpen: false,
374
+ isTimeOptionsOpen: false,
363
375
  isInvalid: false
364
376
  });
377
+ setIsOpen(false);
365
378
  };
366
379
 
367
380
  onInputClick = (e: any) => {
368
- if (!this.state.isOpen) {
381
+ if (!this.state.isTimeOptionsOpen) {
369
382
  this.onToggle(true);
370
383
  }
371
384
  e.stopPropagation();
@@ -417,6 +430,10 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
417
430
  inputProps,
418
431
  /* eslint-disable @typescript-eslint/no-unused-vars */
419
432
  onChange,
433
+ /* eslint-disable @typescript-eslint/no-unused-vars */
434
+ setIsOpen,
435
+ /* eslint-disable @typescript-eslint/no-unused-vars */
436
+ isOpen,
420
437
  time,
421
438
  validateTime,
422
439
  minTime,
@@ -425,7 +442,7 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
425
442
  /* eslint-enable @typescript-eslint/no-unused-vars */
426
443
  ...props
427
444
  } = this.props;
428
- const { timeState, isOpen, isInvalid, minTimeState, maxTimeState } = this.state;
445
+ const { timeState, isTimeOptionsOpen, isInvalid, minTimeState, maxTimeState } = this.state;
429
446
  const style = { '--pf-c-date-picker__input--c-form-control--Width': width } as React.CSSProperties;
430
447
  const options = makeTimeOptions(stepMinutes, !is24Hour, delimiter, minTimeState, maxTimeState, includeSeconds);
431
448
  const isValidFormat = this.isValidFormat(timeState);
@@ -484,13 +501,13 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
484
501
  appendTo={menuAppendTo === 'parent' ? getParentElement() : menuAppendTo}
485
502
  trigger={textInput}
486
503
  popper={menuContainer}
487
- isVisible={isOpen}
504
+ isVisible={isTimeOptionsOpen}
488
505
  />
489
506
  ) : (
490
507
  textInput
491
508
  )}
492
509
  </div>
493
- {isOpen && menuAppendTo === 'inline' && menuContainer}
510
+ {isTimeOptionsOpen && menuAppendTo === 'inline' && menuContainer}
494
511
  </div>
495
512
  </InputGroup>
496
513
  {isInvalid && (
@@ -811,6 +811,7 @@ import {
811
811
  Dropdown,
812
812
  DropdownItem,
813
813
  DropdownSeparator,
814
+ DropdownPosition,
814
815
  KebabToggle,
815
816
  SearchInput
816
817
  } from '@patternfly/react-core';
@@ -1036,6 +1037,7 @@ class ToolbarWithFilterExample extends React.Component {
1036
1037
  isOpen={kebabIsOpen}
1037
1038
  isPlain
1038
1039
  dropdownItems={dropdownItems}
1040
+ position={DropdownPosition.right}
1039
1041
  />
1040
1042
  </ToolbarItem>
1041
1043
  </React.Fragment>
@@ -1080,6 +1082,7 @@ import {
1080
1082
  DropdownToggle,
1081
1083
  DropdownToggleCheckbox,
1082
1084
  DropdownItem,
1085
+ DropdownPosition,
1083
1086
  Divider,
1084
1087
  OverflowMenu,
1085
1088
  OverflowMenuContent,
@@ -1293,6 +1296,7 @@ class ToolbarStacked extends React.Component {
1293
1296
  isOpen={kebabIsOpen}
1294
1297
  isPlain
1295
1298
  dropdownItems={dropdownItems}
1299
+ position={DropdownPosition.right}
1296
1300
  />
1297
1301
  </OverflowMenuControl>
1298
1302
  </OverflowMenu>
@@ -525,6 +525,8 @@ class CardViewBasic extends React.Component {
525
525
  isOpen={isLowerToolbarKebabDropdownOpen}
526
526
  isPlain
527
527
  dropdownItems={toolbarKebabDropdownItems}
528
+ isFlipEnabled
529
+ menuAppendTo="parent"
528
530
  />
529
531
  </OverflowMenuControl>
530
532
  </OverflowMenu>
@@ -146,3 +146,11 @@ DateTimeRangePicker = () => {
146
146
  }
147
147
  ```
148
148
 
149
+
150
+ ### Date and time pickers in modal
151
+ Modals trap focus and watch a few document level events. In order to place a date picker in a modal:
152
+ - To avoid the modal's escape press event handler from overruling the date picker's escape press handlers, use the `DatePickerRef` to close the calendar when it is open and the escape key is pressed.
153
+ - Append the calendar to the modal to keep it as close to the date picker in the DOM while maintaining correct layouts visually
154
+ In order to place a time picker in the modal, its menu must be appended to the time picker's parent.
155
+ ```ts file="./examples/DateTimePickerInModal.tsx"
156
+ ```
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { DatePicker, Modal, ModalVariant, Button, TimePicker, InputGroup } from '@patternfly/react-core';
3
+
4
+ export const SimpleModal = () => {
5
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
6
+ const [isTimePickerOpen, setIsTimePickerOpen] = React.useState(false);
7
+ const dateRef = React.useRef(null);
8
+
9
+ const handleModalToggle = () => {
10
+ setIsModalOpen(!isModalOpen);
11
+ };
12
+
13
+ const onEscapePress = (event: KeyboardEvent) => {
14
+ if (dateRef && dateRef.current && dateRef.current.isCalendarOpen) {
15
+ dateRef.current.toggleCalendar(false, event.key);
16
+ } else if (isTimePickerOpen) {
17
+ setIsTimePickerOpen(false);
18
+ } else {
19
+ handleModalToggle();
20
+ }
21
+ };
22
+
23
+ return (
24
+ <React.Fragment>
25
+ <Button variant="primary" onClick={handleModalToggle}>
26
+ Launch modal
27
+ </Button>
28
+ <Modal
29
+ id="date-time-picker-modal"
30
+ variant={ModalVariant.small}
31
+ title="Generic modal header"
32
+ isOpen={isModalOpen}
33
+ onEscapePress={onEscapePress}
34
+ onClose={handleModalToggle}
35
+ actions={[
36
+ <Button key="confirm" variant="primary" onClick={handleModalToggle}>
37
+ Confirm
38
+ </Button>,
39
+ <Button key="cancel" variant="link" onClick={handleModalToggle}>
40
+ Cancel
41
+ </Button>
42
+ ]}
43
+ >
44
+ <InputGroup>
45
+ <DatePicker ref={dateRef} appendTo={() => document.getElementById('date-time-picker-modal')} />
46
+ <TimePicker menuAppendTo="parent" isOpen={isTimePickerOpen} setIsOpen={setIsTimePickerOpen} />
47
+ </InputGroup>
48
+ </Modal>
49
+ </React.Fragment>
50
+ );
51
+ };
@@ -1246,6 +1246,8 @@ class PrimaryDetailCardView extends React.Component {
1246
1246
  isOpen={isLowerToolbarKebabDropdownOpen}
1247
1247
  isPlain
1248
1248
  dropdownItems={toolbarKebabDropdownItems}
1249
+ isFlipEnabled
1250
+ menuAppendTo="parent"
1249
1251
  />
1250
1252
  </OverflowMenuControl>
1251
1253
  </OverflowMenu>
@@ -1337,14 +1339,14 @@ class PrimaryDetailCardView extends React.Component {
1337
1339
  <Text component="p">This is a demo that showcases Patternfly Cards.</Text>
1338
1340
  </TextContent>
1339
1341
  </PageSection>
1342
+ <PageSection isFilled padding={{ default: 'noPadding' }}>
1343
+ <Toolbar id="card-view-data-toolbar-group-types" usePageInsets clearAllFilters={this.onDelete}>
1344
+ <ToolbarContent>{toolbarItems}</ToolbarContent>
1345
+ </Toolbar>
1346
+ <Divider component="div" />
1347
+ </PageSection>
1340
1348
  <PageSection isFilled padding={{ default: 'noPadding' }}>
1341
1349
  <Drawer isExpanded={isDrawerExpanded} className={'pf-m-inline-on-2xl'}>
1342
- <DrawerSection>
1343
- <Toolbar id="card-view-data-toolbar-group-types" usePageInsets clearAllFilters={this.onDelete}>
1344
- <ToolbarContent>{toolbarItems}</ToolbarContent>
1345
- </Toolbar>
1346
- <Divider component="div" />
1347
- </DrawerSection>
1348
1350
  <DrawerContent panelContent={panelContent} className={'pf-m-no-background'}>
1349
1351
  <DrawerContentBody hasPadding>{drawerContent}</DrawerContentBody>
1350
1352
  </DrawerContent>
@@ -1663,6 +1665,8 @@ class PrimaryDetailDataListInCard extends React.Component {
1663
1665
  }
1664
1666
  isOpen={isDropdownOpen}
1665
1667
  dropdownItems={dropdownItems}
1668
+ isFlipEnabled
1669
+ menuAppendTo="parent"
1666
1670
  />
1667
1671
  </ToolbarItem>
1668
1672
  </ToolbarContent>