@patternfly/react-core 4.227.8 → 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.
Files changed (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/esm/components/Menu/MenuItem.d.ts +3 -1
  3. package/dist/esm/components/Menu/MenuItem.d.ts.map +1 -1
  4. package/dist/esm/components/Menu/MenuItem.js +2 -2
  5. package/dist/esm/components/Menu/MenuItem.js.map +1 -1
  6. package/dist/esm/components/MenuToggle/MenuToggle.d.ts +6 -12
  7. package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
  8. package/dist/esm/components/MenuToggle/MenuToggle.js +12 -8
  9. package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
  10. package/dist/esm/components/TextInputGroup/TextInputGroup.d.ts +2 -0
  11. package/dist/esm/components/TextInputGroup/TextInputGroup.d.ts.map +1 -1
  12. package/dist/esm/components/TextInputGroup/TextInputGroup.js +2 -2
  13. package/dist/esm/components/TextInputGroup/TextInputGroup.js.map +1 -1
  14. package/dist/esm/components/TextInputGroup/TextInputGroupMain.d.ts +1 -1
  15. package/dist/esm/components/TextInputGroup/TextInputGroupMain.d.ts.map +1 -1
  16. package/dist/js/components/Menu/MenuItem.d.ts +3 -1
  17. package/dist/js/components/Menu/MenuItem.d.ts.map +1 -1
  18. package/dist/js/components/Menu/MenuItem.js +2 -2
  19. package/dist/js/components/Menu/MenuItem.js.map +1 -1
  20. package/dist/js/components/MenuToggle/MenuToggle.d.ts +6 -12
  21. package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
  22. package/dist/js/components/MenuToggle/MenuToggle.js +12 -8
  23. package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
  24. package/dist/js/components/TextInputGroup/TextInputGroup.d.ts +2 -0
  25. package/dist/js/components/TextInputGroup/TextInputGroup.d.ts.map +1 -1
  26. package/dist/js/components/TextInputGroup/TextInputGroup.js +2 -2
  27. package/dist/js/components/TextInputGroup/TextInputGroup.js.map +1 -1
  28. package/dist/js/components/TextInputGroup/TextInputGroupMain.d.ts +1 -1
  29. package/dist/js/components/TextInputGroup/TextInputGroupMain.d.ts.map +1 -1
  30. package/dist/umd/react-core.css +815 -818
  31. package/dist/umd/react-core.js +17 -12
  32. package/dist/umd/react-core.min.css +15 -18
  33. package/dist/umd/react-core.min.js +5 -5
  34. package/package.json +6 -6
  35. package/src/components/Menu/MenuItem.tsx +4 -0
  36. package/src/components/MenuToggle/MenuToggle.tsx +63 -25
  37. package/src/components/TextInputGroup/TextInputGroup.tsx +9 -1
  38. package/src/components/TextInputGroup/TextInputGroupMain.tsx +1 -1
  39. package/src/demos/ComposableMenu/ComposableMenu.md +8 -0
  40. package/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx +196 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "4.227.8",
3
+ "version": "4.227.9",
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.78.8",
38
- "@patternfly/react-styles": "^4.77.8",
39
- "@patternfly/react-tokens": "^4.79.8",
37
+ "@patternfly/react-icons": "^4.78.9",
38
+ "@patternfly/react-styles": "^4.77.9",
39
+ "@patternfly/react-tokens": "^4.79.9",
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.204.3",
46
+ "@patternfly/patternfly": "4.205.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": "09b80fb1de7a57f0917554a36d2e7422d70f551f"
63
+ "gitHead": "997f82511b3461fe6b960fc9412b17ee94864352"
64
64
  }
@@ -50,6 +50,8 @@ export interface MenuItemProps extends Omit<React.HTMLProps<HTMLLIElement>, 'onC
50
50
  isExternalLink?: boolean;
51
51
  /** Flag indicating if the option is selected */
52
52
  isSelected?: boolean;
53
+ /** Flag indicating the item is focused */
54
+ isFocused?: boolean;
53
55
  /** @beta Flyout menu */
54
56
  flyoutMenu?: React.ReactElement;
55
57
  /** @beta Callback function when mouse leaves trigger */
@@ -88,6 +90,7 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
88
90
  isDisabled = false,
89
91
  isExternalLink = false,
90
92
  isSelected = null,
93
+ isFocused,
91
94
  icon,
92
95
  actions,
93
96
  onShowFlyout,
@@ -290,6 +293,7 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
290
293
  _isOnPath && styles.modifiers.currentPath,
291
294
  isLoadButton && styles.modifiers.load,
292
295
  isLoading && styles.modifiers.loading,
296
+ isFocused && styles.modifiers.focus,
293
297
  className
294
298
  )}
295
299
  onMouseOver={onMouseOver}
@@ -4,8 +4,10 @@ import { css } from '@patternfly/react-styles';
4
4
  import CaretDownIcon from '@patternfly/react-icons/dist/esm/icons/caret-down-icon';
5
5
  import { BadgeProps } from '../Badge';
6
6
 
7
+ export type MenuToggleElement = HTMLDivElement | HTMLButtonElement;
8
+
7
9
  export interface MenuToggleProps
