@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.
- package/CHANGELOG.md +27 -0
- package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts +17 -3
- package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
- package/dist/esm/components/ExpandableSection/ExpandableSection.js +39 -8
- package/dist/esm/components/ExpandableSection/ExpandableSection.js.map +1 -1
- package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts +2 -0
- package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
- package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js +4 -4
- package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
- package/dist/esm/components/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/esm/components/InputGroup/InputGroup.js +5 -3
- package/dist/esm/components/InputGroup/InputGroup.js.map +1 -1
- package/dist/esm/components/Label/Label.d.ts.map +1 -1
- package/dist/esm/components/Label/Label.js +3 -0
- package/dist/esm/components/Label/Label.js.map +1 -1
- package/dist/esm/components/LabelGroup/LabelGroup.d.ts +4 -4
- package/dist/esm/components/LabelGroup/LabelGroup.d.ts.map +1 -1
- package/dist/esm/components/NumberInput/NumberInput.d.ts +5 -0
- package/dist/esm/components/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/esm/components/NumberInput/NumberInput.js +5 -4
- package/dist/esm/components/NumberInput/NumberInput.js.map +1 -1
- package/dist/js/components/ExpandableSection/ExpandableSection.d.ts +17 -3
- package/dist/js/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
- package/dist/js/components/ExpandableSection/ExpandableSection.js +40 -9
- package/dist/js/components/ExpandableSection/ExpandableSection.js.map +1 -1
- package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts +2 -0
- package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
- package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js +4 -4
- package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
- package/dist/js/components/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/js/components/InputGroup/InputGroup.js +5 -3
- package/dist/js/components/InputGroup/InputGroup.js.map +1 -1
- package/dist/js/components/Label/Label.d.ts.map +1 -1
- package/dist/js/components/Label/Label.js +3 -0
- package/dist/js/components/Label/Label.js.map +1 -1
- package/dist/js/components/LabelGroup/LabelGroup.d.ts +4 -4
- package/dist/js/components/LabelGroup/LabelGroup.d.ts.map +1 -1
- package/dist/js/components/NumberInput/NumberInput.d.ts +5 -0
- package/dist/js/components/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/js/components/NumberInput/NumberInput.js +4 -3
- package/dist/js/components/NumberInput/NumberInput.js.map +1 -1
- package/dist/umd/react-core.css +69 -69
- package/dist/umd/react-core.js +59 -18
- package/dist/umd/react-core.min.css +756 -756
- package/dist/umd/react-core.min.js +5 -5
- package/package.json +6 -6
- package/src/components/ExpandableSection/ExpandableSection.tsx +73 -19
- package/src/components/ExpandableSection/ExpandableSectionToggle.tsx +15 -9
- package/src/components/ExpandableSection/__tests__/ExpandableSection.test.tsx +27 -1
- package/src/components/ExpandableSection/examples/ExpandableSection.md +7 -0
- package/src/components/ExpandableSection/examples/ExpandableSectionTruncateExpansion.tsx +27 -0
- package/src/components/InputGroup/InputGroup.tsx +5 -3
- package/src/components/InputGroup/__tests__/InputGroup.test.tsx +30 -0
- package/src/components/Label/Label.tsx +3 -1
- package/src/components/LabelGroup/LabelGroup.tsx +4 -4
- package/src/components/LabelGroup/examples/LabelGroup.md +1 -1
- package/src/components/LabelGroup/examples/LabelGroupEditableAdd.tsx +1 -1
- package/src/components/LabelGroup/examples/LabelGroupEditableAddDropdown.tsx +1 -1
- package/src/components/LabelGroup/examples/LabelGroupEditableAddModal.tsx +23 -21
- package/src/components/NumberInput/NumberInput.tsx +14 -8
- package/src/components/NumberInput/__tests__/NumberInput.test.tsx +16 -0
- package/src/components/NumberInput/__tests__/__snapshots__/NumberInput.test.tsx.snap +292 -18
- package/src/components/NumberInput/examples/NumberInput.md +5 -0
- 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.
|
|
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.
|
|
54
|
-
"@patternfly/react-styles": "^4.
|
|
55
|
-
"@patternfly/react-tokens": "^4.
|
|
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.
|
|
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": "
|
|
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
|
|
14
|
+
/** Content rendered inside the expandable section. */
|
|
9
15
|
children?: React.ReactNode;
|
|
10
|
-
/** Additional classes added to the
|
|
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
|
|
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
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
?
|
|
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
|
-
/**
|
|
55
|
+
/** Flag indicating contained labels are editable. Allows spacing for a text input after the labels. */
|
|
56
56
|
isEditable?: boolean;
|
|
57
|
-
/**
|
|
57
|
+
/** Flag indicating the editable label group should be appended with a textarea. */
|
|
58
58
|
hasEditableTextArea?: boolean;
|
|
59
|
-
/**
|
|
59
|
+
/** Additional props passed to the editable textarea. */
|
|
60
60
|
editableTextAreaProps?: any;
|
|
61
|
-
/**
|
|
61
|
+
/** Control for adding new labels */
|
|
62
62
|
addLabelControl?: React.ReactNode;
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -121,7 +121,7 @@ export const LabelGroupEditableAddDropdown: React.FunctionComponent = () => {
|
|
|
121
121
|
>
|
|
122
122
|
{labels.map((label, index) => (
|
|
123
123
|
<Label
|
|
124
|
-
key={
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
116
|
+
colorToggleRef?.current?.focus();
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
if (isIconOpen && iconMenuRef
|
|
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
|
|
122
|
+
iconToggleRef?.current?.focus();
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
const handleClickOutside = (event: MouseEvent) => {
|
|
128
|
-
if (isColorOpen && !colorMenuRef
|
|
128
|
+
if (isColorOpen && !colorMenuRef?.current?.contains(event.target as Node)) {
|
|
129
129
|
setIsColorOpen(false);
|
|
130
130
|
}
|
|
131
|
-
if (isIconOpen && !iconMenuRef
|
|
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
|
|
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
|
|
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={
|
|
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
|
-
<
|
|
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 && {
|
|
131
|
-
{...(onChange && { onChange })}
|
|
135
|
+
{...(isDisabled && { isDisabled })}
|
|
136
|
+
{...(onChange && { onChange: (value, event) => onChange(event) })}
|
|
132
137
|
onBlur={handleBlur}
|
|
133
|
-
{...(!onChange && {
|
|
134
|
-
{...inputProps}
|
|
138
|
+
{...(!onChange && { isReadOnly: true })}
|
|
135
139
|
onKeyDown={keyDownHandler}
|
|
140
|
+
validated={validated}
|
|
141
|
+
{...inputProps}
|
|
136
142
|
/>
|
|
137
143
|
<Button
|
|
138
144
|
variant="control"
|