Skip to content

Commit

Permalink
feat: added card actionIcon prop with 'dismiss' and 'overflow'
Browse files Browse the repository at this point in the history
  • Loading branch information
kalebjdavenport committed Aug 9, 2022
1 parent dfc009c commit 0b67220
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.DS_Store
.eslintcache
.prettierrc
node_modules
npm-debug.log
coverage
Expand Down
7 changes: 7 additions & 0 deletions src/Card/Card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@
}
}

.pgn__card-close-container {
position: absolute;
z-index: 10;
top: .5rem;
inset-inline-end: .25rem;
}

.pgn__card-divider {
border-top: 1px solid $card-divider-bg;
height: 0;
Expand Down
81 changes: 81 additions & 0 deletions src/Card/CardActionIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { IconButton, Icon } from '..';
import { Close, MoreVert } from '../../icons';

const CardActionIcon = React.forwardRef(
({
className,
actionIcon,
onClick,
isActive,
variant,
}, ref) => {
if (actionIcon === 'overflow') {
return (
<div
className={classNames(
className,
'd-flex flex-wrap pgn__card-close-container',
)}
ref={ref}
>
<IconButton
src={MoreVert}
iconAs={Icon}
alt="See more"
onClick={onClick}
variant={variant}
invertColors={variant === 'dark'}
isActive={isActive}
className="mr-2"
/>
</div>
);
}
return (
<div
className={classNames(
className,
'd-flex flex-wrap pgn__card-close-container',
)}
ref={ref}
>
<IconButton
src={Close}
iconAs={Icon}
alt="Close"
onClick={onClick}
variant={variant}
invertColors={variant === 'dark'}
isActive={isActive}
className="mr-2"
/>
</div>
);
},
);

CardActionIcon.propTypes = {
/** Specifies class name to append to the base element. */
className: PropTypes.string,
/** Options for which type of actionIcon the Card will use. */
actionIcon: PropTypes.oneOf(['overflow', 'dismiss']),
/** Click handler for the button */
onClick: PropTypes.func,
/** whether to show the `IconButton` in an active state, whose styling is distinct from default state */
isActive: PropTypes.bool,
/** The visual style of the dialog box */
variant: PropTypes.oneOf(['default', 'warning', 'danger', 'success', 'dark']),
};

CardActionIcon.defaultProps = {
className: undefined,
actionIcon: 'dismiss',
onClick: () => {},
isActive: false,
variant: 'dark',
};

export default CardActionIcon;
2 changes: 1 addition & 1 deletion src/Card/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This component uses a `Card` from react-bootstrap as a base component and extend
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth });

return (
<Card style={{ width: isExtraSmall ? "100%" : "18rem" }}>
<Card actionIcon="dismiss" style={{ width: isExtraSmall ? "100%" : "18rem" }}>
<Card.ImageCap
src="https://source.unsplash.com/360x200/?nature,flower"
srcAlt="Card image"
Expand Down
12 changes: 11 additions & 1 deletion src/Card/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import CardSection from './CardSection';
import CardFooter from './CardFooter';
import CardImageCap from './CardImageCap';
import CardBody from './CardBody';
import CardActionIcon from './CardActionIcon';

const Card = React.forwardRef(({
orientation,
actionIcon,
isLoading,
className,
isClickable,
muted,
children,
...props
}, ref) => (
<CardContextProvider orientation={orientation} isLoading={isLoading}>
Expand All @@ -28,7 +31,10 @@ const Card = React.forwardRef(({
})}
ref={ref}
tabIndex={isClickable ? '0' : '-1'}
/>
>
{actionIcon ? <CardActionIcon actionIcon={actionIcon} variant="dark" /> : undefined}
{children}
</BaseCard>
</CardContextProvider>
));

Expand All @@ -37,6 +43,7 @@ export { default as CardDeck } from 'react-bootstrap/CardDeck';
export { default as CardImg } from 'react-bootstrap/CardImg';
export { default as CardGroup } from 'react-bootstrap/CardGroup';
export { default as CardGrid } from './CardGrid';
export { default as CardActionIcon } from './CardActionIcon';

Card.propTypes = {
...BaseCard.propTypes,
Expand All @@ -46,6 +53,8 @@ Card.propTypes = {
orientation: PropTypes.oneOf(['vertical', 'horizontal']),
/** Specifies whether the `Card` is clickable, if `true` appropriate `hover` and `focus` styling will be added. */
isClickable: PropTypes.bool,
/** Optional interactive icon positioned in the top right of the card. */
actionIcon: PropTypes.oneOf(['overflow', 'dismiss']),
/** Specifies loading state. */
isLoading: PropTypes.bool,
/** Specifies whether to display `Card` in muted styling. */
Expand All @@ -58,6 +67,7 @@ Card.defaultProps = {
orientation: 'vertical',
isClickable: false,
muted: false,
actionIcon: undefined,
isLoading: false,
};

Expand Down
27 changes: 27 additions & 0 deletions src/Card/tests/CardActionIcon.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { mount } from 'enzyme';
import CardActionIcon from '../CardActionIcon';
import CardContext from '../CardContext';

// eslint-disable-next-line react/prop-types
const CardActionIconWrapper = ({ isLoading }) => (
<CardContext.Provider value={{ isLoading }}>
<CardActionIcon />
</CardContext.Provider>
);

describe('<CardActionIcon />', () => {
it('renders without props', () => {
const tree = renderer.create((
<CardActionIcon />
)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders with "overflow" option for actionIcon prop', () => {
const tree = renderer.create((
<CardActionIcon actionIcon="overflow" />
)).toJSON();
expect(tree).toMatchSnapshot();
});
});
75 changes: 75 additions & 0 deletions src/Card/tests/__snapshots__/CardActionIcon.test.jsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<CardActionIcon /> renders with "overflow" option for actionIcon prop 1`] = `
<div
className="d-flex flex-wrap pgn__card-close-container"
>
<button
aria-label="See more"
className="btn-icon btn-icon-inverse-dark btn-icon-md mr-2"
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="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
fill="currentColor"
/>
</svg>
</span>
</span>
</button>
</div>
`;

exports[`<CardActionIcon /> renders without props 1`] = `
<div
className="d-flex flex-wrap pgn__card-close-container"
>
<button
aria-label="Close"
className="btn-icon btn-icon-inverse-dark btn-icon-md mr-2"
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="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"
fill="currentColor"
/>
</svg>
</span>
</span>
</button>
</div>
`;

0 comments on commit 0b67220

Please sign in to comment.