import * as React from 'react'; import styles from '@breakaway/react-styles/css/components/Card/card'; import { css } from '@breakaway/react-styles'; import { useOUIAProps, OUIAProps } from '../../helpers'; export interface CardProps extends React.HTMLProps, OUIAProps { /** Content rendered inside the Card */ children?: React.ReactNode; /** ID of the Card. Also passed back in the CardHeader onExpand callback. */ id?: string; /** Additional classes added to the Card */ className?: string; /** Sets the base component to render. defaults to article */ component?: keyof JSX.IntrinsicElements; /** @deprecated to make a card hoverable, use isSelectable or isSelectableRaised. */ isHoverable?: boolean; /** Modifies the card to include compact styling. Should not be used with isLarge. */ isCompact?: boolean; /** Modifies the card to include selectable styling */ isSelectable?: boolean; /** Specifies the card is selectable, and applies the new raised styling on hover and select */ isSelectableRaised?: boolean; /** Modifies the card to include selected styling */ isSelected?: boolean; /** Modifies a raised selectable card to have disabled styling */ isDisabledRaised?: boolean; /** Modifies the card to include flat styling */ isFlat?: boolean; /** Modifies the card to include rounded styling */ isRounded?: boolean; /** Modifies the card to be large. Should not be used with isCompact. */ isLarge?: boolean; /** Cause component to consume the available height of its container */ isFullHeight?: boolean; /** Modifies the card to include plain styling; this removes border and background */ isPlain?: boolean; /** Flag indicating if a card is expanded. Modifies the card to be expandable. */ isExpanded?: boolean; /** Flag indicating that the card should render a hidden input to make it selectable */ hasSelectableInput?: boolean; /** Aria label to apply to the selectable input if one is rendered */ selectableInputAriaLabel?: string; /** Callback that executes when the selectable input is changed */ onSelectableInputChange?: (labelledBy: string, event: React.FormEvent) => void; /** Value to overwrite the randomly generated data-ouia-component-id.*/ ouiaId?: number | string; /** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */ ouiaSafe?: boolean; } interface CardContextProps { cardId: string; registerTitleId: (id: string) => void; isExpanded: boolean; } interface AriaProps { 'aria-label'?: string; 'aria-labelledby'?: string; } export const CardContext = React.createContext>({ cardId: '', registerTitleId: () => {}, isExpanded: false }); export const Card: React.FunctionComponent = ({ children = null, id = '', className = '', component = 'article', isHoverable = false, isCompact = false, isSelectable = false, isSelectableRaised = false, isSelected = false, isDisabledRaised = false, isFlat = false, isExpanded = false, isRounded = false, isLarge = false, isFullHeight = false, isPlain = false, ouiaId, ouiaSafe = true, hasSelectableInput = false, selectableInputAriaLabel, onSelectableInputChange = () => {}, ...props }: CardProps) => { const Component = component as any; const ouiaProps = useOUIAProps(Card.displayName, ouiaId, ouiaSafe); const [titleId, setTitleId] = React.useState(''); const [ariaProps, setAriaProps] = React.useState(); if (isCompact && isLarge) { // eslint-disable-next-line no-console console.warn('Card: Cannot use isCompact with isLarge. Defaulting to isCompact'); isLarge = false; } const getSelectableModifiers = () => { if (isDisabledRaised) { return css(styles.modifiers.nonSelectableRaised); } if (isSelectableRaised) { return css(styles.modifiers.selectableRaised, isSelected && styles.modifiers.selectedRaised); } if (isSelectable || isHoverable) { return css(styles.modifiers.selectable, isSelected && styles.modifiers.selected); } return ''; }; const containsCardTitleChildRef = React.useRef(false); const registerTitleId = (id: string) => { setTitleId(id); containsCardTitleChildRef.current = !!id; }; React.useEffect(() => { if (selectableInputAriaLabel) { setAriaProps({ 'aria-label': selectableInputAriaLabel }); } else if (titleId) { setAriaProps({ 'aria-labelledby': titleId }); } else if (hasSelectableInput && !containsCardTitleChildRef.current) { setAriaProps({}); // eslint-disable-next-line no-console console.warn( 'If no CardTitle component is passed as a child of Card the selectableInputAriaLabel prop must be passed' ); } }, [hasSelectableInput, selectableInputAriaLabel, titleId]); return ( {hasSelectableInput && ( onSelectableInputChange(id, event)} disabled={isDisabledRaised} tabIndex={-1} /> )} {children} ); }; Card.displayName = 'Card';