@patternfly/react-core 4.232.11 → 4.233.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts +17 -3
  3. package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
  4. package/dist/esm/components/ExpandableSection/ExpandableSection.js +39 -8
  5. package/dist/esm/components/ExpandableSection/ExpandableSection.js.map +1 -1
  6. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts +2 -0
  7. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
  8. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js +4 -4
  9. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
  10. package/dist/esm/components/InputGroup/InputGroup.d.ts.map +1 -1
  11. package/dist/esm/components/InputGroup/InputGroup.js +5 -3
  12. package/dist/esm/components/InputGroup/InputGroup.js.map +1 -1
  13. package/dist/esm/components/Label/Label.d.ts.map +1 -1
  14. package/dist/esm/components/Label/Label.js +3 -0
  15. package/dist/esm/components/Label/Label.js.map +1 -1
  16. package/dist/esm/components/LabelGroup/LabelGroup.d.ts +4 -4
  17. package/dist/esm/components/LabelGroup/LabelGroup.d.ts.map +1 -1
  18. package/dist/esm/components/NumberInput/NumberInput.d.ts +5 -0
  19. package/dist/esm/components/NumberInput/NumberInput.d.ts.map +1 -1
  20. package/dist/esm/components/NumberInput/NumberInput.js +5 -4
  21. package/dist/esm/components/NumberInput/NumberInput.js.map +1 -1
  22. package/dist/js/components/ExpandableSection/ExpandableSection.d.ts +17 -3
  23. package/dist/js/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
  24. package/dist/js/components/ExpandableSection/ExpandableSection.js +40 -9
  25. package/dist/js/components/ExpandableSection/ExpandableSection.js.map +1 -1
  26. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts +2 -0
  27. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
  28. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js +4 -4
  29. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
  30. package/dist/js/components/InputGroup/InputGroup.d.ts.map +1 -1
  31. package/dist/js/components/InputGroup/InputGroup.js +5 -3
  32. package/dist/js/components/InputGroup/InputGroup.js.map +1 -1
  33. package/dist/js/components/Label/Label.d.ts.map +1 -1
  34. package/dist/js/components/Label/Label.js +3 -0
  35. package/dist/js/components/Label/Label.js.map +1 -1
  36. package/dist/js/components/LabelGroup/LabelGroup.d.ts +4 -4
  37. package/dist/js/components/LabelGroup/LabelGroup.d.ts.map +1 -1
  38. package/dist/js/components/NumberInput/NumberInput.d.ts +5 -0
  39. package/dist/js/components/NumberInput/NumberInput.d.ts.map +1 -1
  40. package/dist/js/components/NumberInput/NumberInput.js +4 -3
  41. package/dist/js/components/NumberInput/NumberInput.js.map +1 -1
  42. package/dist/umd/react-core.css +69 -69
  43. package/dist/umd/react-core.js +59 -18
  44. package/dist/umd/react-core.min.css +756 -756
  45. package/dist/umd/react-core.min.js +5 -5
  46. package/package.json +6 -6
  47. package/src/components/ExpandableSection/ExpandableSection.tsx +73 -19
  48. package/src/components/ExpandableSection/ExpandableSectionToggle.tsx +15 -9
  49. package/src/components/ExpandableSection/__tests__/ExpandableSection.test.tsx +27 -1
  50. package/src/components/ExpandableSection/examples/ExpandableSection.md +7 -0
  51. package/src/components/ExpandableSection/examples/ExpandableSectionTruncateExpansion.tsx +27 -0
  52. package/src/components/InputGroup/InputGroup.tsx +5 -3
  53. package/src/components/InputGroup/__tests__/InputGroup.test.tsx +30 -0
  54. package/src/components/Label/Label.tsx +3 -1
  55. package/src/components/LabelGroup/LabelGroup.tsx +4 -4
  56. package/src/components/LabelGroup/examples/LabelGroup.md +1 -1
  57. package/src/components/LabelGroup/examples/LabelGroupEditableAdd.tsx +1 -1
  58. package/src/components/LabelGroup/examples/LabelGroupEditableAddDropdown.tsx +1 -1
  59. package/src/components/LabelGroup/examples/LabelGroupEditableAddModal.tsx +23 -21
  60. package/src/components/NumberInput/NumberInput.tsx +14 -8
  61. package/src/components/NumberInput/__tests__/NumberInput.test.tsx +16 -0
  62. package/src/components/NumberInput/__tests__/__snapshots__/NumberInput.test.tsx.snap +292 -18
  63. package/src/components/NumberInput/examples/NumberInput.md +5 -0
  64. package/src/components/NumberInput/examples/WithStatus.tsx +59 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "4.232.11",
