,
- iconWhenOpen:
,
- },
- // card and card-lg use the defaults specified in defaultProps
-};
-
-const Collapsible = React.forwardRef((props, ref) => {
- const {
- children,
- className,
- title,
- styling,
- iconWhenClosed,
- iconWhenOpen,
- ...other
- } = props;
-
- const icons = { iconWhenClosed, iconWhenOpen, ...styleIcons[styling] };
- const titleElement = React.isValidElement(title) ? title :
{title};
-
- return (
-
-
- {titleElement}
-
- {icons.iconWhenClosed}
- {icons.iconWhenOpen}
-
-
-
- {children}
-
- );
-});
-
-Collapsible.propTypes = {
- /** Specifies contents of the component. */
- children: PropTypes.node.isRequired,
- /** Specifies class name to append to the base element. */
- className: PropTypes.string,
- /** Specifies whether the `Collapsible` should be initially open. */
- defaultOpen: PropTypes.bool,
- /** Specifies icon to show when `Collapsible` is closed. */
- iconWhenClosed: PropTypes.element,
- /** Specifies icon to show when `Collapsible` is open. */
- iconWhenOpen: PropTypes.element,
- /** Callback fired when `Collapsible` closes. */
- onClose: PropTypes.func,
- /** Callback fired when `Collapsible` opens. */
- onOpen: PropTypes.func,
- /** Callback fired when `Collapsible's` state is toggled. */
- onToggle: PropTypes.func,
- /** Specifies whether `Collapsible` is open. */
- open: PropTypes.bool,
- /** Specifies style variant. */
- styling: PropTypes.oneOf(['basic', 'card', 'card-lg']),
- /** Specifies title. */
- title: PropTypes.node.isRequired,
- /** Unmount the component (remove it from the DOM) when it is collapsed */
- unmountOnExit: PropTypes.bool,
-};
-
-Collapsible.defaultProps = {
- className: undefined,
- defaultOpen: false,
- iconWhenClosed:
,
- iconWhenOpen:
,
- onClose: undefined,
- onOpen: undefined,
- onToggle: undefined,
- open: undefined,
- styling: 'card',
- unmountOnExit: true,
-};
-
-Collapsible.Advanced = CollapsibleAdvanced;
-Collapsible.Body = CollapsibleBody;
-Collapsible.Trigger = CollapsibleTrigger;
-Collapsible.Visible = CollapsibleVisible;
-Collapsible.Context = CollapsibleContext;
-
-export default Collapsible;
diff --git a/src/Collapsible/index.tsx b/src/Collapsible/index.tsx
new file mode 100644
index 0000000000..b2923d4ee3
--- /dev/null
+++ b/src/Collapsible/index.tsx
@@ -0,0 +1,88 @@
+import React from 'react';
+import classNames from 'classnames';
+
+import { ExpandLess, ExpandMore } from '../../icons';
+import CollapsibleAdvanced, { CollapsibleContext } from './CollapsibleAdvanced';
+import CollapsibleBody from './CollapsibleBody';
+import CollapsibleTrigger from './CollapsibleTrigger';
+import CollapsibleVisible from './CollapsibleVisible';
+import Icon from '../Icon';
+
+interface StyleIcons {
+ iconWhenClosed: React.ReactElement;
+ iconWhenOpen: React.ReactElement;
+}
+
+const styleIcons: Record
= {
+ basic: {
+ iconWhenClosed: ,
+ iconWhenOpen: ,
+ },
+};
+
+export interface CollapsibleProps extends Omit, 'title'> {
+ children: React.ReactNode;
+ className?: string;
+ defaultOpen?: boolean;
+ iconWhenClosed?: React.ReactElement;
+ iconWhenOpen?: React.ReactElement;
+ onClose?: () => void;
+ onOpen?: () => void;
+ onToggle?: (isOpen: boolean) => void;
+ open?: boolean;
+ styling?: 'basic' | 'card' | 'card-lg';
+ title: React.ReactNode;
+ unmountOnExit?: boolean;
+}
+
+const Collapsible = React.forwardRef((props, ref) => {
+ const {
+ children,
+ className,
+ title,
+ styling = 'card',
+ iconWhenClosed = ,
+ iconWhenOpen = ,
+ ...other
+ } = props;
+
+ const icons = { ...(styleIcons[styling] || {}), iconWhenClosed, iconWhenOpen };
+ const titleElement = React.isValidElement(title) ? title : {title};
+
+ return (
+
+
+ {titleElement}
+
+ {icons.iconWhenClosed}
+ {icons.iconWhenOpen}
+
+
+
+ {children}
+
+ );
+}) as CollapsibleComponent;
+
+interface CollapsibleComponent extends React.ForwardRefExoticComponent<
+CollapsibleProps & React.RefAttributes
+> {
+ Advanced: typeof CollapsibleAdvanced;
+ Body: typeof CollapsibleBody;
+ Trigger: typeof CollapsibleTrigger;
+ Visible: typeof CollapsibleVisible;
+ Context: typeof CollapsibleContext;
+}
+
+Collapsible.Advanced = CollapsibleAdvanced;
+Collapsible.Body = CollapsibleBody;
+Collapsible.Trigger = CollapsibleTrigger;
+Collapsible.Visible = CollapsibleVisible;
+Collapsible.Context = CollapsibleContext;
+
+export default Collapsible;
diff --git a/src/TransitionReplace/TransitionReplace.test.jsx b/src/TransitionReplace/TransitionReplace.test.tsx
similarity index 82%
rename from src/TransitionReplace/TransitionReplace.test.jsx
rename to src/TransitionReplace/TransitionReplace.test.tsx
index f66df4fca4..7a5fede220 100644
--- a/src/TransitionReplace/TransitionReplace.test.jsx
+++ b/src/TransitionReplace/TransitionReplace.test.tsx
@@ -1,10 +1,12 @@
-import React from 'react';
import { render } from '@testing-library/react';
-import TransitionReplace from '.';
+import TransitionReplace, { TransitionReplaceProps } from '.';
-/* eslint-disable-next-line react/prop-types */
-function TestReplacement({ showContentA, ...props }) {
+interface TestReplacementProps extends Omit {
+ showContentA: boolean;
+}
+
+function TestReplacement({ showContentA, ...props }: TestReplacementProps) {
return (
{showContentA ? (
@@ -19,17 +21,17 @@ function TestReplacement({ showContentA, ...props }) {
describe('TransitionReplace', () => {
it('should add entering class names for each part of the transition', (done) => {
let count = 0;
- const onChildEnter = (node) => {
+ const onChildEnter = (node: HTMLElement) => {
count++;
expect(count).toEqual(1);
expect(node.classList.contains('test-enter')).toEqual(true);
};
- const onChildEntering = (node) => {
+ const onChildEntering = (node: HTMLElement) => {
count++;
expect(count).toEqual(2);
expect(node.classList.contains('test-enter-active')).toEqual(true);
};
- const onChildEntered = (node) => {
+ const onChildEntered = (node: HTMLElement) => {
count++;
expect(count).toEqual(3);
expect(node.classList.contains('test-enter-done')).toEqual(true);
@@ -63,17 +65,17 @@ describe('TransitionReplace', () => {
it('should add exiting class names for each part of the transition', (done) => {
let count = 0;
- const onChildExit = (node) => {
+ const onChildExit = (node: HTMLElement) => {
count++;
expect(count).toEqual(1);
expect(node.classList.contains('test-exit')).toEqual(true);
};
- const onChildExiting = (node) => {
+ const onChildExiting = (node: HTMLElement) => {
count++;
expect(count).toEqual(2);
expect(node.classList.contains('test-exit-active')).toEqual(true);
};
- const onChildExited = (node) => {
+ const onChildExited = (node: HTMLElement) => {
count++;
expect(count).toEqual(3);
expect(node.classList.contains('test-exit-done')).toEqual(true);
diff --git a/src/TransitionReplace/index.jsx b/src/TransitionReplace/index.jsx
deleted file mode 100644
index f39bcd48e0..0000000000
--- a/src/TransitionReplace/index.jsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { CSSTransition, TransitionGroup } from 'react-transition-group';
-import classNames from 'classnames';
-
-class TransitionReplace extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = { height: null };
-
- this.onChildEnter = this.onChildEnter.bind(this);
- this.onChildEntering = this.onChildEntering.bind(this);
- this.onChildEntered = this.onChildEntered.bind(this);
- this.onChildExit = this.onChildExit.bind(this);
- this.onChildExiting = this.onChildExiting.bind(this);
- this.onChildExited = this.onChildExited.bind(this);
- }
-
- // Transition events are fired in this order:
- //
- // onEnter > onEntering > onEntered
- // onExit > onExiting > onExited
- //
- // Keep in mind that we always have two transitions happening
- // both the entering and leaving children
- //
- // We set the container height (for animation) in this order:
- //
- // 1. onChildExit (explicitly set the height to match the current current)
- // 2. onChildEntering (set the height to match the new content)
- // 3. onChildExited (reset the height to null)
-
- onChildEnter(htmlNode) {
- if (this.props.onChildEnter) { this.props.onChildEnter(htmlNode); }
- }
-
- onChildEntering(htmlNode) {
- this.setState({ height: htmlNode.offsetHeight });
- if (this.props.onChildEntering) { this.props.onChildEntering(htmlNode); }
- }
-
- onChildEntered(htmlNode) {
- this.setState({ height: null });
- if (this.props.onChildEntered) { this.props.onChildEntered(htmlNode); }
- }
-
- onChildExit(htmlNode) {
- this.setState({ height: htmlNode.offsetHeight });
- if (this.props.onChildExit) { this.props.onChildExit(htmlNode); }
- }
-
- onChildExiting(htmlNode) {
- if (this.props.onChildExiting) { this.props.onChildExiting(htmlNode); }
- }
-
- onChildExited(htmlNode) {
- this.setState({ height: null });
- if (this.props.onChildExited) { this.props.onChildExited(htmlNode); }
- }
-
- renderChildTransition(child) {
- if (!child.key && process.env.NODE_ENV === 'development') {
- // eslint-disable-next-line no-console
- console.warn("TransitionReplace: A child is missing a 'key' prop. Keys are required for any child of this component.");
- }
- // Makes the exiting and entering children occupy the same space
- // SCSS handles the crossfade so it can be easily overridden
- const commonChildStyles = {
- // Prevent margin-collapsing which throws off height calculations
- padding: '.1px 0',
- };
-
- const transitionStyles = {
- entering: {},
- entered: {},
- exiting: {
- position: 'absolute',
- top: 0,
- left: 0,
- right: 0,
- pointerEvents: 'none',
- },
- exited: {},
- };
-
- return (
-
- {state => (
-
- {child}
-
- )}
-
- );
- }
-
- render() {
- return (
-
- {React.Children.map(this.props.children, this.renderChildTransition, this)}
-
- );
- }
-}
-
-TransitionReplace.propTypes = {
- /** Specifies an additional class for the base element */
- children: PropTypes.element,
- /** Duration of the element appearance transition. */
- enterDuration: PropTypes.number,
- /** Duration of the element dismiss transition. */
- exitDuration: PropTypes.number,
- /** Specifies class name to append to the base element. */
- className: PropTypes.string,
- /** A `Transition` callback fired immediately after the `enter` or `appear` class is applied. */
- onChildEnter: PropTypes.func,
- /** A `Transition` callback fired immediately after the `enter-active` or `appear-active` class is applied. */
- onChildEntering: PropTypes.func,
- /**
- * A `Transition` callback fired immediately after the `enter` or
- * `appear` classes are removed and the done class is added to the DOM node.
- */
- onChildEntered: PropTypes.func,
- /** A `Transition` callback fired immediately after the `exit` class is applied. */
- onChildExit: PropTypes.func,
- /** A `Transition` callback fired immediately after the `exit-active` is applied. */
- onChildExiting: PropTypes.func,
- /**
- * A `Transition` callback fired immediately after the `exit` classes
- * are removed and the exit-done class is added to the DOM node.
- */
- onChildExited: PropTypes.func,
- /** An object that specifies transition styles. */
- transitionStyles: PropTypes.shape({
- entering: PropTypes.shape({}),
- entered: PropTypes.shape({}),
- exiting: PropTypes.shape({}),
- exited: PropTypes.shape({}),
- }),
- /** Specifies class name to append to the `Transition`. */
- transitionClassNames: PropTypes.string,
-};
-
-TransitionReplace.defaultProps = {
- children: undefined,
- enterDuration: 300,
- exitDuration: 300,
- className: undefined,
- onChildEnter: undefined,
- onChildEntering: undefined,
- onChildEntered: undefined,
- onChildExit: undefined,
- onChildExiting: undefined,
- onChildExited: undefined,
- transitionStyles: {},
- transitionClassNames: 'pgn__transition-replace',
-};
-
-export default TransitionReplace;
diff --git a/src/TransitionReplace/index.tsx b/src/TransitionReplace/index.tsx
new file mode 100644
index 0000000000..2c92b56c14
--- /dev/null
+++ b/src/TransitionReplace/index.tsx
@@ -0,0 +1,144 @@
+import React from 'react';
+import { CSSTransition, TransitionGroup } from 'react-transition-group';
+import classNames from 'classnames';
+
+interface TransitionStyles {
+ entering?: React.CSSProperties;
+ entered?: React.CSSProperties;
+ exiting?: React.CSSProperties;
+ exited?: React.CSSProperties;
+}
+
+export interface TransitionReplaceProps {
+ children?: React.ReactElement;
+ enterDuration?: number;
+ exitDuration?: number;
+ className?: string;
+ onChildEnter?: (node: HTMLElement) => void;
+ onChildEntering?: (node: HTMLElement) => void;
+ onChildEntered?: (node: HTMLElement) => void;
+ onChildExit?: (node: HTMLElement) => void;
+ onChildExiting?: (node: HTMLElement) => void;
+ onChildExited?: (node: HTMLElement) => void;
+ transitionStyles?: TransitionStyles;
+ transitionClassNames?: string;
+}
+
+interface TransitionReplaceState {
+ height: number | null;
+}
+
+class TransitionReplace extends React.Component {
+ constructor(props: TransitionReplaceProps) {
+ super(props);
+
+ this.state = { height: null };
+
+ this.onChildEnter = this.onChildEnter.bind(this);
+ this.onChildEntering = this.onChildEntering.bind(this);
+ this.onChildEntered = this.onChildEntered.bind(this);
+ this.onChildExit = this.onChildExit.bind(this);
+ this.onChildExiting = this.onChildExiting.bind(this);
+ this.onChildExited = this.onChildExited.bind(this);
+ }
+
+ onChildEnter(htmlNode: HTMLElement) {
+ if (this.props.onChildEnter) { this.props.onChildEnter(htmlNode); }
+ }
+
+ onChildEntering(htmlNode: HTMLElement) {
+ this.setState({ height: htmlNode.offsetHeight });
+ if (this.props.onChildEntering) { this.props.onChildEntering(htmlNode); }
+ }
+
+ onChildEntered(htmlNode: HTMLElement) {
+ this.setState({ height: null });
+ if (this.props.onChildEntered) { this.props.onChildEntered(htmlNode); }
+ }
+
+ onChildExit(htmlNode: HTMLElement) {
+ this.setState({ height: htmlNode.offsetHeight });
+ if (this.props.onChildExit) { this.props.onChildExit(htmlNode); }
+ }
+
+ onChildExiting(htmlNode: HTMLElement) {
+ if (this.props.onChildExiting) { this.props.onChildExiting(htmlNode); }
+ }
+
+ onChildExited(htmlNode: HTMLElement) {
+ this.setState({ height: null });
+ if (this.props.onChildExited) { this.props.onChildExited(htmlNode); }
+ }
+
+ renderChildTransition = (child: React.ReactElement) => {
+ if (!child.key && process.env.NODE_ENV === 'development') {
+ // eslint-disable-next-line no-console
+ console.warn("TransitionReplace: A child is missing a 'key' prop. Keys are required for any child of this component.");
+ }
+
+ const commonChildStyles: React.CSSProperties = {
+ padding: '.1px 0',
+ };
+
+ const transitionStyles: Record = {
+ entering: {},
+ entered: {},
+ exiting: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ pointerEvents: 'none',
+ },
+ exited: {},
+ };
+
+ return (
+
+ {state => (
+
+ {child}
+
+ )}
+
+ );
+ };
+
+ render() {
+ return (
+
+ {React.Children.map(this.props.children, this.renderChildTransition)}
+
+ );
+ }
+}
+
+export default TransitionReplace;