diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-17 19:46:08 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:14:41 +0100 |
| commit | c153f93dc8691a71dc76aad3dd618298da9d238a (patch) | |
| tree | 9c116c1472bab5585f98bceee19cfeca5041360d /src/components/molecules/card/card.tsx | |
| parent | 006b15b467a5cd835a6eab1b49023100bdc8f2e6 (diff) | |
refactor(components): rewrite Card component
* make the component more generic
* merge `<Summary />` and `<Comment />` styles into card component
to avoid repeating the same structure
* remove most of the props to use composition
However the CSS is a bit complex because of the two variants...
Also, the component should be refactored when the CSS pseudo-class
`:has` has enough support: the provider and the `cover` and `meta`
props should be removed.
Diffstat (limited to 'src/components/molecules/card/card.tsx')
| -rw-r--r-- | src/components/molecules/card/card.tsx | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/src/components/molecules/card/card.tsx b/src/components/molecules/card/card.tsx new file mode 100644 index 0000000..788b040 --- /dev/null +++ b/src/components/molecules/card/card.tsx @@ -0,0 +1,112 @@ +import { + forwardRef, + type HTMLAttributes, + type ForwardedRef, + type ReactNode, + type ReactElement, +} from 'react'; +import { Article, ButtonLink, type ButtonLinkProps } from '../../atoms'; +import { CardCoverProvider, CardFooterMetaProvider } from './card-provider'; +import styles from './card.module.scss'; + +type CardBaseProps<T extends string | undefined> = T extends string + ? Omit<ButtonLinkProps, 'children' | 'isExternal' | 'kind' | 'shape' | 'to'> + : Omit<HTMLAttributes<HTMLDivElement>, 'children'>; + +export type CardProps<T extends string | undefined> = CardBaseProps<T> & { + /** + * The card contents. + */ + children: ReactNode; + /** + * The card cover. You need to add a `<CardHeader />` as children to use it. + */ + cover?: ReactElement; + /** + * Should the contents be centered? + * + * @default false + */ + isCentered?: boolean; + /** + * Link the card to another page. + * + * @default undefined + */ + linkTo?: T; + /** + * The meta to place in the card footer. You need to add a `<CardFooter />` + * as children to use it. + */ + meta?: ReactElement; + /** + * The card variant. + * + * @default 1 + */ + variant?: 1 | 2; +}; + +const CardWrapper = <T extends string | undefined>( + { + children, + className = '', + cover, + isCentered = false, + linkTo, + meta, + variant = 1, + ...props + }: CardProps<T>, + ref: ForwardedRef<T extends string ? HTMLAnchorElement : HTMLDivElement> +) => { + const wrapperClass = [ + styles.wrapper, + styles[isCentered ? 'wrapper--centered' : 'wrapper--not-centered'], + styles[linkTo ? 'wrapper--is-link' : 'wrapper--is-block'], + className, + ].join(' '); + const cardClass = [ + styles.card, + styles[cover ? 'card--has-cover' : 'card--no-cover'], + styles[meta ? 'card--has-footer-meta' : 'card--no-footer-meta'], + styles[`card--variant-${variant}`], + ].join(' '); + + return ( + <CardCoverProvider cover={cover}> + <CardFooterMetaProvider meta={meta}> + {linkTo ? ( + <ButtonLink + {...(props as CardBaseProps<typeof linkTo>)} + className={wrapperClass} + ref={ref} + // eslint-disable-next-line react/jsx-no-literals -- Shape allowed + shape="auto" + to={linkTo} + > + <Article className={cardClass}>{children}</Article> + </ButtonLink> + ) : ( + <div + {...(props as CardBaseProps<undefined>)} + className={wrapperClass} + ref={ref as ForwardedRef<HTMLDivElement>} + > + <Article className={cardClass}>{children}</Article> + </div> + )} + </CardFooterMetaProvider> + </CardCoverProvider> + ); +}; + +/** + * Card component + * + * Render a card with title and some other optional data. + * + * TODO: remove `cover` and `meta` props (and adapt CSS) when support for CSS + * `:has()` pseudo-class will be good enough. + */ +export const Card = forwardRef(CardWrapper); |