3
+ "version": "4.233.0",
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",
@@ -50,16 +50,16 @@
50
50
  "generate": "node scripts/copyStyles.js"
51
51
  },
52
52
  "dependencies": {
53
- "@patternfly/react-icons": "^4.83.11",
54
- "@patternfly/react-styles": "^4.82.11",
55
- "@patternfly/react-tokens": "^4.84.11",
53
+ "@patternfly/react-icons": "^4.84.0",
54
+ "@patternfly/react-styles": "^4.83.0",
55
+ "@patternfly/react-tokens": "^4.85.0",
56
56
  "focus-trap": "6.9.2",
57
57
  "react-dropzone": "9.0.0",
58
58
  "tippy.js": "5.1.2",
59
59
  "tslib": "^2.0.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@patternfly/patternfly": "4.208.1",
62
+ "@patternfly/patternfly": "4.209.0",
63
63
  "@rollup/plugin-commonjs": "^22.0.2",
64
64
  "@rollup/plugin-node-resolve": "^13.3.0",
65
65
  "@rollup/plugin-replace": "^4.0.0",
@@ -76,5 +76,5 @@
76
76
  "react": "^16.8.0 || ^17.0.0",
77
77
  "react-dom": "^16.8.0 || ^17.0.0"
78
78
  },
79
- "gitHead": "3b4fe0a85d88aa9616d7a6768d187d6fc87abb32"
79
+ "gitHead": "816b8605d11e6374b6f6b4c5d6da80d43265fc70"
80
80
  }
@@ -1,13 +1,19 @@
1
1
  import * as React from 'react';
2
2
  import styles from '@patternfly/react-styles/css/components/ExpandableSection/expandable-section';
3
3
  import { css } from '@patternfly/react-styles';
4
+ import lineClamp from '@patternfly/react-tokens/dist/esm/c_expandable_section_m_truncate__content_LineClamp';
4
5
  import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
5
6
  import { PickOptional } from '../../helpers/typeUtils';
6
7
 
8
+ export enum ExpandableSectionVariant {
9
+ default = 'default',
10
+ truncate = 'truncate'
11
+ }
12
+
7
13
  export interface ExpandableSectionProps extends React.HTMLProps<HTMLDivElement> {
8
- /** Content rendered inside the Expandable Component */
14
+ /** Content rendered inside the expandable section. */
9
15
  children?: React.ReactNode;
10
- /** Additional classes added to the Expandable Component */
16
+ /** Additional classes added to the expandable section. */
11
17
  className?: string;
12
18
  /** Flag to indicate if the content is expanded */
13
19
  isExpanded?: boolean;
@@ -19,7 +25,7 @@ export interface ExpandableSectionProps extends React.HTMLProps<HTMLDivElement>
19
25
  toggleTextCollapsed?: string;
20
26
  /** React node that appears in the attached toggle in place of toggle text */
21
27
  toggleContent?: React.ReactNode;
22
- /** Callback function to toggle the expandable content. Detached expandable sections should use the onToggle property of ExpandableSectionToggle. */
28
+ /** Callback function to toggle the expandable section. Detached expandable sections should use the onToggle property of the expandable section toggle sub-component. */
23
29
  onToggle?: (isExpanded: boolean) => void;
24
30
  /** Forces active state */
25
31
  isActive?: boolean;
@@ -33,12 +39,27 @@ export interface ExpandableSectionProps extends React.HTMLProps<HTMLDivElement>
33
39
  isWidthLimited?: boolean;
34
40
  /** Flag to indicate if the content is indented */
35
41
  isIndented?: boolean;
42
+ /** @beta Determines the variant of the expandable section. When passing in "truncate" as the
43
+ * variant, the expandable content will be truncated after 3 lines by default.
44
+ */
45
+ variant?: 'default' | 'truncate';
46
+ /** @beta Truncates the expandable content to the specified number of lines when using the
47
+ * "truncate" variant. */
48
+ truncateMaxLines?: number;
36
49
  }
37
50
 
