Skip to content

Commit

Permalink
feat(card): Add Card component (#279)
Browse files Browse the repository at this point in the history
* feat(card): Add Card component

* Add CardContent component

* Changes from design review
  • Loading branch information
diogomateus authored Oct 25, 2023
1 parent 4e4a606 commit 6cdfec3
Show file tree
Hide file tree
Showing 5 changed files with 427 additions and 2 deletions.
244 changes: 244 additions & 0 deletions src/lib/components/card/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import { Card, CardProps } from '.';
import { illustrations } from '../../util/images';
import Button from '../button';
import { Badge } from '../badge';
import { CheckIcon, MehIcon, PlusCircleIcon, XIcon } from '../icon';

const story = {
title: 'JSX/Card',
component: Card,
argTypes: {
density: {
description: 'Spacing around the card'
},
label: {
description: 'Content to be rendered as label'
},
title: {
description: 'Content to be rendered as title'
},
titleVariant: {
description: 'Variant that allows changing title sizing styles.'
},
description: {
description: 'Content to be rendered as description'
},
descriptionVariant: {
description: 'Variant that allows changing description sizing styles.'
},
icon: {
description: 'ReactNode to be rendered on the left side of the card',
},
children: {
control: { type: 'text' },
description: 'Content that is displayed inside the card under the pre-defined props',
},
onClick: {
description: 'On click action to be triggered on card click.',
},
dropShadow: {
description: 'Wether to display card with drop shadow styles or not.',
},
actionIcon: {
description: 'ReactNode to be rendered on the right side of the card when there is an onClick action. By default it renders the ChevronRightIcon.',
},
},
args: {
density: 'balanced',
description: 'Believe you’re a great fit but can’t find a position listed for your skill set? We’d love to hear from you!',
descriptionVariant: 'large',
label: 'Label',
title: 'Honest, simple insurance',
titleVariant: 'large',
icon: 'ABC',
children: '',
classNames: {
wrapper: '',
label: '',
title: '',
description: '',
children: '',
icon: ''
},
dropShadow: true,
}
};

export const CardStory = ({
actionIcon,
children,
classNames,
density,
description,
descriptionVariant,
dropShadow,
icon,
label,
onClick,
title,
titleVariant,
}: CardProps) => (
<div className='d-flex p24 bg-grey-200'>
<Card
classNames={classNames}
description={description}
descriptionVariant={descriptionVariant}
density={density}
dropShadow={dropShadow}
icon={icon}
label={label}
title={title}
titleVariant={titleVariant}
onClick={onClick}
actionIcon={actionIcon}
>
{children}
</Card>
</div>
);

CardStory.storyName = "Card";

export const CardDensities = () => (
<div className='d-flex fd-column gap16 p24 bg-grey-200'>
<Card
title={'Card density: Compact'}
density='compact'
/>
<Card
title={'Card density: Balanced'}
density='balanced'
/>
<Card
title={'Card density: Spacious'}
density='spacious'
/>
</div>
);

export const CardsWithIcons = ({
children,
icon,
title,
}: CardProps) => (
<div className='d-flex gap16 p24 bg-grey-200'>
<Card
icon={
<img
alt=""
src={illustrations.aids}
width={24}
/>
}
title={title}
/>
<Card
icon={<MehIcon size={24} />}
title={title}
/>
</div>
);

export const CardWithOnClickAction = ({
children,
icon,
title,
}: CardProps) => (
<div className='d-flex p24 bg-grey-200'>
<Card
icon={
<img
alt=""
src={illustrations.aids}
width={24}
/>
}
title={title}
titleVariant={'medium'}
onClick={() => {}}
>
{children}
</Card>
</div>
);

export const CardOverridesStyles = ({
children,
label,
title,
}: CardProps) => (
<div className='d-flex p24 bg-grey-200'>
<Card
label={label}
title={title}
titleVariant={'medium'}
icon={<PlusCircleIcon color="grey-100" size={32} />}
classNames={{
wrapper: 'bg-grey-700',
label: 'tc-white',
title: 'tc-white'
}}
>
{children}
</Card>
</div>
);

export const CardsWithinCardsAndComplexLayout = ({
children,
label,
title,
}: CardProps) => (
<div className='d-flex p24 bg-grey-200'>
<Card
label={(
<Badge size='small' variant='success'>
Active
</Badge>
)}
title={(
<div className='d-flex jc-between ai-center w100'>
Coverage overview

<Button
onClick={() => {}}
buttonTitle='Full coverage details'
buttonType='secondaryGrey'
/>
</div>
)}
>
<div className='d-flex gap16 mt16'>
<Card
description="Lost keys"
classNames={{ wrapper: 'bg-grey-300' }}
icon={<CheckIcon color={'primary-500'} />}
density='compact'
/>
<Card
description="Broken glass"
classNames={{ wrapper: 'bg-grey-300' }}
icon={<XIcon color={'primary-500'} />}
density='compact'
/>
</div>

<div className='d-flex gap16 mt16'>
<Card
description="Damage to property"
classNames={{ wrapper: 'bg-grey-300' }}
icon={<CheckIcon color={'primary-500'} />}
density='compact'
/>
<Card
description="Drones"
classNames={{ wrapper: 'bg-grey-300' }}
icon={<XIcon color={'primary-500'} />}
density='compact'
/>
</div>
</Card>
</div>
);

export default story;
144 changes: 144 additions & 0 deletions src/lib/components/card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { ReactNode } from 'react';
import classNamesUtil from 'classnames';
import { ChevronRightIcon } from '../icon';

import styles from './style.module.scss';

export interface CardProps {
children?: ReactNode;
classNames?: {
wrapper?: string;
label?: string;
title?: string;
description?: string;
children?: string;
icon?: string;
actionIcon?: string;
};
density?: 'balanced' | 'compact' | 'spacious';
dropShadow?: boolean;
icon?: ReactNode;
title?: ReactNode;
titleVariant?: 'small' | 'medium' | 'large';
description?: ReactNode;
descriptionVariant?: 'small' | 'large';
label?: ReactNode;
onClick?: () => void;
actionIcon?: ReactNode;
}

const CardContent = ({
children,
classNames,
density = 'balanced',
description,
descriptionVariant = 'large',
dropShadow = true,
icon,
label,
onClick,
actionIcon,
title,
titleVariant = 'large',
}: CardProps) => (
<section
className={classNamesUtil(
'd-flex fd-column jc-center br8 bg-white w100 ta-left',
{ 'bs-sm': dropShadow },
{
compact: 'p16',
balanced: 'p24',
spacious: 'p32'
}[density],
classNames?.wrapper,
)}
>
<div className='d-flex w100'>
{icon && (
<div
className={classNamesUtil(
`d-flex ai-center tc-primary-500`,
styles.icon,
styles[`icon${density}`],
classNames?.icon
)}
>
{icon}
</div>
)}

<div className='d-flex jc-between w100'>
<div className='d-flex jc-center gap8 fd-column tc-grey-900 w100'>
{label && (
<h3
className={classNamesUtil('p-p--small', classNames?.label)}
>
{label}
</h3>
)}

{title && (
<h2
className={classNamesUtil(classNames?.title, {
large:'p-h3',
medium:'p-h4',
small:'p-p',
}[titleVariant])}
>
{title}
</h2>
)}

{description && (
<div
className={classNamesUtil(
'tc-grey-600',
classNames?.description,
descriptionVariant === 'small' ? 'p-p--small' : 'p-p'
)}
>
{description}
</div>
)}
</div>

{onClick && (
<div
className={classNamesUtil(
styles.actionIcon,
classNames?.actionIcon,
styles[`actionIcon${density}`],
'd-flex ai-center'
)}
>
{actionIcon || <ChevronRightIcon size={24} />}
</div>
)}
</div>
</div>

{children && (
<div className={classNames?.children}>{children}</div>
)}
</section>
);

const Card = (props: CardProps) => {
const { onClick } = props;

if (onClick) {
return (
<button
className={classNamesUtil('c-pointer d-flex w100 br8', styles.button)}
onClick={onClick}
type="button"
>
<CardContent {...props} />
</button>
)
}

return <CardContent {...props} />;
}

export { Card };
Loading

0 comments on commit 6cdfec3

Please sign in to comment.