@patternfly/react-core 4.213.1 → 4.213.4
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 +33 -0
- package/dist/esm/components/ApplicationLauncher/ApplicationLauncher.d.ts +2 -9
- package/dist/esm/components/ApplicationLauncher/ApplicationLauncher.d.ts.map +1 -1
- package/dist/esm/components/ApplicationLauncher/ApplicationLauncher.js.map +1 -1
- package/dist/esm/components/Card/Card.d.ts +7 -0
- package/dist/esm/components/Card/Card.d.ts.map +1 -1
- package/dist/esm/components/Card/Card.js +24 -1
- package/dist/esm/components/Card/Card.js.map +1 -1
- package/dist/esm/components/Card/CardTitle.d.ts.map +1 -1
- package/dist/esm/components/Card/CardTitle.js +8 -1
- package/dist/esm/components/Card/CardTitle.js.map +1 -1
- package/dist/esm/components/ContextSelector/ContextSelector.d.ts +2 -9
- package/dist/esm/components/ContextSelector/ContextSelector.d.ts.map +1 -1
- package/dist/esm/components/ContextSelector/ContextSelector.js.map +1 -1
- package/dist/esm/components/Dropdown/Dropdown.d.ts +2 -9
- package/dist/esm/components/Dropdown/Dropdown.d.ts.map +1 -1
- package/dist/esm/components/Dropdown/Dropdown.js +2 -2
- package/dist/esm/components/Dropdown/Dropdown.js.map +1 -1
- package/dist/esm/components/OptionsMenu/OptionsMenu.d.ts +2 -9
- package/dist/esm/components/OptionsMenu/OptionsMenu.d.ts.map +1 -1
- package/dist/esm/components/OptionsMenu/OptionsMenu.js.map +1 -1
- package/dist/esm/components/Select/Select.d.ts +2 -9
- package/dist/esm/components/Select/Select.d.ts.map +1 -1
- package/dist/esm/components/Select/Select.js.map +1 -1
- package/dist/esm/helpers/Popper/Popper.d.ts +10 -0
- package/dist/esm/helpers/Popper/Popper.d.ts.map +1 -1
- package/dist/esm/helpers/Popper/Popper.js.map +1 -1
- package/dist/js/components/ApplicationLauncher/ApplicationLauncher.d.ts +2 -9
- package/dist/js/components/ApplicationLauncher/ApplicationLauncher.d.ts.map +1 -1
- package/dist/js/components/ApplicationLauncher/ApplicationLauncher.js.map +1 -1
- package/dist/js/components/Card/Card.d.ts +7 -0
- package/dist/js/components/Card/Card.d.ts.map +1 -1
- package/dist/js/components/Card/Card.js +24 -1
- package/dist/js/components/Card/Card.js.map +1 -1
- package/dist/js/components/Card/CardTitle.d.ts.map +1 -1
- package/dist/js/components/Card/CardTitle.js +8 -1
- package/dist/js/components/Card/CardTitle.js.map +1 -1
- package/dist/js/components/ContextSelector/ContextSelector.d.ts +2 -9
- package/dist/js/components/ContextSelector/ContextSelector.d.ts.map +1 -1
- package/dist/js/components/ContextSelector/ContextSelector.js.map +1 -1
- package/dist/js/components/Dropdown/Dropdown.d.ts +2 -9
- package/dist/js/components/Dropdown/Dropdown.d.ts.map +1 -1
- package/dist/js/components/Dropdown/Dropdown.js +2 -2
- package/dist/js/components/Dropdown/Dropdown.js.map +1 -1
- package/dist/js/components/OptionsMenu/OptionsMenu.d.ts +2 -9
- package/dist/js/components/OptionsMenu/OptionsMenu.d.ts.map +1 -1
- package/dist/js/components/OptionsMenu/OptionsMenu.js.map +1 -1
- package/dist/js/components/Select/Select.d.ts +2 -9
- package/dist/js/components/Select/Select.d.ts.map +1 -1
- package/dist/js/components/Select/Select.js.map +1 -1
- package/dist/js/helpers/Popper/Popper.d.ts +10 -0
- package/dist/js/helpers/Popper/Popper.d.ts.map +1 -1
- package/dist/js/helpers/Popper/Popper.js.map +1 -1
- package/dist/umd/react-core.css +69 -69
- package/dist/umd/react-core.js +33 -4
- package/dist/umd/react-core.min.js +1 -1
- package/package.json +5 -5
- package/src/components/ApplicationLauncher/ApplicationLauncher.tsx +2 -9
- package/src/components/Card/Card.tsx +53 -0
- package/src/components/Card/CardTitle.tsx +11 -1
- package/src/components/Card/__tests__/Card.test.tsx +90 -1
- package/src/components/Card/__tests__/CardTitle.test.tsx +13 -0
- package/src/components/Card/examples/Card.md +17 -0
- package/src/components/Card/examples/CardLegacySelectable.tsx +11 -2
- package/src/components/Card/examples/CardSelectable.tsx +10 -1
- package/src/components/Card/examples/CardSelectableA11yHighlight.tsx +61 -0
- package/src/components/ContextSelector/ContextSelector.tsx +2 -9
- package/src/components/Dropdown/Dropdown.tsx +3 -11
- package/src/components/OptionsMenu/OptionsMenu.tsx +2 -9
- package/src/components/Select/Select.tsx +3 -10
- package/src/demos/Card/Card.md +29 -29
- package/src/demos/PrimaryDetail.md +16 -2
- package/src/demos/examples/Tabs/ModalTabs.tsx +4 -1
- package/src/helpers/Popper/Popper.tsx +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/react-core",
|
|
3
|
-
"version": "4.213.
|
|
3
|
+
"version": "4.213.4",
|
|
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,9 +34,9 @@
|
|
|
34
34
|
"generate": "node scripts/copyStyles.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@patternfly/react-icons": "^4.64.
|
|
38
|
-
"@patternfly/react-styles": "^4.63.
|
|
39
|
-
"@patternfly/react-tokens": "^4.65.
|
|
37
|
+
"@patternfly/react-icons": "^4.64.4",
|
|
38
|
+
"@patternfly/react-styles": "^4.63.4",
|
|
39
|
+
"@patternfly/react-tokens": "^4.65.4",
|
|
40
40
|
"focus-trap": "6.2.2",
|
|
41
41
|
"react-dropzone": "9.0.0",
|
|
42
42
|
"tippy.js": "5.1.2",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"react": "^16.8.0 || ^17.0.0",
|
|
61
61
|
"react-dom": "^16.8.0 || ^17.0.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "351d36e715f337de045e0652779d01393c1cf812"
|
|
64
64
|
}
|
|
@@ -9,9 +9,10 @@ import { ApplicationLauncherGroup } from './ApplicationLauncherGroup';
|
|
|
9
9
|
import { ApplicationLauncherSeparator } from './ApplicationLauncherSeparator';
|
|
10
10
|
import { ApplicationLauncherItem } from './ApplicationLauncherItem';
|
|
11
11
|
import { ApplicationLauncherContext } from './ApplicationLauncherContext';
|
|
12
|
+
import { ToggleMenuBaseProps } from '../../helpers/Popper/Popper';
|
|
12
13
|
import { createRenderableFavorites, extendItemsWithFavorite } from '../../helpers/favorites';
|
|
13
14
|
|
|
14
|
-
export interface ApplicationLauncherProps extends React.HTMLProps<HTMLDivElement> {
|
|
15
|
+
export interface ApplicationLauncherProps extends ToggleMenuBaseProps, React.HTMLProps<HTMLDivElement> {
|
|
15
16
|
/** Additional element css classes */
|
|
16
17
|
className?: string;
|
|
17
18
|
/** Display menu above or below dropdown toggle */
|
|
@@ -34,14 +35,6 @@ export interface ApplicationLauncherProps extends React.HTMLProps<HTMLDivElement
|
|
|
34
35
|
isGrouped?: boolean;
|
|
35
36
|
/** Toggle Icon, optional to override the icon used for the toggle */
|
|
36
37
|
toggleIcon?: React.ReactNode;
|
|
37
|
-
/** The container to append the menu to. Defaults to 'inline'.
|
|
38
|
-
* If your menu is being cut off you can append it to an element higher up the DOM tree.
|
|
39
|
-
* Some examples:
|
|
40
|
-
* menuAppendTo="parent"
|
|
41
|
-
* menuAppendTo={() => document.body}
|
|
42
|
-
* menuAppendTo={document.getElementById('target')}
|
|
43
|
-
*/
|
|
44
|
-
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
|
|
45
38
|
/** ID list of favorited ApplicationLauncherItems */
|
|
46
39
|
favorites?: string[];
|
|
47
40
|
/** Enables favorites. Callback called when an ApplicationLauncherItem's favorite button is clicked */
|
|
@@ -36,15 +36,28 @@ export interface CardProps extends React.HTMLProps<HTMLElement>, OUIAProps {
|
|
|
36
36
|
isPlain?: boolean;
|
|
37
37
|
/** Flag indicating if a card is expanded. Modifies the card to be expandable. */
|
|
38
38
|
isExpanded?: boolean;
|
|
39
|
+
/** Flag indicating that the card should render a hidden input to make it selectable */
|
|
40
|
+
hasSelectableInput?: boolean;
|
|
41
|
+
/** Aria label to apply to the selectable input if one is rendered */
|
|
42
|
+
selectableInputAriaLabel?: string;
|
|
43
|
+
/** Callback that executes when the selectable input is changed */
|
|
44
|
+
onSelectableInputChange?: (labelledBy: string, event: React.FormEvent<HTMLInputElement>) => void;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
interface CardContextProps {
|
|
42
48
|
cardId: string;
|
|
49
|
+
registerTitleId: (id: string) => void;
|
|
43
50
|
isExpanded: boolean;
|
|
44
51
|
}
|
|
45
52
|
|
|
53
|
+
interface AriaProps {
|
|
54
|
+
'aria-label'?: string;
|
|
55
|
+
'aria-labelledby'?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
export const CardContext = React.createContext<Partial<CardContextProps>>({
|
|
47
59
|
cardId: '',
|
|
60
|
+
registerTitleId: () => {},
|
|
48
61
|
isExpanded: false
|
|
49
62
|
});
|
|
50
63
|
|
|
@@ -67,10 +80,16 @@ export const Card: React.FunctionComponent<CardProps> = ({
|
|
|
67
80
|
isPlain = false,
|
|
68
81
|
ouiaId,
|
|
69
82
|
ouiaSafe = true,
|
|
83
|
+
hasSelectableInput = false,
|
|
84
|
+
selectableInputAriaLabel,
|
|
85
|
+
onSelectableInputChange = () => {},
|
|
70
86
|
...props
|
|
71
87
|
}: CardProps) => {
|
|
72
88
|
const Component = component as any;
|
|
73
89
|
const ouiaProps = useOUIAProps(Card.displayName, ouiaId, ouiaSafe);
|
|
90
|
+
const [titleId, setTitleId] = React.useState('');
|
|
91
|
+
const [ariaProps, setAriaProps] = React.useState<AriaProps>();
|
|
92
|
+
|
|
74
93
|
if (isCompact && isLarge) {
|
|
75
94
|
// eslint-disable-next-line no-console
|
|
76
95
|
console.warn('Card: Cannot use isCompact with isLarge. Defaulting to isCompact');
|
|
@@ -90,13 +109,47 @@ export const Card: React.FunctionComponent<CardProps> = ({
|
|
|
90
109
|
return '';
|
|
91
110
|
};
|
|
92
111
|
|
|
112
|
+
const containsCardTitleChildRef = React.useRef(false);
|
|
113
|
+
|
|
114
|
+
const registerTitleId = (id: string) => {
|
|
115
|
+
setTitleId(id);
|
|
116
|
+
containsCardTitleChildRef.current = !!id;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
if (selectableInputAriaLabel) {
|
|
121
|
+
setAriaProps({ 'aria-label': selectableInputAriaLabel });
|
|
122
|
+
} else if (titleId) {
|
|
123
|
+
setAriaProps({ 'aria-labelledby': titleId });
|
|
124
|
+
} else if (hasSelectableInput && !containsCardTitleChildRef.current) {
|
|
125
|
+
setAriaProps({});
|
|
126
|
+
// eslint-disable-next-line no-console
|
|
127
|
+
console.warn(
|
|
128
|
+
'If no CardTitle component is passed as a child of Card the selectableInputAriaLabel prop must be passed'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}, [hasSelectableInput, selectableInputAriaLabel, titleId]);
|
|
132
|
+
|
|
93
133
|
return (
|
|
94
134
|
<CardContext.Provider
|
|
95
135
|
value={{
|
|
96
136
|
cardId: id,
|
|
137
|
+
registerTitleId,
|
|
97
138
|
isExpanded
|
|
98
139
|
}}
|
|
99
140
|
>
|
|
141
|
+
{hasSelectableInput && (
|
|
142
|
+
<input
|
|
143
|
+
className="pf-screen-reader"
|
|
144
|
+
id={`${id}-input`}
|
|
145
|
+
{...ariaProps}
|
|
146
|
+
type="checkbox"
|
|
147
|
+
checked={isSelected}
|
|
148
|
+
onChange={event => onSelectableInputChange(id, event)}
|
|
149
|
+
disabled={isDisabledRaised}
|
|
150
|
+
tabIndex={-1}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
100
153
|
<Component
|
|
101
154
|
id={id}
|
|
102
155
|
className={css(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { css } from '@patternfly/react-styles';
|
|
3
3
|
import styles from '@patternfly/react-styles/css/components/Card/card';
|
|
4
|
+
import { CardContext } from './Card';
|
|
4
5
|
|
|
5
6
|
export interface CardTitleProps extends React.HTMLProps<HTMLDivElement> {
|
|
6
7
|
/** Content rendered inside the CardTitle */
|
|
@@ -17,9 +18,18 @@ export const CardTitle: React.FunctionComponent<CardTitleProps> = ({
|
|
|
17
18
|
component = 'div',
|
|
18
19
|
...props
|
|
19
20
|
}: CardTitleProps) => {
|
|
21
|
+
const { cardId, registerTitleId } = React.useContext(CardContext);
|
|
20
22
|
const Component = component as any;
|
|
23
|
+
const titleId = cardId ? `${cardId}-title` : '';
|
|
24
|
+
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
registerTitleId(titleId);
|
|
27
|
+
|
|
28
|
+
return () => registerTitleId('');
|
|
29
|
+
}, [registerTitleId, titleId]);
|
|
30
|
+
|
|
21
31
|
return (
|
|
22
|
-
<Component className={css(styles.cardTitle, className)} {...props}>
|
|
32
|
+
<Component className={css(styles.cardTitle, className)} id={titleId || undefined} {...props}>
|
|
23
33
|
{children}
|
|
24
34
|
</Component>
|
|
25
35
|
);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
|
|
2
3
|
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
|
|
6
|
+
import { Card, CardContext } from '../Card';
|
|
4
7
|
|
|
5
8
|
describe('Card', () => {
|
|
6
9
|
test('renders with PatternFly Core styles', () => {
|
|
@@ -118,4 +121,90 @@ describe('Card', () => {
|
|
|
118
121
|
render(<Card isLarge isCompact />);
|
|
119
122
|
expect(consoleWarnMock).toHaveBeenCalled();
|
|
120
123
|
});
|
|
124
|
+
|
|
125
|
+
test('card renders with a hidden input to improve a11y when hasSelectableInput is passed', () => {
|
|
126
|
+
render(<Card isSelectable hasSelectableInput />);
|
|
127
|
+
|
|
128
|
+
const selectableInput = screen.getByRole('checkbox', { hidden: true });
|
|
129
|
+
|
|
130
|
+
expect(selectableInput).toBeInTheDocument();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('card does not render the hidden input when hasSelectableInput is not passed', () => {
|
|
134
|
+
render(<Card isSelectable />);
|
|
135
|
+
|
|
136
|
+
const selectableInput = screen.queryByRole('checkbox', { hidden: true });
|
|
137
|
+
|
|
138
|
+
expect(selectableInput).not.toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('card warns when hasSelectableInput is passed without selectableInputAriaLabel or a card title', () => {
|
|
142
|
+
const consoleWarnMock = jest.fn();
|
|
143
|
+
global.console = { warn: consoleWarnMock } as any;
|
|
144
|
+
|
|
145
|
+
render(<Card isSelectable hasSelectableInput />);
|
|
146
|
+
|
|
147
|
+
const selectableInput = screen.getByRole('checkbox', { hidden: true });
|
|
148
|
+
|
|
149
|
+
expect(consoleWarnMock).toBeCalled();
|
|
150
|
+
expect(selectableInput).toHaveAccessibleName('');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('card applies selectableInputAriaLabel to the hidden input', () => {
|
|
154
|
+
render(<Card isSelectable hasSelectableInput selectableInputAriaLabel="Input label test" />);
|
|
155
|
+
|
|
156
|
+
const selectableInput = screen.getByRole('checkbox', { hidden: true });
|
|
157
|
+
|
|
158
|
+
expect(selectableInput).toHaveAccessibleName('Input label test');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('card applies the supplied card title as the aria label of the hidden input', () => {
|
|
162
|
+
|
|
163
|
+
// this component is used to mock the CardTitle's title registry behavior to keep this a pure unit test
|
|
164
|
+
const MockCardTitle = ({ children }) => {
|
|
165
|
+
const { registerTitleId } = React.useContext(CardContext);
|
|
166
|
+
const id = 'card-title-id';
|
|
167
|
+
|
|
168
|
+
React.useEffect(() => {
|
|
169
|
+
registerTitleId(id);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return <div id={id}>{children}</div>;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
render(
|
|
176
|
+
<Card id="card" isSelectable hasSelectableInput>
|
|
177
|
+
<MockCardTitle>Card title from title component</MockCardTitle>
|
|
178
|
+
</Card>
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const selectableInput = screen.getByRole('checkbox', { hidden: true });
|
|
182
|
+
|
|
183
|
+
expect(selectableInput).toHaveAccessibleName('Card title from title component');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('card prioritizes selectableInputAriaLabel over card title labelling via card title', () => {
|
|
187
|
+
|
|
188
|
+
// this component is used to mock the CardTitle's title registry behavior to keep this a pure unit test
|
|
189
|
+
const MockCardTitle = ({ children }) => {
|
|
190
|
+
const { registerTitleId } = React.useContext(CardContext);
|
|
191
|
+
const id = 'card-title-id';
|
|
192
|
+
|
|
193
|
+
React.useEffect(() => {
|
|
194
|
+
registerTitleId(id);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return <div id={id}>{children}</div>;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
render(
|
|
201
|
+
<Card id="card" isSelectable hasSelectableInput selectableInputAriaLabel="Input label test">
|
|
202
|
+
<MockCardTitle>Card title from title component</MockCardTitle>
|
|
203
|
+
</Card>
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const selectableInput = screen.getByRole('checkbox', { hidden: true });
|
|
207
|
+
|
|
208
|
+
expect(selectableInput).toHaveAccessibleName('Input label test');
|
|
209
|
+
});
|
|
121
210
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { CardTitle } from '../CardTitle';
|
|
4
|
+
import { CardContext } from '../Card';
|
|
4
5
|
|
|
5
6
|
describe('CardTitle', () => {
|
|
6
7
|
test('renders with PatternFly Core styles', () => {
|
|
@@ -19,4 +20,16 @@ describe('CardTitle', () => {
|
|
|
19
20
|
render(<CardTitle data-testid={testId} />);
|
|
20
21
|
expect(screen.getByTestId(testId)).toBeInTheDocument();
|
|
21
22
|
});
|
|
23
|
+
|
|
24
|
+
test('calls the registerTitleId function provided by the CardContext with the generated title id', () => {
|
|
25
|
+
const mockRegisterTitleId = jest.fn();
|
|
26
|
+
|
|
27
|
+
render(
|
|
28
|
+
<CardContext.Provider value={{ cardId: 'card', registerTitleId: mockRegisterTitleId }}>
|
|
29
|
+
<CardTitle>text</CardTitle>
|
|
30
|
+
</CardContext.Provider>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(mockRegisterTitleId).toHaveBeenCalledWith('card-title');
|
|
34
|
+
});
|
|
22
35
|
});
|
|
@@ -71,6 +71,23 @@ import pfLogoSmall from './pf-logo-small.svg';
|
|
|
71
71
|
```ts file='./CardSelectable.tsx'
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### Selectable accessibility highlight
|
|
75
|
+
|
|
76
|
+
This example demonstrates how the `hasSelectableInput` and `onSelectableInputChange` props improve accessibility for selectable cards.
|
|
77
|
+
|
|
78
|
+
The first card sets `hasSelectableInput` to true, which renders a checkbox input that is only visible to, and navigable by, screen readers. This input communicates to assistive technology users that a card is selectable, and if so, it communicates the current selection state as well.
|
|
79
|
+
|
|
80
|
+
By default this input will have an aria-label that corresponds to the title given to the card if using the card title component. If you don't use the card title component in your selectable card, you must pass a custom aria-label for this input using the `selectableInputAriaLabel` prop.
|
|
81
|
+
|
|
82
|
+
The first card also (by passing an onchange callback to `onSelectableInputChange`) enables the selection/deselection of the associated card by checking/unchecking the checkbox input.
|
|
83
|
+
|
|
84
|
+
The second card does not set `hasSelectableInput` to true, so the input is not rendered. It does not communicate to screen reader users that it is selectable or if it is currently selected.
|
|
85
|
+
|
|
86
|
+
To best understand this example it is encouraged that you navigate both of these cards using a screen reader.
|
|
87
|
+
|
|
88
|
+
```ts file='./CardSelectableA11yHighlight.tsx'
|
|
89
|
+
```
|
|
90
|
+
|
|
74
91
|
### With heading element
|
|
75
92
|
|
|
76
93
|
```ts file='./CardWithHeadingElement.tsx'
|
|
@@ -31,6 +31,11 @@ export const CardLegacySelectable: React.FunctionComponent = () => {
|
|
|
31
31
|
setSelected(newSelected);
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
const onChange = (labelledById: string, _event: React.FormEvent<HTMLInputElement>) => {
|
|
35
|
+
const newSelected = labelledById === selected ? null : labelledById;
|
|
36
|
+
setSelected(newSelected);
|
|
37
|
+
};
|
|
38
|
+
|
|
34
39
|
const onToggle = (isOpen: boolean, event: any) => {
|
|
35
40
|
event.stopPropagation();
|
|
36
41
|
setIsKebabOpen(isOpen);
|
|
@@ -65,8 +70,10 @@ export const CardLegacySelectable: React.FunctionComponent = () => {
|
|
|
65
70
|
id="legacy-first-card"
|
|
66
71
|
onKeyDown={onKeyDown}
|
|
67
72
|
onClick={onClick}
|
|
73
|
+
onSelectableInputChange={onChange}
|
|
68
74
|
isSelectable
|
|
69
75
|
isSelected={selected === 'legacy-first-card'}
|
|
76
|
+
hasSelectableInput
|
|
70
77
|
>
|
|
71
78
|
<CardHeader>
|
|
72
79
|
<CardActions>
|
|
@@ -80,7 +87,7 @@ export const CardLegacySelectable: React.FunctionComponent = () => {
|
|
|
80
87
|
/>
|
|
81
88
|
</CardActions>
|
|
82
89
|
</CardHeader>
|
|
83
|
-
<CardTitle>First card</CardTitle>
|
|
90
|
+
<CardTitle>First legacy selectable card</CardTitle>
|
|
84
91
|
<CardBody>This is a selectable card. Click me to select me. Click again to deselect me.</CardBody>
|
|
85
92
|
</Card>
|
|
86
93
|
<br />
|
|
@@ -88,10 +95,12 @@ export const CardLegacySelectable: React.FunctionComponent = () => {
|
|
|
88
95
|
id="legacy-second-card"
|
|
89
96
|
onKeyDown={onKeyDown}
|
|
90
97
|
onClick={onClick}
|
|
98
|
+
onSelectableInputChange={onChange}
|
|
91
99
|
isSelectable
|
|
92
100
|
isSelected={selected === 'legacy-second-card'}
|
|
101
|
+
hasSelectableInput
|
|
93
102
|
>
|
|
94
|
-
<CardTitle>Second card</CardTitle>
|
|
103
|
+
<CardTitle>Second legacy selectable card</CardTitle>
|
|
95
104
|
<CardBody>This is a selectable card. Click me to select me. Click again to deselect me.</CardBody>
|
|
96
105
|
</Card>
|
|
97
106
|
</>
|
|
@@ -31,6 +31,11 @@ export const CardSelectable: React.FunctionComponent = () => {
|
|
|
31
31
|
setSelected(newSelected);
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
const onChange = (labelledById: string, _event: React.FormEvent<HTMLInputElement>) => {
|
|
35
|
+
const newSelected = labelledById === selected ? null : labelledById;
|
|
36
|
+
setSelected(newSelected);
|
|
37
|
+
};
|
|
38
|
+
|
|
34
39
|
const onToggle = (
|
|
35
40
|
isOpen: boolean,
|
|
36
41
|
event: MouseEvent | TouchEvent | KeyboardEvent | React.KeyboardEvent<any> | React.MouseEvent<HTMLButtonElement>
|
|
@@ -68,6 +73,8 @@ export const CardSelectable: React.FunctionComponent = () => {
|
|
|
68
73
|
id="selectable-first-card"
|
|
69
74
|
onKeyDown={onKeyDown}
|
|
70
75
|
onClick={onClick}
|
|
76
|
+
hasSelectableInput
|
|
77
|
+
onSelectableInputChange={onChange}
|
|
71
78
|
isSelectableRaised
|
|
72
79
|
isSelected={selected === 'selectable-first-card'}
|
|
73
80
|
>
|
|
@@ -91,6 +98,8 @@ export const CardSelectable: React.FunctionComponent = () => {
|
|
|
91
98
|
id="selectable-second-card"
|
|
92
99
|
onKeyDown={onKeyDown}
|
|
93
100
|
onClick={onClick}
|
|
101
|
+
hasSelectableInput
|
|
102
|
+
onSelectableInputChange={onChange}
|
|
94
103
|
isSelectableRaised
|
|
95
104
|
isSelected={selected === 'selectable-second-card'}
|
|
96
105
|
>
|
|
@@ -98,7 +107,7 @@ export const CardSelectable: React.FunctionComponent = () => {
|
|
|
98
107
|
<CardBody>This is a selectable card. Click me to select me. Click again to deselect me.</CardBody>
|
|
99
108
|
</Card>
|
|
100
109
|
<br />
|
|
101
|
-
<Card id="selectable-third-card" isSelectableRaised isDisabledRaised>
|
|
110
|
+
<Card id="selectable-third-card" isSelectableRaised isDisabledRaised hasSelectableInput>
|
|
102
111
|
<CardTitle>Third card</CardTitle>
|
|
103
112
|
<CardBody>This is a raised but disabled card.</CardBody>
|
|
104
113
|
</Card>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card, CardTitle, CardBody } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const CardSelectableA11yHighlight: React.FunctionComponent = () => {
|
|
5
|
+
const [selected, setSelected] = React.useState<string>('');
|
|
6
|
+
|
|
7
|
+
const onKeyDown = (event: React.KeyboardEvent) => {
|
|
8
|
+
if (event.target !== event.currentTarget) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if ([' ', 'Enter'].includes(event.key)) {
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
const newSelected = event.currentTarget.id === selected ? null : event.currentTarget.id;
|
|
14
|
+
setSelected(newSelected);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const onClick = (event: React.MouseEvent) => {
|
|
19
|
+
const newSelected = event.currentTarget.id === selected ? null : event.currentTarget.id;
|
|
20
|
+
setSelected(newSelected);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const onChange = (labelledById: string, _event: React.FormEvent<HTMLInputElement>) => {
|
|
24
|
+
const newSelected = labelledById === selected ? null : labelledById;
|
|
25
|
+
setSelected(newSelected);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<React.Fragment>
|
|
30
|
+
<Card
|
|
31
|
+
id="selectable-first-card"
|
|
32
|
+
onKeyDown={onKeyDown}
|
|
33
|
+
onClick={onClick}
|
|
34
|
+
hasSelectableInput
|
|
35
|
+
onSelectableInputChange={onChange}
|
|
36
|
+
isSelectableRaised
|
|
37
|
+
isSelected={selected === 'selectable-first-card'}
|
|
38
|
+
>
|
|
39
|
+
<CardTitle>Selectable card with proper accessibility considerations</CardTitle>
|
|
40
|
+
<CardBody>
|
|
41
|
+
When using a screen reader a checkbox will become navigable that indicates this card is selectable and
|
|
42
|
+
communicate if it is currently selected.
|
|
43
|
+
</CardBody>
|
|
44
|
+
</Card>
|
|
45
|
+
<br />
|
|
46
|
+
<Card
|
|
47
|
+
id="selectable-second-card"
|
|
48
|
+
onKeyDown={onKeyDown}
|
|
49
|
+
onClick={onClick}
|
|
50
|
+
isSelectableRaised
|
|
51
|
+
isSelected={selected === 'selectable-second-card'}
|
|
52
|
+
>
|
|
53
|
+
<CardTitle>Selectable card without proper accessibility considerations</CardTitle>
|
|
54
|
+
<CardBody>
|
|
55
|
+
When using a screen reader there are no indications that this card is selectable or if it is currently
|
|
56
|
+
selected.
|
|
57
|
+
</CardBody>
|
|
58
|
+
</Card>
|
|
59
|
+
</React.Fragment>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -10,6 +10,7 @@ import { TextInput } from '../TextInput';
|
|
|
10
10
|
import { InputGroup } from '../InputGroup';
|
|
11
11
|
import { KEY_CODES } from '../../helpers/constants';
|
|
12
12
|
import { FocusTrap } from '../../helpers';
|
|
13
|
+
import { ToggleMenuBaseProps } from '../../helpers/Popper/Popper';
|
|
13
14
|
import { Popper } from '../../helpers/Popper/Popper';
|
|
14
15
|
import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers';
|
|
15
16
|
|
|
@@ -17,7 +18,7 @@ import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers';
|
|
|
17
18
|
let currentId = 0;
|
|
18
19
|
const newId = currentId++;
|
|
19
20
|
|
|
20
|
-
export interface ContextSelectorProps extends OUIAProps {
|
|
21
|
+
export interface ContextSelectorProps extends ToggleMenuBaseProps, OUIAProps {
|
|
21
22
|
/** content rendered inside the Context Selector */
|
|
22
23
|
children?: React.ReactNode;
|
|
23
24
|
/** Classes applied to root element of Context Selector */
|
|
@@ -28,14 +29,6 @@ export interface ContextSelectorProps extends OUIAProps {
|
|
|
28
29
|
onToggle?: (event: any, value: boolean) => void;
|
|
29
30
|
/** Function callback called when user selects item */
|
|
30
31
|
onSelect?: (event: any, value: React.ReactNode) => void;
|
|
31
|
-
/** The container to append the menu to. Defaults to 'inline'.
|
|
32
|
-
* If your menu is being cut off you can append it to an element higher up the DOM tree.
|
|
33
|
-
* Some examples:
|
|
34
|
-
* menuAppendTo="parent"
|
|
35
|
-
* menuAppendTo={() => document.body}
|
|
36
|
-
* menuAppendTo={document.getElementById('target')}
|
|
37
|
-
*/
|
|
38
|
-
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
|
|
39
32
|
/** Flag indicating that the context selector should expand to full height */
|
|
40
33
|
isFullHeight?: boolean;
|
|
41
34
|
/** Labels the Context Selector for Screen Readers */
|
|
@@ -2,9 +2,10 @@ import * as React from 'react';
|
|
|
2
2
|
import styles from '@patternfly/react-styles/css/components/Dropdown/dropdown';
|
|
3
3
|
import { DropdownPosition, DropdownDirection, DropdownContext } from './dropdownConstants';
|
|
4
4
|
import { DropdownWithContext } from './DropdownWithContext';
|
|
5
|
+
import { ToggleMenuBaseProps } from '../../helpers/Popper/Popper';
|
|
5
6
|
import { OUIAProps, useOUIAId } from '../../helpers';
|
|
6
7
|
|
|
7
|
-
export interface DropdownProps extends React.HTMLProps<HTMLDivElement>, OUIAProps {
|
|
8
|
+
export interface DropdownProps extends ToggleMenuBaseProps, React.HTMLProps<HTMLDivElement>, OUIAProps {
|
|
8
9
|
/** Anything which can be rendered in a dropdown */
|
|
9
10
|
children?: React.ReactNode;
|
|
10
11
|
/** Classes applied to root element of dropdown */
|
|
@@ -31,14 +32,6 @@ export interface DropdownProps extends React.HTMLProps<HTMLDivElement>, OUIAProp
|
|
|
31
32
|
};
|
|
32
33
|
/** Display menu above or below dropdown toggle */
|
|
33
34
|
direction?: DropdownDirection | 'up' | 'down';
|
|
34
|
-
/** The container to append the menu to. Defaults to 'inline'.
|
|
35
|
-
* If your menu is being cut off you can append it to an element higher up the DOM tree.
|
|
36
|
-
* Some examples:
|
|
37
|
-
* menuAppendTo="parent"
|
|
38
|
-
* menuAppendTo={() => document.body}
|
|
39
|
-
* menuAppendTo={document.getElementById('target')}
|
|
40
|
-
*/
|
|
41
|
-
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
|
|
42
35
|
/** Flag to indicate if dropdown has groups */
|
|
43
36
|
isGrouped?: boolean;
|
|
44
37
|
/** Toggle for the dropdown, examples: <DropdownToggle> or <DropdownToggleCheckbox> */
|
|
@@ -61,7 +54,6 @@ export const Dropdown: React.FunctionComponent<DropdownProps> = ({
|
|
|
61
54
|
ouiaSafe,
|
|
62
55
|
alignments,
|
|
63
56
|
contextProps,
|
|
64
|
-
menuAppendTo = 'inline',
|
|
65
57
|
...props
|
|
66
58
|
}: DropdownProps) => (
|
|
67
59
|
<DropdownContext.Provider
|
|
@@ -87,7 +79,7 @@ export const Dropdown: React.FunctionComponent<DropdownProps> = ({
|
|
|
87
79
|
...contextProps
|
|
88
80
|
}}
|
|
89
81
|
>
|
|
90
|
-
<DropdownWithContext
|
|
82
|
+
<DropdownWithContext {...props} />
|
|
91
83
|
</DropdownContext.Provider>
|
|
92
84
|
);
|
|
93
85
|
Dropdown.displayName = 'Dropdown';
|
|
@@ -3,6 +3,7 @@ import styles from '@patternfly/react-styles/css/components/OptionsMenu/options-
|
|
|
3
3
|
import { DropdownContext } from '../Dropdown';
|
|
4
4
|
import { DropdownWithContext } from '../Dropdown/DropdownWithContext';
|
|
5
5
|
import { OUIAProps, useOUIAId } from '../../helpers';
|
|
6
|
+
import { ToggleMenuBaseProps } from '../../helpers/Popper/Popper';
|
|
6
7
|
|
|
7
8
|
export enum OptionsMenuPosition {
|
|
8
9
|
right = 'right',
|
|
@@ -14,7 +15,7 @@ export enum OptionsMenuDirection {
|
|
|
14
15
|
down = 'down'
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export interface OptionsMenuProps extends React.HTMLProps<HTMLDivElement>, OUIAProps {
|
|
18
|
+
export interface OptionsMenuProps extends ToggleMenuBaseProps, React.HTMLProps<HTMLDivElement>, OUIAProps {
|
|
18
19
|
/** Classes applied to root element of the options menu */
|
|
19
20
|
className?: string;
|
|
20
21
|
/** Id of the root element of the options menu */
|
|
@@ -35,14 +36,6 @@ export interface OptionsMenuProps extends React.HTMLProps<HTMLDivElement>, OUIAP
|
|
|
35
36
|
position?: 'right' | 'left';
|
|
36
37
|
/** Menu will open up or open down from the options menu toggle */
|
|
37
38
|
direction?: 'up' | 'down';
|
|
38
|
-
/** The container to append the menu to. Defaults to 'inline'.
|
|
39
|
-
* If your menu is being cut off you can append it to an element higher up the DOM tree.
|
|
40
|
-
* Some examples:
|
|
41
|
-
* menuAppendTo="parent"
|
|
42
|
-
* menuAppendTo={() => document.body}
|
|
43
|
-
* menuAppendTo={document.getElementById('target')}
|
|
44
|
-
*/
|
|
45
|
-
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
export const OptionsMenu: React.FunctionComponent<OptionsMenuProps> = ({
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from '../../helpers';
|
|
34
34
|
import { KeyTypes } from '../../helpers/constants';
|
|
35
35
|
import { Divider } from '../Divider';
|
|
36
|
-
import { Popper } from '../../helpers/Popper/Popper';
|
|
36
|
+
import { ToggleMenuBaseProps, Popper } from '../../helpers/Popper/Popper';
|
|
37
37
|
import { createRenderableFavorites, extendItemsWithFavorite } from '../../helpers/favorites';
|
|
38
38
|
import { ValidatedOptions } from '../../helpers/constants';
|
|
39
39
|
import { findTabbableElements } from '../../helpers/util';
|
|
@@ -48,7 +48,8 @@ export interface SelectViewMoreObject {
|
|
|
48
48
|
onClick: (event: React.MouseEvent | React.ChangeEvent) => void;
|
|
49
49
|
}
|
|
50
50
|
export interface SelectProps
|
|
51
|
-
extends
|
|
51
|
+
extends ToggleMenuBaseProps,
|
|
52
|
+
Omit<React.HTMLProps<HTMLDivElement>, 'onSelect' | 'ref' | 'checked' | 'selected'>,
|
|
52
53
|
OUIAProps {
|
|
53
54
|
/** Content rendered inside the Select. Must be React.ReactElement<SelectGroupProps>[] */
|
|
54
55
|
children?: React.ReactElement[];
|
|
@@ -164,14 +165,6 @@ export interface SelectProps
|
|
|
164
165
|
shouldResetOnSelect?: boolean;
|
|
165
166
|
/** Content rendered in the footer of the select menu */
|
|
166
167
|
footer?: React.ReactNode;
|
|
167
|
-
/** The container to append the menu to. Defaults to 'inline'.
|
|
168
|
-
* If your menu is being cut off you can append it to an element higher up the DOM tree.
|
|
169
|
-
* Some examples:
|
|
170
|
-
* menuAppendTo="parent"
|
|
171
|
-
* menuAppendTo={() => document.body}
|
|
172
|
-
* menuAppendTo={document.getElementById('target')}
|
|
173
|
-
*/
|
|
174
|
-
menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
|
|
175
168
|
}
|
|
176
169
|
|
|
177
170
|
export interface SelectState {
|