38
51
  interface ExpandableSectionState {
39
52
  isExpanded: boolean;
40
53
  }
41
54
 
55
+ const setLineClamp = (lines: number, element: HTMLDivElement) => {
56
+ if (!element || lines < 1) {
57
+ return;
58
+ }
59
+
60
+ element.style.setProperty(lineClamp.name, lines.toString());
61
+ };
62
+
42
63
  export class ExpandableSection extends React.Component<ExpandableSectionProps, ExpandableSectionState> {
43
64
  static displayName = 'ExpandableSection';
44
65
  constructor(props: ExpandableSectionProps) {
@@ -49,6 +70,7 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
49
70
  };
50
71
  }
51
72
 
73
+ expandableContentRef = React.createRef<HTMLDivElement>();
52
74
  static defaultProps: PickOptional<ExpandableSectionProps> = {
53
75
  className: '',
54
76
  toggleText: '',
@@ -61,7 +83,8 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
61
83
  displaySize: 'default',
62
84
  isWidthLimited: false,
63
85
  isIndented: false,
64
- contentId: ''
86
+ contentId: '',
87
+ variant: 'default'
65
88
  };
66
89
 
67
90
  private calculateToggleText(
@@ -79,6 +102,23 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
79
102
  return toggleText;
80
103
  }
81
104
 
105
+ componentDidMount() {
106
+ if (this.props.variant === ExpandableSectionVariant.truncate && this.props.truncateMaxLines) {
107
+ const expandableContent = this.expandableContentRef.current;
108
+ setLineClamp(this.props.truncateMaxLines, expandableContent);
109
+ }
110
+ }
111
+
112
+ componentDidUpdate(prevProps: ExpandableSectionProps) {
113
+ if (
114
+ this.props.variant === ExpandableSectionVariant.truncate &&
115
+ prevProps.truncateMaxLines !== this.props.truncateMaxLines
116
+ ) {
117
+ const expandableContent = this.expandableContentRef.current;
118
+ setLineClamp(this.props.truncateMaxLines, expandableContent);
119
+ }
120
+ }
121
+
82
122
  render() {
83
123
  const {
84
124
  onToggle: onToggleProp,
@@ -96,6 +136,9 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
96
136
  isWidthLimited,
97
137
  isIndented,
98
138
  contentId,
139
+ variant,
140
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
141
+ truncateMaxLines,
99
142
  ...props
100
143
  } = this.props;
101
144
  let onToggle = onToggleProp;
@@ -116,9 +159,24 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
116
159
  propOrStateIsExpanded
117
160
  );
118
161
 
162
+ const expandableToggle = !isDetached && (
163
+ <button
164
+ className={css(styles.expandableSectionToggle)}
165
+ type="button"
166
+ aria-expanded={propOrStateIsExpanded}
167
+ onClick={() => onToggle(!propOrStateIsExpanded)}
168
+ >
169
+ {variant !== ExpandableSectionVariant.truncate && (
170
+ <span className={css(styles.expandableSectionToggleIcon)}>
171
+ <AngleRightIcon aria-hidden />
172
+ </span>
173
+ )}
174
+ <span className={css(styles.expandableSectionToggleText)}>{toggleContent || computedToggleText}</span>
175
+ </button>
176
+ );
177
+
119
178
  return (
120
179
  <div
121
- {...props}
122
180
  className={css(
123
181
  styles.expandableSection,
124
182
  propOrStateIsExpanded && styles.modifiers.expanded,
@@ -127,25 +185,21 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
127
185
  displaySize === 'large' && styles.modifiers.displayLg,
128
186
  isWidthLimited && styles.modifiers.limitWidth,
129
187
  isIndented && styles.modifiers.indented,
188
+ variant === ExpandableSectionVariant.truncate && styles.modifiers.truncate,
130
189
  className
131
190
  )}
191
+ {...props}
132
192
  >
133
- {!isDetached && (
134
- <button
135
- className={css(styles.expandableSectionToggle)}
136
- type="button"
137
- aria-expanded={propOrStateIsExpanded}
138
- onClick={() => onToggle(!propOrStateIsExpanded)}
139
- >
140
- <span className={css(styles.expandableSectionToggleIcon)}>
141
- <AngleRightIcon aria-hidden />
142
- </span>
143
- <span className={css(styles.expandableSectionToggleText)}>{toggleContent || computedToggleText}</span>
144
- </button>
145
- )}
146
- <div className={css(styles.expandableSectionContent)} hidden={!propOrStateIsExpanded} id={contentId}>
193
+ {variant === ExpandableSectionVariant.default && expandableToggle}
194
+ <div
195
+ ref={this.expandableContentRef}
196
+ className={css(styles.expandableSectionContent)}
197
+ hidden={variant !== ExpandableSectionVariant.truncate && !propOrStateIsExpanded}
198
+ id={contentId}
199
+ >
147
200
  {children}
148
201
  </div>
202
+ {variant === ExpandableSectionVariant.truncate && expandableToggle}
149
203
  </div>
150
204
  );