8
- extends Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, 'ref'> {
10
+ extends Omit<React.DetailedHTMLProps<React.HTMLAttributes<MenuToggleElement>, MenuToggleElement>, 'ref'> {
9
11
  /** Content rendered inside the toggle */
10
12
  children?: React.ReactNode;
11
13
  /** Additional classes added to the toggle */
@@ -19,18 +21,18 @@ export interface MenuToggleProps
19
21
  /** Flag indicating the toggle takes up the full width of its parent */
20
22
  isFullWidth?: boolean;
21
23
  /** Variant styles of the menu toggle */
22
- variant?: 'default' | 'plain' | 'primary' | 'plainText' | 'secondary';
24
+ variant?: 'default' | 'plain' | 'primary' | 'plainText' | 'secondary' | 'typeahead';
23
25
  /** Optional icon rendered inside the toggle, before the children content */
24
26
  icon?: React.ReactNode;
25
27
  /** Optional badge rendered inside the toggle, after the children content */
26
28
  badge?: BadgeProps | React.ReactNode;
27
29
  /** Forwarded ref */
28
- innerRef?: React.Ref<HTMLButtonElement>;
30
+ innerRef?: React.Ref<MenuToggleElement>;
29
31
  }
30
32
 
31
33
  export class MenuToggleBase extends React.Component<MenuToggleProps> {
32
34
  displayName = 'MenuToggleBase';
33
- static defaultProps = {
35
+ static defaultProps: MenuToggleProps = {
34
36
  className: '',
35
37
  isExpanded: false,
36
38
  isDisabled: false,
@@ -51,24 +53,63 @@ export class MenuToggleBase extends React.Component<MenuToggleProps> {
51
53
  isFullWidth,
52
54
  variant,
53
55
  innerRef,
54
- ...props
56
+ onClick,
57
+ 'aria-label': ariaLabel,
58
+ ...otherProps
55
59
  } = this.props;
56
-
57
60
  const isPlain = variant === 'plain';
58
61
  const isPlainText = variant === 'plainText';
62
+ const isTypeahead = variant === 'typeahead';
63
+ const toggleControls = (
64
+ <span className={css(styles.menuToggleControls)}>
65
+ <span className={css(styles.menuToggleToggleIcon)}>
66
+ <CaretDownIcon aria-hidden />
67
+ </span>
68
+ </span>
69
+ );
59
70
 
60
71
  const content = (
61
- <React.Fragment>
72
+ <>
62
73
  {icon && <span className={css(styles.menuToggleIcon)}>{icon}</span>}
63
- <span className="pf-c-menu-toggle__text">{children}</span>
64
- {badge && <span className={css(styles.menuToggleCount)}>{badge as React.ReactNode}</span>}
65
- <span className={css(styles.menuToggleControls)}>
66
- <span className={css(styles.menuToggleToggleIcon)}>
67
- <CaretDownIcon aria-hidden />
68
- </span>
69
- </span>
70
- </React.Fragment>
74
+ {isTypeahead ? children : <span className="pf-c-menu-toggle__text">{children}</span>}
75
+ {badge && <span className={css(styles.menuToggleCount)}>{badge}</span>}
76
+ {isTypeahead ? (
77
+ <button
78
+ type="button"
79
+ className={css(styles.menuToggleButton)}
80
+ aria-expanded={isExpanded}
81
+ onClick={onClick}
82
+ aria-label="Menu toggle"
83
+ >
84
+ {toggleControls}
85
+ </button>
86
+ ) : (
87
+ toggleControls
88
+ )}
89
+ </>
71
90
  );
91
+
92
+ const componentProps = {
93
+ className: css(
94
+ styles.menuToggle,
95
+ isExpanded && styles.modifiers.expanded,
96
+ variant === 'primary' && styles.modifiers.primary,
97
+ (isPlain || isPlainText) && styles.modifiers.plain,
98
+ isPlainText && styles.modifiers.text,
99
+ isFullHeight && styles.modifiers.fullHeight,
100
+ isTypeahead && styles.modifiers.typeahead,
101
+ isFullWidth && styles.modifiers.fullWidth,
102
+ className
103
+ ),
104
+ children: isPlain ? children : content,
105
+ ...(isDisabled && { disabled: true }),
106
+ ...otherProps
107
+ };
108
+
109
+ if (isTypeahead) {
110
+ return <div ref={innerRef as React.Ref<HTMLDivElement>} {...componentProps} />;
111
+ }
112
+
72
113
  return (
73
114
  <button
74
115
  className={css(
@@ -82,21 +123,18 @@ export class MenuToggleBase extends React.Component<MenuToggleProps> {
82
123
  isFullWidth && styles.modifiers.fullWidth,
83
124
  className
84
125
  )}
126
+ ref={innerRef as React.Ref<HTMLButtonElement>}
85
127
  type="button"
86
- aria-expanded={false}
87
- ref={innerRef}
88
- {...(isExpanded && { 'aria-expanded': true })}
89
- {...(isDisabled && { disabled: true })}
90
- {...props}
91
- >
92
- {isPlain && children}
93
- {!isPlain && content}
94
- </button>
128
+ aria-label={ariaLabel}
129
+ aria-expanded={isExpanded}
130
+ onClick={onClick}
131
+ {...componentProps}
132
+ />
95
133
  );
96
134
  }
97
135
  }
98
136
 
99
- export const MenuToggle = React.forwardRef((props: MenuToggleProps, ref: React.Ref<HTMLButtonElement>) => (
137
+ export const MenuToggle = React.forwardRef((props: MenuToggleProps, ref: React.Ref<MenuToggleElement>) => (
100
138
  <MenuToggleBase innerRef={ref} {...props} />
101
139
  ));
102
140
 
@@ -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(styles.textInputGroup, isDisabled && styles.modifiers.disabled, className)}
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
- /** @hide A reference object to attach to the input box */
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
+ };