import { Accessibility, listItemBehavior, ListItemBehaviorProps } from '@fluentui/accessibility'; import { ComponentWithAs, getElementType, useUnhandledProps, useAccessibility, useFluentContext, useStyles, useTelemetry, } from '@fluentui/react-bindings'; import { useContextSelectors } from '@fluentui/react-context-selector'; import cx from 'classnames'; import * as _ from 'lodash'; import * as PropTypes from 'prop-types'; import * as React from 'react'; import { Box, BoxProps } from '../Box/Box'; import { ShorthandValue, ComponentEventHandler, FluentComponentStaticProps } from '../../types'; import { createShorthandFactory, UIComponentProps, commonPropTypes, ContentComponentProps } from '../../utils'; import { ListContext, ListContextSubscribedValue } from './listContext'; export interface ListItemSlotClassNames { header: string; headerMedia: string; headerWrapper: string; content: string; contentMedia: string; contentWrapper: string; main: string; media: string; endMedia: string; } export interface ListItemProps extends UIComponentProps, ContentComponentProps> { /** Accessibility behavior if overridden by the user. */ accessibility?: Accessibility; contentMedia?: ShorthandValue; /** Toggle debug mode. */ debug?: boolean; header?: ShorthandValue; endMedia?: ShorthandValue; headerMedia?: ShorthandValue; /** A list item can appear more important and draw the user's attention. */ important?: boolean; media?: ShorthandValue; index?: number; /** A list item can indicate that it can be selected. */ selectable?: boolean; /** A list item can indicate that it can be navigable. */ navigable?: boolean; /** Indicates if the current list item is selected. */ selected?: boolean; truncateContent?: boolean; truncateHeader?: boolean; /** * Called on click. * * @param event - React's original SyntheticEvent. * @param data - All props. */ onClick?: ComponentEventHandler; } export type ListItemStylesProps = Pick< ListItemProps, 'debug' | 'important' | 'navigable' | 'selectable' | 'selected' | 'truncateContent' | 'truncateHeader' > & { hasContent?: boolean; hasContentMedia?: boolean; hasHeader?: boolean; hasHeaderMedia?: boolean; }; export const listItemClassName = 'ui-list__item'; export const listItemSlotClassNames: ListItemSlotClassNames = { header: `${listItemClassName}__header`, headerMedia: `${listItemClassName}__headerMedia`, headerWrapper: `${listItemClassName}__headerWrapper`, main: `${listItemClassName}__main`, content: `${listItemClassName}__content`, contentMedia: `${listItemClassName}__contentMedia`, contentWrapper: `${listItemClassName}__contentWrapper`, media: `${listItemClassName}__media`, endMedia: `${listItemClassName}__endMedia`, }; /** * A ListItem contains a single piece of content within a List. */ export const ListItem: ComponentWithAs<'li', ListItemProps & { index: number }> & FluentComponentStaticProps = props => { const context = useFluentContext(); const { setStart, setEnd } = useTelemetry(ListItem.displayName, context.telemetry); setStart(); const { accessibility, className, content, contentMedia, design, endMedia, header, important, headerMedia, media, styles, } = props; const parentProps: ListContextSubscribedValue = useContextSelectors(ListContext, { debug: v => v.debug, navigable: v => v.navigable, selectable: v => v.selectable, truncateContent: v => v.truncateContent, truncateHeader: v => v.truncateHeader, variables: v => v.variables, onItemClick: v => v.onItemClick, selected: v => v.selectedIndex === props.index, }); const { debug = parentProps.debug, navigable = parentProps.navigable, selectable = parentProps.selectable, selected = parentProps.selected, truncateContent = parentProps.truncateContent, truncateHeader = parentProps.truncateHeader, variables = parentProps.variables, } = props; const getA11Props = useAccessibility(accessibility, { debugName: ListItem.displayName, actionHandlers: { performClick: e => { e.preventDefault(); handleClick(e); }, }, mapPropsToBehavior: () => ({ navigable, selectable, selected, }), rtl: context.rtl, }); const { classes, styles: resolvedStyles } = useStyles(ListItem.displayName, { className: listItemClassName, mapPropsToStyles: () => ({ debug, navigable, important, selectable, selected, truncateContent, truncateHeader, hasContent: !!content, hasContentMedia: !!contentMedia, hasHeader: !!header, hasHeaderMedia: !!headerMedia, }), mapPropsToInlineStyles: () => ({ className, design, styles, variables }), rtl: context.rtl, }); const ElementType = getElementType(props); const unhandledProps = useUnhandledProps(ListItem.handledProps, props); const handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { _.invoke(props, 'onClick', e, props); parentProps.onItemClick(e, props.index); }; const contentElement = Box.create(content, { defaultProps: () => ({ className: listItemSlotClassNames.content, styles: resolvedStyles.content, }), }); const contentMediaElement = Box.create(contentMedia, { defaultProps: () => ({ className: listItemSlotClassNames.contentMedia, styles: resolvedStyles.contentMedia, }), }); const headerElement = Box.create(header, { defaultProps: () => ({ className: listItemSlotClassNames.header, styles: resolvedStyles.header, }), }); const headerMediaElement = Box.create(headerMedia, { defaultProps: () => ({ className: listItemSlotClassNames.headerMedia, styles: resolvedStyles.headerMedia, }), }); const endMediaElement = Box.create(endMedia, { defaultProps: () => ({ className: listItemSlotClassNames.endMedia, styles: resolvedStyles.endMedia, }), }); const mediaElement = Box.create(media, { defaultProps: () => ({ className: listItemSlotClassNames.media, styles: resolvedStyles.media, }), }); const element = getA11Props.unstable_wrapWithFocusZone( {mediaElement}
{(headerElement || headerMediaElement) && (
{headerElement} {headerMediaElement}
)} {(contentElement || contentMediaElement) && (
{contentElement} {contentMediaElement}
)}
{endMediaElement}
, ); setEnd(); return element; }; ListItem.displayName = 'ListItem'; ListItem.defaultProps = { as: 'li', accessibility: listItemBehavior, }; ListItem.propTypes = { ...commonPropTypes.createCommon({ content: false, }), contentMedia: PropTypes.any, content: PropTypes.any, debug: PropTypes.bool, header: PropTypes.any, endMedia: PropTypes.any, headerMedia: PropTypes.any, important: PropTypes.bool, media: PropTypes.any, selectable: PropTypes.bool, navigable: PropTypes.bool, index: PropTypes.number, selected: PropTypes.bool, truncateContent: PropTypes.bool, truncateHeader: PropTypes.bool, onClick: PropTypes.func, }; ListItem.handledProps = Object.keys(ListItem.propTypes) as any; ListItem.create = createShorthandFactory({ Component: ListItem, mappedProp: 'content' });