151
205
  }
@@ -16,6 +16,8 @@ export interface ExpandableSectionToggleProps extends React.HTMLProps<HTMLDivEle
16
16
  contentId?: string;
17
17
  /** Direction the toggle arrow should point when the expandable section is expanded. */
18
18
  direction?: 'up' | 'down';
19
+ /** @beta Flag to determine toggle styling when the expandable content is truncated. */
20
+ hasTruncatedContent?: boolean;
19
21
  }
20
22
 
21
23
  export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionToggleProps> = ({
@@ -25,16 +27,18 @@ export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionT
25
27
  onToggle,
26
28
  contentId,
27
29
  direction = 'down',
30
+ hasTruncatedContent = false,
28
31
  ...props
29
32
  }: ExpandableSectionToggleProps) => (
30
33
  <div
31
- {...props}
32
34
  className={css(
33
35
  styles.expandableSection,
34
36
  isExpanded && styles.modifiers.expanded,
35
37
  styles.modifiers.detached,
38
+ hasTruncatedContent && styles.modifiers.truncate,
36
39
  className
37
40
  )}
41
+ {...props}
38
42
  >
39
43
  <button
40
44
  className={css(styles.expandableSectionToggle)}
@@ -43,14 +47,16 @@ export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionT
43
47
  aria-controls={contentId}
44
48
  onClick={() => onToggle(!isExpanded)}
45
49
  >
46
- <span
47
- className={css(
48
- styles.expandableSectionToggleIcon,
49
- isExpanded && direction === 'up' && styles.modifiers.expandTop
50
- )}
51
- >
52
- <AngleRightIcon aria-hidden />
53
- </span>
50
+ {!hasTruncatedContent && (
51
+ <span
52
+ className={css(
53
+ styles.expandableSectionToggleIcon,
54
+ isExpanded && direction === 'up' && styles.modifiers.expandTop
55
+ )}
56
+ >
57
+ <AngleRightIcon aria-hidden />
58
+ </span>
59
+ )}
54
60
  <span className={css(styles.expandableSectionToggleText)}>{children}</span>
55
61
  </button>
56
62
  </div>
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { render, screen } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
5
 
6
- import { ExpandableSection } from '../ExpandableSection';
6
+ import { ExpandableSection, ExpandableSectionVariant } from '../ExpandableSection';
7
7
  import { ExpandableSectionToggle } from '../ExpandableSectionToggle';
8
8
 
9
9
  const props = {};
@@ -64,3 +64,29 @@ test('Renders ExpandableSection indented', () => {
64
64
  );
65
65
  expect(asFragment()).toMatchSnapshot();
66
66
  });
67
+
68
+ test('Does not render with pf-m-truncate class when variant is not passed', () => {
69
+ render(<ExpandableSection {...props}>test</ExpandableSection>);
70
+
71
+ expect(screen.getByText('test').parentElement).not.toHaveClass('pf-m-truncate');
72
+ });
73
+
74
+ test('Does not render with pf-m-truncate class when variant is not truncate', () => {
75
+ render(
76
+ <ExpandableSection variant={ExpandableSectionVariant.default} {...props}>
77
+ test
78
+ </ExpandableSection>
79
+ );
80
+
81
+ expect(screen.getByText('test').parentElement).not.toHaveClass('pf-m-truncate');
82
+ });
83
+
84
+ test('Renders with pf-m-truncate class when variant is truncate', () => {
85
+ render(
86
+ <ExpandableSection variant={ExpandableSectionVariant.truncate} {...props}>
87
+ test
88
+ </ExpandableSection>
89
+ );
90
+
91
+ expect(screen.getByText('test').parentElement).toHaveClass('pf-m-truncate');
92
+ });
@@ -45,3 +45,10 @@ By using the `toggleContent` prop, you can pass in content other than a simple s
45
45
 
