import clsx from 'clsx'; import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import { HStack, Text } from 'soapbox/components/ui'; import SvgIcon from 'soapbox/components/ui/icon/svg-icon'; const sizes = { md: 'p-4 sm:rounded-xl', lg: 'p-4 sm:p-6 sm:rounded-xl', xl: 'p-4 sm:p-10 sm:rounded-3xl', }; const messages = defineMessages({ back: { id: 'card.back.label', defaultMessage: 'Back' }, }); export type CardSizes = keyof typeof sizes interface ICard { /** The type of card. */ variant?: 'default' | 'rounded' | 'slim' /** Card size preset. */ size?: CardSizes /** Extra classnames for the <div> element. */ className?: string /** Elements inside the card. */ children: React.ReactNode } /** An opaque backdrop to hold a collection of related elements. */ const Card = React.forwardRef<HTMLDivElement, ICard>(({ children, variant = 'default', size = 'md', className, ...filteredProps }, ref): JSX.Element => ( <div ref={ref} {...filteredProps} className={clsx({ 'bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none': variant === 'rounded', [sizes[size]]: variant === 'rounded', 'py-4': variant === 'slim', }, className)} > {children} </div> )); interface ICardHeader { backHref?: string onBackClick?: (event: React.MouseEvent) => void className?: string children?: React.ReactNode } /** * Card header container with back button. * Typically holds a CardTitle. */ const CardHeader: React.FC<ICardHeader> = ({ className, children, backHref, onBackClick }): JSX.Element => { const intl = useIntl(); const renderBackButton = () => { if (!backHref && !onBackClick) { return null; } const Comp: React.ElementType = backHref ? Link : 'button'; const backAttributes = backHref ? { to: backHref } : { onClick: onBackClick }; return ( <Comp {...backAttributes} className='rounded-full text-gray-900 focus:ring-2 focus:ring-primary-500 dark:text-gray-100' aria-label={intl.formatMessage(messages.back)}> <SvgIcon src={require('@tabler/icons/arrow-left.svg')} className='h-6 w-6 rtl:rotate-180' /> <span className='sr-only' data-testid='back-button'>{intl.formatMessage(messages.back)}</span> </Comp> ); }; return ( <HStack alignItems='center' space={2} className={className}> {renderBackButton()} {children} </HStack> ); }; interface ICardTitle { title: React.ReactNode } /** A card's title. */ const CardTitle: React.FC<ICardTitle> = ({ title }): JSX.Element => ( <Text size='xl' weight='bold' tag='h1' data-testid='card-title' truncate>{title}</Text> ); interface ICardBody { /** Classnames for the <div> element. */ className?: string /** Children to appear inside the card. */ children: React.ReactNode } /** A card's body. */ const CardBody: React.FC<ICardBody> = ({ className, children }): JSX.Element => ( <div data-testid='card-body' className={className}>{children}</div> ); export { Card, CardHeader, CardTitle, CardBody };