Skip to content

Commit 175512d

Browse files
committed
feat(AnimationsProvider): update opt-in components with useHasAnimations, add AnimationsProvider to helpers, add md files for documentation
fix(components): update hasAnimations to fall back to context fix(md): remove cssPrefix
1 parent 26e9f0e commit 175512d

File tree

17 files changed

+583
-111
lines changed

17 files changed

+583
-111
lines changed
Lines changed: 55 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Component } from 'react';
1+
import { useEffect, useRef } from 'react';
22
import * as ReactDOM from 'react-dom';
33
import { canUseDOM } from '../../helpers';
44
import { AlertGroupInline } from './AlertGroupInline';
5+
import { useHasAnimations } from '../../helpers';
56

67
export interface AlertGroupProps extends Omit<React.HTMLProps<HTMLUListElement>, 'className'> {
78
/** Additional classes added to the AlertGroup */
@@ -26,78 +27,69 @@ export interface AlertGroupProps extends Omit<React.HTMLProps<HTMLUListElement>,
2627
'aria-label'?: string;
2728
}
2829

29-
interface AlertGroupState {
30-
container: HTMLElement;
31-
}
32-
33-
class AlertGroup extends Component<AlertGroupProps, AlertGroupState> {
34-
static displayName = 'AlertGroup';
35-
state = {
36-
container: undefined
37-
} as AlertGroupState;
38-
39-
componentDidMount() {
40-
const container = document.createElement('div');
41-
const target: HTMLElement = this.getTargetElement();
42-
this.setState({ container });
43-
target.appendChild(container);
44-
}
30+
export const AlertGroup: React.FunctionComponent<AlertGroupProps> = ({
31+
className,
32+
children,
33+
hasAnimations: localHasAnimations,
34+
isToast,
35+
isLiveRegion,
36+
onOverflowClick,
37+
overflowMessage,
38+
'aria-label': ariaLabel,
4539

46-
componentWillUnmount() {
47-
const target: HTMLElement = this.getTargetElement();
48-
if (this.state.container) {
49-
target.removeChild(this.state.container);
50-
}
51-
}
40+
appendTo, // do not pass down to ul
41+
...props
42+
}: AlertGroupProps) => {
43+
const containerRef = useRef<HTMLElement | null>(null);
44+
const hasAnimations = useHasAnimations(localHasAnimations);
5245

53-
getTargetElement() {
54-
const appendTo = this.props.appendTo;
46+
const getTargetElement = () => {
5547
if (typeof appendTo === 'function') {
5648
return appendTo();
5749
}
5850
return appendTo || document.body;
59-
}
51+
};
52+
53+
useEffect(() => {
54+
if (isToast) {
55+
const container = document.createElement('div');
56+
const target = getTargetElement();
57+
containerRef.current = container;
58+
target.appendChild(container);
6059

61-
render() {
62-
const {
63-
className,
64-
children,
65-
hasAnimations = false,
66-
isToast,
67-
isLiveRegion,
68-
onOverflowClick,
69-
overflowMessage,
70-
'aria-label': ariaLabel,
71-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
72-
appendTo, // do not pass down to ul
73-
...props
74-
} = this.props;
75-
const alertGroup = (
76-
<AlertGroupInline
77-
onOverflowClick={onOverflowClick}
78-
className={className}
79-
isToast={isToast}
80-
isLiveRegion={isLiveRegion}
81-
overflowMessage={overflowMessage}
82-
aria-label={ariaLabel}
83-
hasAnimations={hasAnimations}
84-
{...props}
85-
>
86-
{children}
87-
</AlertGroupInline>
88-
);
89-
if (!this.props.isToast) {
90-
return alertGroup;
60+
return () => {
61+
if (containerRef.current) {
62+
target.removeChild(containerRef.current);
63+
}
64+
};
9165
}
66+
}, [isToast, appendTo]);
9267

93-
const container = this.state.container;
68+
const alertGroup = (
69+
<AlertGroupInline
70+
onOverflowClick={onOverflowClick}
71+
className={className}
72+
isToast={isToast}
73+
isLiveRegion={isLiveRegion}
74+
overflowMessage={overflowMessage}
75+
aria-label={ariaLabel}
76+
hasAnimations={hasAnimations}
77+
{...props}
78+
>
79+
{children}
80+
</AlertGroupInline>
81+
);
9482

95-
if (!canUseDOM || !container) {
96-
return null;
97-
}
83+
if (!isToast) {
84+
return alertGroup;
85+
}
86+
87+
const container = containerRef.current;
9888

99-
return ReactDOM.createPortal(alertGroup, container);
89+
if (!canUseDOM || !container) {
90+
return null;
10091
}
101-
}
10292

103-
export { AlertGroup };
93+
return ReactDOM.createPortal(alertGroup, container);
94+
};
95+
AlertGroup.displayName = 'AlertGroup';

packages/react-core/src/components/Alert/AlertGroupInline.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ import styles from '@patternfly/react-styles/css/components/Alert/alert-group';
44
import { AlertGroupProps } from './AlertGroup';
55
import { AlertProps } from '../Alert';
66
import { AlertGroupContext } from './AlertGroupContext';
7+
import { useHasAnimations } from '../../helpers';
78

89
export const AlertGroupInline: React.FunctionComponent<AlertGroupProps> = ({
910
className,
1011
children,
11-
hasAnimations,
12+
hasAnimations: localHasAnimations,
1213
isToast,
1314
isLiveRegion,
1415
onOverflowClick,
1516
overflowMessage,
1617
...props
1718
}: AlertGroupProps) => {
19+
const hasAnimations = useHasAnimations(localHasAnimations);
1820
const [handleTransitionEnd, setHandleTransitionEnd] = useState<() => void>(() => () => {});
1921

2022
const updateTransitionEnd = (onTransitionEnd: () => void) => {
Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Component } from 'react';
21
import styles from '@patternfly/react-styles/css/components/DualListSelector/dual-list-selector';
32
import { css } from '@patternfly/react-styles';
4-
import { GenerateId, PickOptional } from '../../helpers';
3+
import { GenerateId } from '../../helpers';
54
import { DualListSelectorContext } from './DualListSelectorContext';
5+
import { useHasAnimations } from '../../helpers';
66

77
/** Acts as a container for all other DualListSelector sub-components when using a
88
* composable dual list selector.
@@ -24,41 +24,34 @@ export interface DualListSelectorProps {
2424
hasAnimations?: boolean;
2525
}
2626

27-
class DualListSelector extends Component<DualListSelectorProps> {
28-
static displayName = 'DualListSelector';
29-
static defaultProps: PickOptional<DualListSelectorProps> = {
30-
children: '',
31-
isTree: false,
32-
hasAnimations: false
33-
};
27+
export const DualListSelector: React.FunctionComponent<DualListSelectorProps> = ({
28+
className,
29+
children,
30+
id,
31+
isTree = false,
32+
hasAnimations: localHasAnimations,
33+
...props
34+
}: DualListSelectorProps) => {
35+
const hasAnimations = useHasAnimations(localHasAnimations);
3436

35-
constructor(props: DualListSelectorProps) {
36-
super(props);
37-
}
38-
39-
render() {
40-
const { className, children, id, isTree, hasAnimations, ...props } = this.props;
41-
42-
return (
43-
<DualListSelectorContext.Provider value={{ isTree, hasAnimations }}>
44-
<GenerateId>
45-
{(randomId) => (
46-
<div
47-
className={css(
48-
styles.dualListSelector,
49-
hasAnimations && isTree && styles.modifiers.animateExpand,
50-
className
51-
)}
52-
id={id || randomId}
53-
{...props}
54-
>
55-
{children}
56-
</div>
57-
)}
58-
</GenerateId>
59-
</DualListSelectorContext.Provider>
60-
);
61-
}
62-
}
63-
64-
export { DualListSelector };
37+
return (
38+
<DualListSelectorContext.Provider value={{ isTree, hasAnimations }}>
39+
<GenerateId>
40+
{(randomId) => (
41+
<div
42+
className={css(
43+
styles.dualListSelector,
44+
hasAnimations && isTree && styles.modifiers.animateExpand,
45+
className
46+
)}
47+
id={id || randomId}
48+
{...props}
49+
>
50+
{children}
51+
</div>
52+
)}
53+
</GenerateId>
54+
</DualListSelectorContext.Provider>
55+
);
56+
};
57+
DualListSelector.displayName = 'DualListSelector';

packages/react-core/src/components/DualListSelector/DualListSelectorTreeItem.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Badge } from '../Badge';
66
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
77
import { flattenTree } from './treeUtils';
88
import { DualListSelectorListContext } from './DualListSelectorContext';
9+
import { useHasAnimations } from '../../helpers';
910

1011
export interface DualListSelectorTreeItemProps extends React.HTMLProps<HTMLLIElement> {
1112
/** Content rendered inside the dual list selector. */
@@ -58,14 +59,15 @@ const DualListSelectorTreeItemBase: React.FunctionComponent<DualListSelectorTree
5859
badgeProps,
5960
itemData,
6061
isDisabled = false,
61-
hasAnimations,
62+
hasAnimations: localHasAnimations,
6263
// eslint-disable-next-line @typescript-eslint/no-unused-vars
6364
useMemo,
6465
...props
6566
}: DualListSelectorTreeItemProps) => {
6667
const ref = useRef(null);
6768
const [isExpanded, setIsExpanded] = useState(defaultExpanded || false);
6869
const { setFocusedOption } = useContext(DualListSelectorListContext);
70+
const hasAnimations = useHasAnimations(localHasAnimations);
6971

7072
useEffect(() => {
7173
setIsExpanded(defaultExpanded);

packages/react-core/src/components/Form/FormFieldGroupExpandable.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react';
22
import { InternalFormFieldGroup } from './InternalFormFieldGroup';
3+
import { useHasAnimations } from '../../helpers';
34

45
export interface FormFieldGroupExpandableProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onToggle'> {
56
/** Anything that can be rendered as form field group content. */
@@ -25,10 +26,11 @@ export const FormFieldGroupExpandable: React.FunctionComponent<FormFieldGroupExp
2526
header,
2627
isExpanded = false,
2728
toggleAriaLabel,
28-
hasAnimations,
29+
hasAnimations: localHasAnimations,
2930
...props
3031
}: FormFieldGroupExpandableProps) => {
3132
const [localIsExpanded, setIsExpanded] = useState(isExpanded);
33+
const hasAnimations = useHasAnimations(localHasAnimations);
3234

3335
return (
3436
<InternalFormFieldGroup

packages/react-core/src/components/Form/InternalFormFieldGroup.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import styles from '@patternfly/react-styles/css/components/Form/form';
22
import { css } from '@patternfly/react-styles';
33
import { FormFieldGroupToggle } from './FormFieldGroupToggle';
44
import { GenerateId } from '../../helpers';
5+
import { useHasAnimations } from '../../helpers';
56

67
export interface InternalFormFieldGroupProps extends Omit<React.HTMLProps<HTMLDivElement>, 'label' | 'onToggle'> {
78
/** Anything that can be rendered as form field group content. */
@@ -33,9 +34,10 @@ export const InternalFormFieldGroup: React.FunctionComponent<InternalFormFieldGr
3334
isExpanded,
3435
onToggle,
3536
toggleAriaLabel,
36-
hasAnimations,
37+
hasAnimations: localHasAnimations,
3738
...props
3839
}: InternalFormFieldGroupProps) => {
40+
const hasAnimations = useHasAnimations(localHasAnimations);
3941
const headerTitleText = header ? header.props.titleText : null;
4042
if (isExpandable && !toggleAriaLabel && !headerTitleText) {
4143
// eslint-disable-next-line no-console

packages/react-core/src/components/SearchInput/SearchInput.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { AdvancedSearchMenu } from './AdvancedSearchMenu';
1313
import { TextInputGroup, TextInputGroupMain, TextInputGroupUtilities } from '../TextInputGroup';
1414
import { InputGroup, InputGroupItem } from '../InputGroup';
1515
import { Popper } from '../../helpers';
16+
import { useHasAnimations } from '../../helpers';
1617
import textInputGroupStyles from '@patternfly/react-styles/css/components/TextInputGroup/text-input-group';
1718
import inputGroupStyles from '@patternfly/react-styles/css/components/InputGroup/input-group';
1819

@@ -180,7 +181,8 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
180181
const popperRef = useRef(null);
181182
const [focusAfterExpandChange, setFocusAfterExpandChange] = useState(false);
182183

183-
const { isExpanded, onToggleExpand, toggleAriaLabel, hasAnimations } = expandableInput || {};
184+
const { isExpanded, onToggleExpand, toggleAriaLabel, hasAnimations: localHasAnimations } = expandableInput || {};
185+
const hasAnimations = useHasAnimations(localHasAnimations);
184186

185187
useEffect(() => {
186188
// this effect and the focusAfterExpandChange variable are needed to focus the input/toggle as needed when the

packages/react-core/src/components/TreeView/TreeView.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { TreeViewList } from './TreeViewList';
22
import { TreeViewCheckProps, TreeViewListItem } from './TreeViewListItem';
33
import { TreeViewRoot } from './TreeViewRoot';
4+
import { useHasAnimations } from '../../helpers';
45

56
/** Properties that make up a tree view data item. These properties should be passed in as an
67
* object to one of the various tree view component properties which accept TreeViewDataItem as
@@ -135,9 +136,10 @@ export const TreeView: React.FunctionComponent<TreeViewProps> = ({
135136
useMemo,
136137
'aria-label': ariaLabel,
137138
'aria-labelledby': ariaLabelledby,
138-
hasAnimations,
139+
hasAnimations: localHasAnimations,
139140
...props
140141
}: TreeViewProps) => {
142+
const hasAnimations = useHasAnimations(localHasAnimations);
141143
const treeViewList = (
142144
<TreeViewList
143145
isNested={isNested}

packages/react-core/src/components/TreeView/TreeViewListItem.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-i
55
import { TreeViewDataItem } from './TreeView';
66
import { Badge } from '../Badge';
77
import { GenerateId } from '../../helpers/GenerateId/GenerateId';
8+
import { useHasAnimations } from '../../helpers';
89

910
export interface TreeViewCheckProps extends Omit<Partial<React.InputHTMLAttributes<HTMLInputElement>>, 'checked'> {
1011
checked?: boolean | null;
@@ -102,10 +103,11 @@ const TreeViewListItemBase: React.FunctionComponent<TreeViewListItemProps> = ({
102103
expandedIcon,
103104
action,
104105
compareItems,
105-
hasAnimations,
106+
hasAnimations: localHasAnimations,
106107
// eslint-disable-next-line @typescript-eslint/no-unused-vars
107108
useMemo
108109
}: TreeViewListItemProps) => {
110+
const hasAnimations = useHasAnimations(localHasAnimations);
109111
const [internalIsExpanded, setIsExpanded] = useState(defaultExpanded);
110112
useEffect(() => {
111113
if (isExpanded !== undefined && isExpanded !== null) {

packages/react-core/src/components/TreeView/__tests__/__snapshots__/TreeView.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ exports[`Matches snapshot 1`] = `
105105
TreeViewListItem useMemo: undefined
106106
</p>
107107
<p>
108-
TreeViewListItem hasAnimations: undefined
108+
TreeViewListItem hasAnimations: false
109109
</p>
110110
<button>
111111
compareItems clicker

0 commit comments

Comments
 (0)