46
46
  ```ts file="ExpandableSectionCustomToggle.tsx"
47
47
  ```
48
+
49
+ ### Truncate expansion
50
+
51
+ By passing in `variant="truncate"`, the expandable content will be visible up to a maximum number of lines before being truncated, with the toggle revealing or hiding the truncated content. By default the expandable content will truncate after 3 lines, and this can be customized by also passing in the `truncateMaxLines` prop.
52
+
53
+ ```ts file="ExpandableSectionTruncateExpansion.tsx" isBeta
54
+ ```
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { ExpandableSection, ExpandableSectionVariant } from '@patternfly/react-core';
3
+
4
+ export const ExpandableSectionTruncateExpansion: React.FunctionComponent = () => {
5
+ const [isExpanded, setIsExpanded] = React.useState(false);
6
+
7
+ const onToggle = (isExpanded: boolean) => {
8
+ setIsExpanded(isExpanded);
9
+ };
10
+
11
+ return (
12
+ <ExpandableSection
13
+ variant={ExpandableSectionVariant.truncate}
14
+ toggleText={isExpanded ? 'Show less' : 'Show more'}
15
+ onToggle={onToggle}
16
+ isExpanded={isExpanded}
17
+ >
18
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque nec dignissim turpis, et tristique purus.
19
+ Phasellus efficitur ante quis dolor viverra imperdiet. Orci varius natoque penatibus et magnis dis parturient
20
+ montes, nascetur ridiculus mus. Pellentesque laoreet, sem ac elementum semper, lectus mauris vestibulum nulla,
21
+ eget volutpat massa neque vel turpis. Donec finibus enim eu leo accumsan consectetur. Praesent massa diam,
22
+ tincidunt eu dui ac, ullamcorper elementum est. Phasellus metus felis, venenatis vitae semper nec, porta a metus.
23
+ Vestibulum justo nisi, imperdiet id eleifend at, varius nec lorem. Fusce porttitor mollis nibh, ut elementum ante
24
+ commodo tincidunt. Integer tincidunt at ipsum non aliquet.
25
+ </ExpandableSection>
26
+ );
27
+ };
@@ -31,9 +31,11 @@ export const InputGroup: React.FunctionComponent<InputGroupProps> = ({
31
31
  <div ref={inputGroupRef} className={css(styles.inputGroup, className)} {...props}>
32
32
  {idItem
33
33
  ? React.Children.map(children, (child: any) =>
34
- formCtrls.includes(child.type.displayName)
35
- ? React.cloneElement(child, { 'aria-describedby': idItem.props.id })
36
- : child
34
+ !formCtrls.includes(child.type.displayName) || child.props['aria-describedby']
35
+ ? child
36
+ : React.cloneElement(child, {
37
+ 'aria-describedby': child.props['aria-describedby'] === '' ? undefined : idItem.props.id
38
+ })
37
39
  )
38
40
  : children}
39
41
  </div>
@@ -20,4 +20,34 @@ describe('InputGroup', () => {
20
20
  );
21
21
  expect(screen.getByLabelText('some text')).toHaveAttribute('aria-describedby', 'button-id');
22
22
  });
23
+
24
+ test('wont add aria-describedby to form-control if describedby is empty string', () => {
25
+ // In this test, TextInput is a form-control component and Button is not.
26
+ // If Button has an id props, this should be used in aria-describedby, but this
27
+ // example has an empty aria-describedby to prevent that from happening.
28
+ render(
29
+ <InputGroup>
30
+ <TextInput value="some data" aria-describedby="" aria-label="some text" />
31
+ <Button id="button-id">
32
+ hello
33
+ </Button>
34
+ </InputGroup>
35
+ );
36
+ expect(screen.getByLabelText('some text')).not.toHaveAttribute('aria-describedby');
37
+ });
38
+
39
+ test('wont override aria-describedby in form-control if describedby has value', () => {
40
+ // In this test, TextInput is a form-control component and Button is not.
41
+ // If Button has an id props, this should be used in aria-describedby, but this
42
+ // example has a predefined aria-describedby to prevent that from happening
43
+ render(
44
+ <InputGroup>
45
+ <TextInput value="some data" aria-describedby="myself" aria-label="some text" />
46
+ <Button id="button-id">
47
+ hello
48
+ </Button>
49
+ </InputGroup>
50
+ );
51
+ expect(screen.getByLabelText('some text')).toHaveAttribute('aria-describedby', 'myself');
52
+ });
23
53
  });
