Skip to content

Commit

Permalink
feat: add typings for <IconButton>
Browse files Browse the repository at this point in the history
  • Loading branch information
bradenmacdonald committed Jun 17, 2024
1 parent ef4990c commit 1b642a6
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/Chip/ChipIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface ChipIconProps {
className: string,
src: React.ComponentType,
onClick?: KeyboardEventHandler & MouseEventHandler,
alt?: string,
alt: string,
variant: string,
disabled?: boolean,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,27 @@ describe('<IconButton />', () => {
const alt = 'alternative';
const iconAs = Icon;
const src = InfoOutline;
const variant = 'secondary';
const variant = 'secondary' as const;
const props = {
alt,
src,
iconAs,
variant,
};
const iconParams = {
const deprecatedFontAwesomeExample = {
prefix: 'pgn',
iconName: 'InfoOutlineIcon',
icon: [InfoOutline],
};
it('renders with required props', () => {
const tree = renderer.create((
<IconButton icon={iconParams} alt={alt} />
<IconButton iconAs={Icon} src={InfoOutline} alt={alt} />
)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders with deprecated props', () => {
const tree = renderer.create((
<IconButton icon={deprecatedFontAwesomeExample} alt={alt} />
)).toJSON();
expect(tree).toMatchSnapshot();
});
Expand Down Expand Up @@ -94,4 +100,19 @@ describe('<IconButton />', () => {
expect(spy2).toHaveBeenCalledTimes(1);
});
});

describe('<IconButton.IconButtonWithTooltip>', () => {
it('renders with required props', () => {
const tree = renderer.create((
<IconButton.IconButtonWithTooltip
iconAs={Icon}
src={InfoOutline}
alt={alt}
tooltipContent="Hello"
tooltipPlacement="left-end"
/>
)).toJSON();
expect(tree).toMatchSnapshot();
});
});
});
43 changes: 0 additions & 43 deletions src/IconButton/__snapshots__/IconButton.test.jsx.snap

This file was deleted.

112 changes: 112 additions & 0 deletions src/IconButton/__snapshots__/IconButton.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<IconButton /> <IconButton.IconButtonWithTooltip> renders with required props 1`] = `
<button
aria-label="alternative"
className="btn-icon btn-icon-primary btn-icon-md"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
type="button"
>
<span
className="btn-icon__icon-container"
>
<span
className="pgn__icon btn-icon__icon"
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 7h2v2h-2V7Zm0 4h2v6h-2v-6Zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8Z"
fill="currentColor"
/>
</svg>
</span>
</span>
</button>
`;
exports[`<IconButton /> renders with deprecated props 1`] = `
<button
aria-label="alternative"
className="btn-icon btn-icon-primary btn-icon-md"
onClick={[Function]}
type="button"
>
<span
className="btn-icon__icon-container"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-InfoOutlineIcon btn-icon__icon"
data-icon="InfoOutlineIcon"
data-prefix="pgn"
focusable="false"
role="img"
style={{}}
viewBox="0 0 function SvgInfoOutline(props) {
return /*#__PURE__*/React.createElement("svg", _extends({
width: 24,
height: 24,
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg"
}, props), /*#__PURE__*/React.createElement("path", {
d: "M11 7h2v2h-2V7Zm0 4h2v6h-2v-6Zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8Z",
fill: "currentColor"
}));
} undefined"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
style={{}}
/>
</svg>
</span>
</button>
`;
exports[`<IconButton /> renders with required props 1`] = `
<button
aria-label="alternative"
className="btn-icon btn-icon-primary btn-icon-md"
onClick={[Function]}
type="button"
>
<span
className="btn-icon__icon-container"
>
<span
className="pgn__icon btn-icon__icon"
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 7h2v2h-2V7Zm0 4h2v6h-2v-6Zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8Z"
fill="currentColor"
/>
</svg>
</span>
</span>
</button>
`;
84 changes: 62 additions & 22 deletions src/IconButton/index.jsx → src/IconButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { type Placement } from 'react-bootstrap/Overlay';

import { OverlayTrigger } from '../Overlay';
import Tooltip from '../Tooltip';
import Icon from '../Icon';

const IconButton = React.forwardRef(({
interface Props extends React.HTMLAttributes<HTMLButtonElement> {
iconAs?: typeof Icon | typeof FontAwesomeIcon,
/** Additional CSS class[es] to apply to this button */
className?: string;
/** Alt text for your icon. For best practice, avoid using alt text to describe
* the image in the `IconButton`. Instead, we recommend describing the function
* of the button. */
alt: string;
/** Changes icon styles for dark background */
invertColors?: boolean;
/** An icon component to render. Example import of a Paragon icon component:
* `import { Check } from '@openedx/paragon/icons';`
* */
// Note: React.ComponentType is what we want here. React.ElementType would allow some element type strings like "div",
// but we only want to allow components like 'Add' (a specific icon component function/class)
src?: React.ComponentType;
/** Extra class names that will be added to the icon */
iconClassNames?: string;
/** Click handler for the button */
onClick?: React.MouseEventHandler<HTMLButtonElement>;
/** whether to show the `IconButton` in an active state, whose styling is distinct from default state */
isActive?: boolean;
/** @deprecated Using FontAwesome icons is deprecated. Instead, pass iconAs={Icon} src={...} */
icon?: { prefix?: string; iconName?: string, icon?: any[] },
/** Type of button (uses Bootstrap options) */
variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'light' | 'dark' | 'black' | 'brand';
/** size of button to render */
size?: 'sm' | 'md' | 'inline';
/** no children */
children?: never;
}

const IconButton = React.forwardRef<HTMLButtonElement, Props>(({
className,
alt,
invertColors,
Expand All @@ -18,6 +52,7 @@ const IconButton = React.forwardRef(({
variant,
iconAs,
isActive,
children, // unused, just here because we don't want it to be part of 'attrs'
...attrs
}, ref) => {
const invert = invertColors ? 'inverse-' : '';
Expand Down Expand Up @@ -50,7 +85,7 @@ const IconButton = React.forwardRef(({
<span className="btn-icon__icon-container">
<IconComponent
className={classNames('btn-icon__icon', iconClassNames)}
icon={icon}
icon={icon as any}
src={src}
/>
</span>
Expand All @@ -60,7 +95,7 @@ const IconButton = React.forwardRef(({

IconButton.defaultProps = {
iconAs: undefined,
src: null,
src: undefined,
icon: undefined,
iconClassNames: undefined,
className: undefined,
Expand All @@ -69,18 +104,19 @@ IconButton.defaultProps = {
size: 'md',
onClick: () => {},
isActive: false,
children: undefined,
};

IconButton.propTypes = {
/** A custom class name. */
className: PropTypes.string,
/** Component that renders the icon, currently defaults to `FontAwesomeIcon`,
* but is going to be deprecated soon, please use Paragon's icons instead. */
iconAs: PropTypes.elementType,
iconAs: PropTypes.elementType as any,
/** An icon component to render. Example import of a Paragon icon component:
* `import { Check } from '@openedx/paragon/dist/icon';`
* `import { Check } from '@openedx/paragon/icons';`
* */
src: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
src: PropTypes.elementType as any,
/** Alt text for your icon. For best practice, avoid using alt text to describe
* the image in the `IconButton`. Instead, we recommend describing the function
* of the button. */
Expand All @@ -93,7 +129,7 @@ IconButton.propTypes = {
iconName: PropTypes.string,
// eslint-disable-next-line react/forbid-prop-types
icon: PropTypes.array,
}),
}) as any,
/** Extra class names that will be added to the icon */
iconClassNames: PropTypes.string,
/** Click handler for the button */
Expand All @@ -106,38 +142,40 @@ IconButton.propTypes = {
isActive: PropTypes.bool,
};

interface PropsWithTooltip extends Props {
/** choose from https://popper.js.org/docs/v2/constructors/#options */
tooltipPlacement: Placement,
/** any content to pass to tooltip content area */
tooltipContent: React.ReactNode,
}

/**
*
* @param { object } args Arguments
* @param { string } args.tooltipPlacement choose from https://popper.js.org/docs/v2/constructors/#options
* @param { React.Component } args.tooltipContent any content to pass to tooltip content area
* @returns { IconButton } a button wrapped in overlaytrigger
* An icon button wrapped in overlaytrigger to display a tooltip.
*/
function IconButtonWithTooltip({
tooltipPlacement, tooltipContent, variant, invertColors, ...props
}) {
const invert = invertColors ? 'inverse-' : '';
tooltipPlacement, tooltipContent, ...props
}: PropsWithTooltip) {
const invert = props.invertColors ? 'inverse-' : '';
return (
<OverlayTrigger
placement={tooltipPlacement}
overlay={(
<Tooltip
id={`iconbutton-tooltip-${tooltipPlacement}`}
variant={invert ? 'light' : ''}
variant={invert ? 'light' : undefined}
>
{tooltipContent}
</Tooltip>
)}
>
<IconButton variant={variant} invertColors={invertColors} {...props} />
<IconButton {...props} />
</OverlayTrigger>
);
}

IconButtonWithTooltip.defaultProps = {
...IconButton.defaultProps,
tooltipPlacement: 'top',
variant: 'primary',
invertColors: false,
};

IconButtonWithTooltip.propTypes = {
Expand All @@ -151,7 +189,9 @@ IconButtonWithTooltip.propTypes = {
invertColors: PropTypes.bool,
};

IconButton.IconButtonWithTooltip = IconButtonWithTooltip;
(IconButton as any).IconButtonWithTooltip = IconButtonWithTooltip;

export default IconButton;
export default IconButton as typeof IconButton & {
IconButtonWithTooltip: typeof IconButtonWithTooltip,
};
export { IconButtonWithTooltip };
Loading

0 comments on commit 1b642a6

Please sign in to comment.