Skip to content

Commit

Permalink
feat(card): add interaction feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
donaldjbrady committed Aug 20, 2020
1 parent 475ae3a commit 946f907
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { boolean, color, number, select, text } from '@storybook/addon-knobs';
import { mdiMessage, mdiSend } from '@mdi/js';
import { storiesOf } from '@storybook/react';

import Button, { FeedbackTypes } from './Button';
import Button from './Button';
import colors from '../../enums/colors';
import variants from '../../enums/variants';
import FeedbackTypes from '../../enums/feedbackTypes';

const options = {
none: '',
Expand Down
7 changes: 1 addition & 6 deletions packages/hs-react-ui/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SubcomponentPropsType } from '../commonTypes';
import { getShadowStyle } from '../../utils/styles';
import InteractionFeedback from '../InteractionFeedback';
import { InteractionFeedbackProps } from '../InteractionFeedback/InteractionFeedback';
import FeedbackTypes from '../../enums/feedbackTypes';

export type ButtonContainerProps = {
elevation: number;
Expand All @@ -34,11 +35,6 @@ export enum ButtonTypes {
submit = 'submit',
}

export enum FeedbackTypes {
simple = 'simple',
ripple = 'ripple',
}

export type ButtonProps = {
StyledContainer?: string & StyledComponentBase<any, {}, ButtonContainerProps>;
containerProps?: SubcomponentPropsType;
Expand Down Expand Up @@ -218,5 +214,4 @@ const Button = ({
Button.Container = ButtonContainer;
Button.ButtonTypes = ButtonTypes;
Button.LoadingBar = StyledProgress;
Button.FeedbackTypes = FeedbackTypes;
export default Button;
35 changes: 21 additions & 14 deletions packages/hs-react-ui/src/components/Card/Card.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { text, number } from '@storybook/addon-knobs';
import { text, number, boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';

import { storiesOf } from '@storybook/react';
Expand All @@ -9,6 +9,7 @@ import Card, { Header, Footer } from './Card';
import colors from '../../enums/colors';
import timings from '../../enums/timings';
import fonts from '../../enums/fonts';
import feedbackTypes from '../../enums/feedbackTypes';

const design = {
type: 'figma',
Expand All @@ -18,19 +19,23 @@ const design = {
storiesOf('Card', module)
.add(
'Default',
() => (
<Card
header={text('header', 'Card title')}
footer={text('footer', 'Actionable buttons, whatever other stuff you want to pass in!')}
elevation={number('elevation', 2, { range: true, min: -5, max: 5, step: 1 })}
onClick={action('onClick')}
>
{text(
'children',
'A Hello, World! program generally is a computer program that outputs or displays the message Hello, World!.',
)}
</Card>
),
() => {
return (
<Card
header={text('header', 'Card title')}
footer={text('footer', 'Actionable buttons, whatever other stuff you want to pass in!')}
elevation={number('elevation', 2, { range: true, min: -5, max: 5, step: 1 })}
onClick={action('onClick')}
disableFeedback={boolean('disableFeedback', false)}
feedbackType={select('feedbackType', feedbackTypes, feedbackTypes.ripple)}
>
{text(
'children',
'A Hello, World! program generally is a computer program that outputs or displays the message Hello, World!.',
)}
</Card>
);
},
{ design, centered: true },
)
.add('Themed', () => {
Expand Down Expand Up @@ -83,6 +88,8 @@ storiesOf('Card', module)
header={text('header', 'Card title')}
footer={text('footer', 'Actionable buttons, whatever other stuff you want to pass in!')}
elevation={number('elevation', 0, { range: true, min: -5, max: 5, step: 1 })}
onClick={action('onClick')}
disableFeedback={boolean('disableFeedback', true)}
>
{text(
'children',
Expand Down
110 changes: 101 additions & 9 deletions packages/hs-react-ui/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import React, { ReactNode, MouseEvent } from 'react';
import styled, { StyledComponentBase } from 'styled-components';
import useMeasure from 'react-use-measure';
import { ResizeObserver } from '@juggle/resize-observer';
import { darken } from 'polished';

import timings from '../../enums/timings';
import { Div } from '../../htmlElements';
import { SubcomponentPropsType } from '../commonTypes';
import { useTheme } from '../../context';
import { getShadowStyle } from '../../utils/styles';
import InteractionFeedback, {
InteractionFeedbackProps,
} from '../InteractionFeedback/InteractionFeedback';
import FeedbackTypes from 'src/enums/feedbackTypes';

export type CardContainerProps = {
elevation: number;
feedbackType: FeedbackTypes;
disableFeedback: boolean;
};

export const CardContainer = styled(Div)`
${({ elevation }: { elevation: number }) => {
${({ elevation, feedbackType, disableFeedback }: CardContainerProps) => {
const { colors } = useTheme();
return `
Expand All @@ -20,6 +33,19 @@ export const CardContainer = styled(Div)`
transition: filter ${timings.slow}, box-shadow ${timings.slow}, border ${timings.normal};
${getShadowStyle(elevation, colors.shadow)}
background-color: ${colors.background};
${
feedbackType === FeedbackTypes.simple && !disableFeedback
? `
&:active {
background-color: ${
colors.background !== 'transparent'
? darken(0.1, colors.background)
: 'rgba(0, 0, 0, 0.1)'
};
}
`
: ''
}
`;
}}
`;
Expand Down Expand Up @@ -81,6 +107,7 @@ export interface CardProps {
headerProps?: SubcomponentPropsType;
bodyProps?: SubcomponentPropsType;
footerProps?: SubcomponentPropsType;
interactionFeedbackProps?: Omit<InteractionFeedbackProps, 'children'>;

onClick?: (evt: MouseEvent) => void;

Expand All @@ -89,8 +116,12 @@ export interface CardProps {
footer?: ReactNode;

elevation?: number;
disableFeedback?: boolean;
feedbackType?: FeedbackTypes;
}

const defaultOnClick = () => {};

const Card = ({
StyledContainer = CardContainer,
StyledHeader = Header,
Expand All @@ -101,21 +132,82 @@ const Card = ({
headerProps,
bodyProps,
footerProps,
interactionFeedbackProps,

onClick = () => {},
onClick = defaultOnClick,

header,
children,
footer,

elevation = 1,
}: CardProps): JSX.Element | null => (
<StyledContainer onClick={onClick} elevation={elevation} {...containerProps}>
{header && <StyledHeader {...headerProps}>{header}</StyledHeader>}
{children && <StyledBody {...bodyProps}>{children}</StyledBody>}
{footer && <StyledFooter {...footerProps}>{footer}</StyledFooter>}
</StyledContainer>
);
disableFeedback,
feedbackType = FeedbackTypes.ripple,
}: CardProps): JSX.Element | null => {
const { colors } = useTheme();
// get the bounding box of the card so that we can set it's width to r
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const [ref, cardBounds] = useMeasure({ polyfill: ResizeObserver });

if (!disableFeedback && feedbackType !== FeedbackTypes.simple && onClick !== defaultOnClick) {
// 5% larger than the width to account for the circle to cover the entire card
const feedbackRadius = cardBounds.width * 1.05;
const feedbackRatio = feedbackRadius / 100; // 100 is the default r
const tension = (750 / feedbackRatio) * 2; // 750 is the default tension, x2 makes it look a little quicker over large cards
const transitionProps = {
from: {
r: 0,
opacity: 0.25,
fill: colors.grayLight,
},
enter: {
r: feedbackRadius,
opacity: 0.25,
fill: colors.grayLight,
},
leave: {
r: 0,
opacity: 0,
fill: colors.grayLight,
},
config: {
mass: 1,
tension: tension,
friction: 35,
},
};
return (
<InteractionFeedback transitionProps={transitionProps} {...interactionFeedbackProps}>
<StyledContainer
ref={ref}
onClick={onClick}
elevation={elevation}
feedbackType={feedbackType}
disableFeedback={disableFeedback || onClick === defaultOnClick}
{...containerProps}
>
{header && <StyledHeader {...headerProps}>{header}</StyledHeader>}
{children && <StyledBody {...bodyProps}>{children}</StyledBody>}
{footer && <StyledFooter {...footerProps}>{footer}</StyledFooter>}
</StyledContainer>
</InteractionFeedback>
);
}
return (
<StyledContainer
onClick={onClick}
elevation={elevation}
feedbackType={feedbackType}
disableFeedback={disableFeedback || onClick === defaultOnClick}
{...containerProps}
>
{header && <StyledHeader {...headerProps}>{header}</StyledHeader>}
{children && <StyledBody {...bodyProps}>{children}</StyledBody>}
{footer && <StyledFooter {...footerProps}>{footer}</StyledFooter>}
</StyledContainer>
);
};

Card.Header = Header;
Card.NoPaddingHeader = NoPaddingHeader;
Expand Down
6 changes: 6 additions & 0 deletions packages/hs-react-ui/src/enums/feedbackTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum FeedbackTypes {
simple = 'simple',
ripple = 'ripple',
}

export default FeedbackTypes;
2 changes: 2 additions & 0 deletions packages/hs-react-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import colors from './enums/colors';
import timings from './enums/timings';
import fonts from './enums/fonts';
import variants from './enums/variants';
import feedbackTypes from './enums/feedbackTypes';

export {
Button,
Expand All @@ -41,4 +42,5 @@ export {
timings,
fonts,
variants,
feedbackTypes,
};

0 comments on commit 946f907

Please sign in to comment.