@@ -104,7 +104,7 @@ export const Label: React.FunctionComponent<LabelProps> = ({
104
104
  render,
105
105
  ...props
106
106
  }: LabelProps) => {
107
- const [isEditableActive, setIsEditableActive] = useState(false);
107
+ const [isEditableActive, setIsEditableActive] = useState<boolean>(false);
108
108
  const [currValue, setCurrValue] = useState(children);
109
109
  const editableButtonRef = React.useRef<HTMLButtonElement>();
110
110
  const editableInputRef = React.useRef<HTMLInputElement>();
@@ -151,6 +151,7 @@ export const Label: React.FunctionComponent<LabelProps> = ({
151
151
  onEditComplete && onEditComplete(editableInputRef.current.value);
152
152
  }
153
153
  setIsEditableActive(false);
154
+ editableButtonRef?.current?.focus();
154
155
  }
155
156
  if (isEditableActive && key === 'Escape') {
156
157
  event.preventDefault();
@@ -161,6 +162,7 @@ export const Label: React.FunctionComponent<LabelProps> = ({
161
162
  onEditCancel && onEditCancel(children as string);
162
163
  }
163
164
  setIsEditableActive(false);
165
+ editableButtonRef?.current?.focus();
164
166
  }
165
167
  if (!isEditableActive && key === 'Enter') {
166
168
  event.preventDefault();
@@ -52,13 +52,13 @@ export interface LabelGroupProps extends React.HTMLProps<HTMLUListElement> {
52
52
  | 'right-end';
53
53
  /** Flag to implement a vertical layout */
54
54
  isVertical?: boolean;
55
- /** @beta Flag indicating contained labels are editable. Allows spacing for a text input after the labels. */
55
+ /** Flag indicating contained labels are editable. Allows spacing for a text input after the labels. */
56
56
  isEditable?: boolean;
57
- /** @beta Flag indicating the editable label group should be appended with a textarea. */
57
+ /** Flag indicating the editable label group should be appended with a textarea. */
58
58
  hasEditableTextArea?: boolean;
59
- /** @beta Additional props passed to the editable textarea. */
59
+ /** Additional props passed to the editable textarea. */
60
60
  editableTextAreaProps?: any;
61
- /** @beta Control for adding new labels */
61
+ /** Control for adding new labels */
62
62
  addLabelControl?: React.ReactNode;
63
63
  }
64
64
 
@@ -38,7 +38,7 @@ import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-i
38
38
 
39
39
  ### Editable labels
40
40
 
41
- ```ts file="LabelGroupEditableLabels.tsx" isBeta
41
+ ```ts file="LabelGroupEditableLabels.tsx"
42
42
  ```
43
43
 
44
44
  ### Editable labels with add button
@@ -58,7 +58,7 @@ export const LabelGroupEditableAdd: React.FunctionComponent = () => {
58
58
  >
59
59
  {labels.map((label, index) => (
60
60
  <Label
61
- key={`${label.name}-${index}`}
61
+ key={label.id}
62
62
  id={`${label.name}-${index}`}
63
63
  color="blue"
64
64
  onClose={() => onClose(label.id)}
@@ -121,7 +121,7 @@ export const LabelGroupEditableAddDropdown: React.FunctionComponent = () => {
121
121
  >
122
122
  {labels.map((label, index) => (
123
123
  <Label
124
- key={`${label.name}-${index}`}
124
+ key={label.id}
125
125
  id={`${label.name}-${index}`}
126
126
  color="blue"
127
127
  onClose={() => onClose(label.id)}
@@ -27,17 +27,17 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
27
27
  const [labelType, setLabelType] = React.useState<string>('filled');
28
28
  const [isClosable, setIsCloseable] = React.useState<boolean>(false);
29
29
  const [isEditable, setIsEditable] = React.useState<boolean>(false);
30
- const labelInputRef = React.useRef();
30
+ const labelInputRef = React.useRef(null);
31
31
 
32
32
  const [isColorOpen, setIsColorOpen] = React.useState<boolean>(false);
33
- const colorMenuRef = React.useRef<HTMLDivElement>();
34
- const colorContainerRef = React.useRef<HTMLDivElement>();
35
- const colorToggleRef = React.useRef<HTMLButtonElement>();
33
+ const colorMenuRef = React.useRef<HTMLDivElement>(null);
34
+ const colorContainerRef = React.useRef<HTMLDivElement>(null);
35
+ const colorToggleRef = React.useRef<HTMLButtonElement>(null);
36
36
 
37
37
  const [isIconOpen, setIsIconOpen] = React.useState<boolean>(false);
38
- const iconMenuRef = React.useRef<HTMLDivElement>();
39
- const iconContainerRef = React.useRef<HTMLDivElement>();
40
- const iconToggleRef = React.useRef<HTMLButtonElement>();
38
+ const iconMenuRef = React.useRef<HTMLDivElement>(null);
39
+ const iconContainerRef = React.useRef<HTMLDivElement>(null);
40
+ const iconToggleRef = React.useRef<HTMLButtonElement>(null);
41
41
 
42
42
  const [labels, setLabels] = React.useState<any>([
43
43
  { name: 'Label 1', id: 0 },
@@ -65,7 +65,7 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
65
65
  };
66
66
 
67
67
  const onAdd = () => {
68
- let labelIcon = null;
68
+ let labelIcon: any;
69
69
  if (icon === 'Info circle icon') {
70
70
  labelIcon = <InfoCircleIcon />;
71
71
  }
@@ -92,7 +92,7 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
92
92
  setModalOpen(!isModalOpen);
93
93
  setIdIndex(idIndex + 1);
94
94
  setLabelText('');
95
- setColor(null);
95
+ setColor('');
96
96
  setIcon(null);
97
97
  setLabelType('filled');
98
98
  setIsCloseable(false);
@@ -110,25 +110,25 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
110
110
  }, [isModalOpen]);
111
111
 
112
112
  const handleMenuKeys = (event: KeyboardEvent) => {
113
- if (isColorOpen && colorMenuRef.current.contains(event.target as Node)) {
113
+ if (isColorOpen && colorMenuRef?.current?.contains(event.target as Node)) {
114
114
  if (event.key === 'Escape' || event.key === 'Tab') {
115
115
  setIsColorOpen(!isColorOpen);
116
- colorToggleRef.current.focus();
116
+ colorToggleRef?.current?.focus();
117
117
  }
118
118
  }
119
- if (isIconOpen && iconMenuRef.current.contains(event.target as Node)) {
119
+ if (isIconOpen && iconMenuRef?.current?.contains(event.target as Node)) {
120
120
  if (event.key === 'Escape' || event.key === 'Tab') {
121
121
  setIsIconOpen(!isIconOpen);
122
- iconToggleRef.current.focus();
122
+ iconToggleRef?.current?.focus();
123
123
  }
124
124
  }
125
125
  };
126
126
 
127
127
  const handleClickOutside = (event: MouseEvent) => {
128
- if (isColorOpen && !colorMenuRef.current.contains(event.target as Node)) {
128
+ if (isColorOpen && !colorMenuRef?.current?.contains(event.target as Node)) {
129
129
  setIsColorOpen(false);
130
130
  }
131
- if (isIconOpen && !iconMenuRef.current.contains(event.target as Node)) {
131
+ if (isIconOpen && !iconMenuRef?.current?.contains(event.target as Node)) {
132
132
  setIsIconOpen(false);
133
133
  }
134
134
  };
@@ -164,7 +164,8 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
164
164
  ref={colorMenuRef}
165
165
  activeItemId={color}
166
166
  onSelect={(_ev, itemId) => {
167
- setColor(itemId.toString());
167
+ setColor(itemId?.toString());
168
+ colorToggleRef?.current?.focus();
168
169
  setIsColorOpen(false);
169
170
  }}
170
171
  selected={color}
@@ -205,7 +206,8 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
205
206
  ref={iconMenuRef}
206
207
  activeItemId={icon}
207
208
  onSelect={(_ev, itemId) => {
208
- setIcon(itemId.toString());
209
+ setIcon(itemId?.toString());
210
+ iconToggleRef?.current?.focus();
209
211
  setIsIconOpen(false);
210
212
  }}
211
213
  selected={icon}
@@ -234,9 +236,9 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
234
236
  </Label>
235
237
  }
236
238
  >
237
- {labels.map((label, index) => (
239
+ {labels.map((label: { name: string; id: string; props: any }, index: number) => (
238
240
  <Label
239
- key={`${label.name}-${index}`}
241
+ key={label.id}
240
242
  id={`${label.name}-${index}`}
241
243
  color="blue"
242
244
  onClose={() => onClose(label.id)}
@@ -278,7 +280,7 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
278
280
  <Popper
279
281
  trigger={colorToggle}
280
282
  popper={colorMenu}
281
- appendTo={colorContainerRef.current}
283
+ appendTo={colorContainerRef.current as HTMLElement}
282
284
  isVisible={isColorOpen}
283
285
  popperMatchesTriggerWidth={false}
284
286
  />
@@ -289,7 +291,7 @@ export const LabelGroupEditableAddModal: React.FunctionComponent = () => {
289
291
  <Popper
290
292
  trigger={iconToggle}
291
293
  popper={iconMenu}
292
- appendTo={iconContainerRef.current}
294
+ appendTo={iconContainerRef.current as HTMLElement}
293
295
  isVisible={isIconOpen}
294
296
  popperMatchesTriggerWidth={false}
295
297
  />
@@ -5,7 +5,8 @@ import MinusIcon from '@patternfly/react-icons/dist/esm/icons/minus-icon';
5
5
  import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
6
6
  import { InputGroup } from '../InputGroup';
7
7
  import { Button, ButtonProps } from '../Button';
8
- import { KEY_CODES } from '../../helpers';
8
+ import { KEY_CODES, ValidatedOptions } from '../../helpers';
9
+ import { TextInput } from '../TextInput';
9
10
 
10
11
  export interface NumberInputProps extends React.HTMLProps<HTMLDivElement> {
11
12
  /** Value of the number input */
@@ -16,6 +17,10 @@ export interface NumberInputProps extends React.HTMLProps<HTMLDivElement> {
16
17
  widthChars?: number;
17
18
  /** Indicates the whole number input should be disabled */
18
19
  isDisabled?: boolean;
20
+ /** Value to indicate if the input is modified to show that validation state
21
+ * @beta
22
+ */
23
+ validated?: 'default' | 'error' | 'warning' | 'success' | ValidatedOptions;
19
24
  /** Callback for the minus button */
20
25
  onMinus?: (event: React.MouseEvent, name?: string) => void;
21
26
  /** Callback for the text input changing */
@@ -66,6 +71,7 @@ export const NumberInput: React.FunctionComponent<NumberInputProps> = ({
66
71
  className,
67
72
  widthChars,
68
73
  isDisabled = false,
74
+ validated = ValidatedOptions.default,
69
75
  onMinus = () => {},
70
76
  onChange,
71
77
  onBlur,
@@ -99,7 +105,7 @@ export const NumberInput: React.FunctionComponent<NumberInputProps> = ({
99
105
 
100
106
  return (
101
107
  <div
102
- className={css(styles.numberInput, className)}
108
+ className={css(styles.numberInput, validated !== 'default' && styles.modifiers.status, className)}
103
109
  {...(widthChars && {
104
110
  style: {
105
111
  '--pf-c-number-input--c-form-control--width-chars': widthChars,
@@ -121,18 +127,18 @@ export const NumberInput: React.FunctionComponent<NumberInputProps> = ({
121
127
  <MinusIcon aria-hidden="true" />
122
128
  </span>
123
129
  </Button>
124
- <input
125
- className={css(styles.formControl)}
130
+ <TextInput
126
131
  type="number"
127
132
  value={value}
128
133
  name={inputName}
129
134
  aria-label={inputAriaLabel}
130
- {...(isDisabled && { disabled: isDisabled })}
131
- {...(onChange && { onChange })}
135
+ {...(isDisabled && { isDisabled })}
136
+ {...(onChange && { onChange: (value, event) => onChange(event) })}
132
137
  onBlur={handleBlur}
133
- {...(!onChange && { readOnly: true })}
134
- {...inputProps}
138
+ {...(!onChange && { isReadOnly: true })}
135
139
  onKeyDown={keyDownHandler}
140
+ validated={validated}
141
+ {...inputProps}
136
142
  />
137
143
  <Button
138
144
  variant="control"