import { Accessibility, cardBehavior, CardBehaviorProps } from '@fluentui/accessibility'; import { ComponentWithAs, getElementType, useAccessibility, useStyles, useFluentContext, useTelemetry, useUnhandledProps, } from '@fluentui/react-bindings'; import { Ref } from '@fluentui/react-component-ref'; import * as CustomPropTypes from '@fluentui/react-proptypes'; import * as _ from 'lodash'; import * as PropTypes from 'prop-types'; import * as React from 'react'; import { ComponentEventHandler, FluentComponentStaticProps } from '../../types'; import { commonPropTypes, createShorthandFactory, SizeValue, UIComponentProps } from '../../utils'; import { CardBody } from './CardBody'; import { CardColumn } from './CardColumn'; import { CardFooter } from './CardFooter'; import { CardHeader } from './CardHeader'; import { CardPreview } from './CardPreview'; import { CardTopControls } from './CardTopControls'; import { CardExpandableBox } from './CardExpandableBox'; export interface CardProps extends UIComponentProps { /** * Accessibility behavior if overridden by the user. */ accessibility?: Accessibility; /** A primary content. */ children?: React.ReactNode; /** * Called on click. * * @param event - React's original SyntheticEvent. * @param data - All props. */ onClick?: ComponentEventHandler; /** A card can be compact, without any padding inside. */ compact?: boolean; /** A card will used horizontal layout. */ horizontal?: boolean; /** Centers content in a card. */ centered?: boolean; /** A card can be sized. */ size?: SizeValue; /** A card can take up the width and height of its container. */ fluid?: boolean; /** A card can show that it cannot be interacted with. */ disabled?: boolean; /** A card can be hiding part of the content and expand on hover/focus. */ expandable?: boolean; /** A card can have elevation styles. */ elevated?: boolean; /** A card can have inverted background styles. */ inverted?: boolean; /** A card can have quiet styles. */ quiet?: boolean; /** A card can show that it is currently selected or not. */ selected?: boolean; } export type CardStylesProps = Pick< CardProps, | 'compact' | 'horizontal' | 'centered' | 'size' | 'fluid' | 'disabled' | 'expandable' | 'elevated' | 'inverted' | 'quiet' | 'selected' > & { actionable: boolean; }; export const cardClassName = 'ui-card'; /** * A Card is used to display data in sematically grouped way. * * @accessibility * By default adds `group` role ([more information available in aria documentation](https://www.w3.org/TR/wai-aria-1.1/#group)), thus it's necessary to provide `aria-roledescription` for correct widget description. [More information available in aria documentation.](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription-property) * When card is actionable (i.e. has `onClick` property), use [cardFocusableBehavior](/components/card/accessibility#card-focusable). [More information available in aria documentation.](https://www.w3.org/TR/wai-aria-practices/#gridNav_focus) * When card contains actionable elements, use [cardChildrenFocusableBehavior](/components/card/accessibility#card-children-focusable). * */ export const Card: ComponentWithAs<'div', CardProps> & FluentComponentStaticProps & { Header: typeof CardHeader; Body: typeof CardBody; Footer: typeof CardFooter; Preview: typeof CardPreview; TopControls: typeof CardPreview; Column: typeof CardColumn; ExpandableBox: typeof CardExpandableBox; } = props => { const context = useFluentContext(); const { setStart, setEnd } = useTelemetry(Card.displayName, context.telemetry); setStart(); const cardRef = React.useRef(); const { className, design, styles, variables, children, compact, horizontal, centered, size, fluid, onClick, disabled, expandable, elevated, inverted, quiet, selected, } = props; const ElementType = getElementType(props); const unhandledProps = useUnhandledProps(Card.handledProps, props); const getA11yProps = useAccessibility(props.accessibility, { debugName: Card.displayName, actionHandlers: { performClick: e => { // prevent Spacebar from scrolling e.preventDefault(); handleClick(e); }, focusCard: e => { cardRef.current.focus(); }, }, mapPropsToBehavior: () => ({ disabled, }), rtl: context.rtl, }); const { classes } = useStyles(Card.displayName, { className: cardClassName, mapPropsToStyles: () => ({ centered, horizontal, compact, size, fluid, actionable: !!onClick, disabled, expandable, elevated, inverted, quiet, selected, }), mapPropsToInlineStyles: () => ({ className, design, styles, variables, }), rtl: context.rtl, }); const handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { if (disabled) { e.preventDefault(); return; } _.invoke(props, 'onClick', e, props); }; const element = ( {getA11yProps.unstable_wrapWithFocusZone( {children} , )} ); setEnd(); return element; }; Card.displayName = 'Card'; Card.propTypes = { ...commonPropTypes.createCommon(), onClick: PropTypes.func, compact: PropTypes.bool, horizontal: PropTypes.bool, centered: PropTypes.bool, size: CustomPropTypes.size, fluid: PropTypes.bool, expandable: PropTypes.bool, disabled: PropTypes.bool, elevated: PropTypes.bool, quiet: PropTypes.bool, inverted: PropTypes.bool, selected: PropTypes.bool, }; Card.defaultProps = { accessibility: cardBehavior, size: 'medium', }; Card.handledProps = Object.keys(Card.propTypes) as any; Card.Header = CardHeader; Card.Body = CardBody; Card.Footer = CardFooter; Card.Preview = CardPreview; Card.TopControls = CardTopControls; Card.Column = CardColumn; Card.ExpandableBox = CardExpandableBox; Card.create = createShorthandFactory({ Component: Card });