diff --git a/CHANGELOG.md b/CHANGELOG.md index e77a19614a..213690e61b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `mode` property from `focusZone` configuration in accessibility behaviors is no longer supported - the focus zone will always be in embed mode @layershifter ([#2265](https://github.com/microsoft/fluent-ui-react/pull/2265)) - `FocusZoneMode` and `FOCUSZONE_WRAP_ATTRIBUTE` are no longer exported @layershifter ([#2265](https://github.com/microsoft/fluent-ui-react/pull/2265)) - Returned function from `useAccessibility` no longer keeps the same reference @layershifter ([#2268](https://github.com/microsoft/fluent-ui-react/pull/2268)) -- Changed `avatarBorderWidth` and `statusBorderWidth` avatar variables types from number to string and updated styles in Teams theme @mnajdova ([#2238](https://github.com/microsoft/fluent-ui-react/pull/2238)) - Add logic for mounting/removing elements when they are shown/hidden using the `Animation` component; `Animation` component is not rendering element anymore, just applying classes to it's children @mnajdova ([#2115](https://github.com/microsoft/fluent-ui-react/pull/2115)) +- Restricted prop set in the `Button`, `Avatar`, `Box` and `Image` styles; changed `avatarBorderWidth` and `statusBorderWidth` avatar variables types from number to string and updated styles in Teams theme @mnajdova ([#2238](https://github.com/microsoft/fluent-ui-react/pull/2238)) +- Restricted prop set in the `List` & `ListItem` @layershifter ([#2238](https://github.com/microsoft/fluent-ui-react/pull/2238)) ### Fixes - Fix event listener leak in `FocusZone` @miroslavstastny ([#2227](https://github.com/microsoft/fluent-ui-react/pull/2227)) diff --git a/packages/accessibility/src/behaviors/index.ts b/packages/accessibility/src/behaviors/index.ts index fb2a0b1dca..9d5a887a42 100644 --- a/packages/accessibility/src/behaviors/index.ts +++ b/packages/accessibility/src/behaviors/index.ts @@ -9,14 +9,21 @@ export { default as menuBehavior } from './Menu/menuBehavior' export { default as menuItemBehavior } from './Menu/menuItemBehavior' export { default as menuDividerBehavior } from './Menu/menuDividerBehavior' export { default as submenuBehavior } from './Menu/submenuBehavior' + export { default as basicListBehavior } from './List/listBehavior' export { default as basicListItemBehavior } from './List/basicListItemBehavior' + export { default as listBehavior } from './List/listBehavior' +export * from './List/listBehavior' export { default as listItemBehavior } from './List/listItemBehavior' +export * from './List/listItemBehavior' + export { default as navigableListBehavior } from './List/navigableListBehavior' export { default as navigableListItemBehavior } from './List/navigableListItemBehavior' + export { default as selectableListBehavior } from './List/selectableListBehavior' export { default as selectableListItemBehavior } from './List/selectableListItemBehavior' + export { default as loaderBehavior } from './Loader/loaderBehavior' export { default as inputBehavior } from './Input/inputBehavior' export { default as iconBehavior } from './Icon/iconBehavior' diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 9e0d25c706..10df6c5e41 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -1,36 +1,42 @@ -import { Accessibility, listBehavior } from '@fluentui/accessibility' +import { Accessibility, listBehavior, ListBehaviorProps } from '@fluentui/accessibility' +import { + getElementType, + getUnhandledProps, + useAccessibility, + useStateManager, + useStyles, + useTelemetry, +} from '@fluentui/react-bindings' +import { createListManager } from '@fluentui/state' import * as customPropTypes from '@fluentui/react-proptypes' import * as _ from 'lodash' -import * as React from 'react' import * as PropTypes from 'prop-types' +import * as React from 'react' +// @ts-ignore +import { ThemeContext } from 'react-fela' +import { + WithAsProp, + ComponentEventHandler, + withSafeTypeForAs, + ShorthandCollection, + ReactChildren, + ProviderContextPrepared, + FluentComponentStaticProps, +} from '../../types' import { childrenExist, - AutoControlledComponent, UIComponentProps, ChildrenComponentProps, commonPropTypes, rtlTextContainer, - applyAccessibilityKeyHandlers, createShorthandFactory, - ShorthandFactory, } from '../../utils' import ListItem, { ListItemProps } from './ListItem' -import { - WithAsProp, - ComponentEventHandler, - withSafeTypeForAs, - ShorthandCollection, - ReactChildren, -} from '../../types' - -export interface ListSlotClassNames { - item: string -} export interface ListProps extends UIComponentProps, ChildrenComponentProps { /** Accessibility behavior if overridden by the user. */ - accessibility?: Accessibility + accessibility?: Accessibility /** Toggle debug mode */ debug?: boolean @@ -70,122 +76,142 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps { wrap?: (children: ReactChildren) => React.ReactNode } -export interface ListState { - selectedIndex?: number -} +// List props that are passed to each individual Item props +const itemProps = [ + 'debug', + 'selectable', + 'navigable', + 'truncateContent', + 'truncateHeader', + 'variables', +] + +const List: React.FC> & + FluentComponentStaticProps & { + Item: typeof ListItem + } = props => { + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const { setStart, setEnd } = useTelemetry(List.displayName, context.telemetry) + setStart() + + const { + accessibility, + as, + children, + className, + debug, + defaultSelectedIndex, + design, + horizontal, + navigable, + items, + selectable, + selectedIndex, + styles, + variables, + wrap, + } = props + + const { state, actions } = useStateManager(createListManager, { + mapPropsToInitialState: () => ({ selectedIndex: defaultSelectedIndex }), + mapPropsToState: () => ({ selectedIndex }), + }) + const getA11Props = useAccessibility(accessibility, { + debugName: List.displayName, + mapPropsToBehavior: () => ({ + horizontal, + navigable, + selectable, + }), + rtl: context.rtl, + }) + const { classes } = useStyles(List.displayName, { + className: List.className, + mapPropsToStyles: () => ({ isListTag: as === 'ol' || as === 'ul', debug, horizontal }), + mapPropsToInlineStyles: () => ({ className, design, styles, variables }), + rtl: context.rtl, + }) -class List extends AutoControlledComponent, ListState> { - static displayName = 'List' + const ElementType = getElementType(props) + const unhandledProps = getUnhandledProps(List.handledProps, props) - static className = 'ui-list' + const hasContent = childrenExist(children) || (items && items.length > 0) - static slotClassNames: ListSlotClassNames = { - item: `${List.className}__item`, - } + const handleItemOverrides = (predefinedProps: ListItemProps) => ({ + onClick: (e: React.SyntheticEvent, itemProps: ListItemProps) => { + _.invoke(predefinedProps, 'onClick', e, itemProps) - static propTypes = { - ...commonPropTypes.createCommon({ - content: false, - }), - debug: PropTypes.bool, - items: customPropTypes.collectionShorthand, - selectable: customPropTypes.every([customPropTypes.disallow(['navigable']), PropTypes.bool]), - navigable: customPropTypes.every([customPropTypes.disallow(['selectable']), PropTypes.bool]), - truncateContent: PropTypes.bool, - truncateHeader: PropTypes.bool, - selectedIndex: PropTypes.number, - defaultSelectedIndex: PropTypes.number, - onSelectedIndexChange: PropTypes.func, - horizontal: PropTypes.bool, - wrap: PropTypes.func, - } - - static defaultProps = { - as: 'ul', - accessibility: listBehavior as Accessibility, - wrap: children => children, - } - - static autoControlledProps = ['selectedIndex'] - - getInitialAutoControlledState() { - return { selectedIndex: -1 } - } - - static Item = ListItem - - // List props that are passed to each individual Item props - static itemProps = [ - 'debug', - 'selectable', - 'navigable', - 'truncateContent', - 'truncateHeader', - 'variables', - ] - - static create: ShorthandFactory - - handleItemOverrides = (predefinedProps: ListItemProps) => { - const { selectable } = this.props - - return { - onClick: (e: React.SyntheticEvent, itemProps: ListItemProps) => { - _.invoke(predefinedProps, 'onClick', e, itemProps) - - if (selectable) { - this.setState({ selectedIndex: itemProps.index }) - _.invoke(this.props, 'onSelectedIndexChange', e, { - ...this.props, - ...{ selectedIndex: itemProps.index }, - }) - } - }, - } - } - - renderComponent({ ElementType, classes, accessibility, unhandledProps }) { - const { children, items, wrap } = this.props - const hasContent = childrenExist(children) || (items && items.length > 0) - - return ( - - {hasContent && wrap(childrenExist(children) ? children : this.renderItems())} - - ) - } - - renderItems() { - const { items, selectable } = this.props - const { selectedIndex } = this.state - - return _.map(items, (item, index) => { + if (selectable) { + actions.select(itemProps.index) + _.invoke(props, 'onSelectedIndexChange', e, { + ...props, + ...{ selectedIndex: itemProps.index }, + }) + } + }, + }) + + const renderItems = () => + _.map(items, (item, index) => { const maybeSelectableItemProps = {} as any if (selectable) { - maybeSelectableItemProps.selected = index === selectedIndex + maybeSelectableItemProps.selected = index === state.selectedIndex } - const itemProps = () => ({ - className: List.slotClassNames.item, - ..._.pick(this.props, List.itemProps), - ...maybeSelectableItemProps, - index, - }) - return ListItem.create(item, { - defaultProps: itemProps, - overrideProps: this.handleItemOverrides, + defaultProps: () => ({ + ..._.pick(props, itemProps), + ...maybeSelectableItemProps, + index, + }), + overrideProps: handleItemOverrides, }) }) - } + + const element = getA11Props.unstable_wrapWithFocusZone( + + {hasContent && wrap(childrenExist(children) ? children : renderItems())} + , + ) + setEnd() + + return element +} + +List.className = 'ui-list' +List.displayName = 'List' + +List.defaultProps = { + as: 'ul', + accessibility: listBehavior, + wrap: children => children, } +List.propTypes = { + ...commonPropTypes.createCommon({ + content: false, + }), + debug: PropTypes.bool, + items: customPropTypes.collectionShorthand, + selectable: customPropTypes.every([customPropTypes.disallow(['navigable']), PropTypes.bool]), + navigable: customPropTypes.every([customPropTypes.disallow(['selectable']), PropTypes.bool]), + truncateContent: PropTypes.bool, + truncateHeader: PropTypes.bool, + selectedIndex: PropTypes.number, + defaultSelectedIndex: PropTypes.number, + onSelectedIndexChange: PropTypes.func, + horizontal: PropTypes.bool, + wrap: PropTypes.func, +} + +List.handledProps = Object.keys(List.propTypes) as any +List.Item = ListItem List.create = createShorthandFactory({ Component: List, mappedArrayProp: 'items' }) diff --git a/packages/react/src/components/List/ListItem.tsx b/packages/react/src/components/List/ListItem.tsx index 84824d4f0f..6af5e1aed2 100644 --- a/packages/react/src/components/List/ListItem.tsx +++ b/packages/react/src/components/List/ListItem.tsx @@ -1,19 +1,33 @@ -import { Accessibility, listItemBehavior } from '@fluentui/accessibility' +import { Accessibility, listItemBehavior, ListItemBehaviorProps } from '@fluentui/accessibility' +import { + getElementType, + getUnhandledProps, + useAccessibility, + useStyles, + useTelemetry, +} from '@fluentui/react-bindings' import cx from 'classnames' -import * as React from 'react' import * as _ from 'lodash' import * as PropTypes from 'prop-types' +import * as React from 'react' +// @ts-ignore +import { ThemeContext } from 'react-fela' + +import Box, { BoxProps } from '../Box/Box' +import { + ShorthandValue, + WithAsProp, + ComponentEventHandler, + withSafeTypeForAs, + ProviderContextPrepared, + FluentComponentStaticProps, +} from '../../types' import { createShorthandFactory, - UIComponent, UIComponentProps, commonPropTypes, ContentComponentProps, - applyAccessibilityKeyHandlers, - ShorthandFactory, } from '../../utils' -import { ShorthandValue, WithAsProp, ComponentEventHandler, withSafeTypeForAs } from '../../types' -import Box, { BoxProps } from '../Box/Box' export interface ListItemSlotClassNames { header: string @@ -31,7 +45,7 @@ export interface ListItemProps extends UIComponentProps, ContentComponentProps> { /** Accessibility behavior if overridden by the user. */ - accessibility?: Accessibility + accessibility?: Accessibility contentMedia?: ShorthandValue /** Toggle debug mode. */ debug?: boolean @@ -63,130 +77,185 @@ export interface ListItemProps onClick?: ComponentEventHandler } -class ListItem extends UIComponent> { - static create: ShorthandFactory +const ListItem: React.FC & { index: number }> & + FluentComponentStaticProps & { + slotClassNames: ListItemSlotClassNames + } = props => { + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const { setStart, setEnd } = useTelemetry(ListItem.displayName, context.telemetry) - static displayName = 'ListItem' + setStart() - static className = 'ui-list__item' + const { + accessibility, + className, + content, + contentMedia, + design, + endMedia, + header, + important, + headerMedia, + media, + styles, + debug, + navigable, + selectable, + selected, + truncateContent, + truncateHeader, + variables, + } = props - static slotClassNames: ListItemSlotClassNames + 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: ListItem.className, + mapPropsToStyles: () => ({ + debug, + navigable, + important, + selectable, + selected, + truncateContent, + truncateHeader, - static propTypes = { - ...commonPropTypes.createCommon({ - content: false, + hasContent: !!content, + hasContentMedia: !!contentMedia, + hasHeader: !!header, + hasHeaderMedia: !!headerMedia, }), - contentMedia: PropTypes.any, - content: PropTypes.any, + mapPropsToInlineStyles: () => ({ className, design, styles, variables }), + rtl: context.rtl, + }) - debug: PropTypes.bool, + const ElementType = getElementType(props) + const unhandledProps = getUnhandledProps(ListItem.handledProps, props) - header: PropTypes.any, - endMedia: PropTypes.any, - headerMedia: PropTypes.any, + const handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { + _.invoke(props, 'onClick', e, props) + } - important: PropTypes.bool, - media: PropTypes.any, + const contentElement = Box.create(content, { + defaultProps: () => ({ + className: ListItem.slotClassNames.content, + styles: resolvedStyles.content, + }), + }) + const contentMediaElement = Box.create(contentMedia, { + defaultProps: () => ({ + className: ListItem.slotClassNames.contentMedia, + styles: resolvedStyles.contentMedia, + }), + }) + const headerElement = Box.create(header, { + defaultProps: () => ({ + className: ListItem.slotClassNames.header, + styles: resolvedStyles.header, + }), + }) + const headerMediaElement = Box.create(headerMedia, { + defaultProps: () => ({ + className: ListItem.slotClassNames.headerMedia, + styles: resolvedStyles.headerMedia, + }), + }) + const endMediaElement = Box.create(endMedia, { + defaultProps: () => ({ + className: ListItem.slotClassNames.endMedia, + styles: resolvedStyles.endMedia, + }), + }) + const mediaElement = Box.create(media, { + defaultProps: () => ({ + className: ListItem.slotClassNames.media, + styles: resolvedStyles.media, + }), + }) - selectable: PropTypes.bool, - navigable: PropTypes.bool, - index: PropTypes.number, - selected: PropTypes.bool, + const element = ( + + {mediaElement} - truncateContent: PropTypes.bool, - truncateHeader: PropTypes.bool, +
+ {(headerElement || headerMediaElement) && ( +
+ {headerElement} + {headerMediaElement} +
+ )} + {(contentElement || contentMediaElement) && ( +
+ {contentElement} + {contentMediaElement} +
+ )} +
- onClick: PropTypes.func, - } + {endMediaElement} +
+ ) - static defaultProps = { - as: 'li', - accessibility: listItemBehavior as Accessibility, - } + setEnd() - actionHandlers = { - performClick: event => { - this.handleClick(event) - event.preventDefault() - }, - } + return element +} - handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { - _.invoke(this.props, 'onClick', e, this.props) - } +ListItem.className = 'ui-list__item' +ListItem.displayName = 'ListItem' - renderComponent({ ElementType, classes, accessibility, unhandledProps, styles }) { - const { endMedia, media, content, contentMedia, header, headerMedia } = this.props - - const contentElement = Box.create(content, { - defaultProps: () => ({ - className: ListItem.slotClassNames.content, - styles: styles.content, - }), - }) - const contentMediaElement = Box.create(contentMedia, { - defaultProps: () => ({ - className: ListItem.slotClassNames.contentMedia, - styles: styles.contentMedia, - }), - }) - const headerElement = Box.create(header, { - defaultProps: () => ({ - className: ListItem.slotClassNames.header, - styles: styles.header, - }), - }) - const headerMediaElement = Box.create(headerMedia, { - defaultProps: () => ({ - className: ListItem.slotClassNames.headerMedia, - styles: styles.headerMedia, - }), - }) - const endMediaElement = Box.create(endMedia, { - defaultProps: () => ({ - className: ListItem.slotClassNames.endMedia, - styles: styles.endMedia, - }), - }) - const mediaElement = Box.create(media, { - defaultProps: () => ({ - className: ListItem.slotClassNames.media, - styles: styles.media, - }), - }) - - return ( - - {mediaElement} - -
- {(headerElement || headerMediaElement) && ( -
- {headerElement} - {headerMediaElement} -
- )} - {(contentElement || contentMediaElement) && ( -
- {contentElement} - {contentMediaElement} -
- )} -
- - {endMediaElement} -
- ) - } +ListItem.defaultProps = { + as: 'li', + accessibility: listItemBehavior, } -ListItem.create = createShorthandFactory({ Component: ListItem, mappedProp: 'content' }) +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.slotClassNames = { header: `${ListItem.className}__header`, headerMedia: `${ListItem.className}__headerMedia`, @@ -199,6 +268,8 @@ ListItem.slotClassNames = { endMedia: `${ListItem.className}__endMedia`, } +ListItem.create = createShorthandFactory({ Component: ListItem, mappedProp: 'content' }) + /** * A ListItem contains a single piece of content within a List. */ diff --git a/packages/react/src/themes/teams/components/List/listItemStyles.ts b/packages/react/src/themes/teams/components/List/listItemStyles.ts index 6f7f5895c5..a013736dd1 100644 --- a/packages/react/src/themes/teams/components/List/listItemStyles.ts +++ b/packages/react/src/themes/teams/components/List/listItemStyles.ts @@ -3,6 +3,23 @@ import { screenReaderContainerStyles } from '../../../../utils/accessibility/Sty import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '@fluentui/styles' import { default as ListItem, ListItemProps } from '../../../../components/List/ListItem' import getBorderFocusStyles from '../../getBorderFocusStyles' +import { ListItemVariables } from './listItemVariables' + +export type ListItemStylesProps = Pick< + ListItemProps, + | 'debug' + | 'important' + | 'navigable' + | 'selectable' + | 'selected' + | 'truncateContent' + | 'truncateHeader' +> & { + hasContent?: boolean + hasContentMedia?: boolean + hasHeader?: boolean + hasHeaderMedia?: boolean +} const truncateStyle: ICSSInJSStyle = { overflow: 'hidden', @@ -10,7 +27,7 @@ const truncateStyle: ICSSInJSStyle = { whiteSpace: 'nowrap', } -const selectableHoverStyle = (p: ListItemProps, v): ICSSInJSStyle => ({ +const selectableHoverStyle = (p: ListItemStylesProps, v): ICSSInJSStyle => ({ background: v.selectableFocusHoverBackgroundColor, color: v.selectableFocusHoverColor, cursor: 'pointer', @@ -34,7 +51,7 @@ const selectedStyle = variables => ({ color: variables.selectedColor, }) -const listItemStyles: ComponentSlotStylesPrepared = { +const listItemStyles: ComponentSlotStylesPrepared = { root: ({ props: p, variables: v, theme: { siteVariables } }): ICSSInJSStyle => { const borderFocusStyles = getBorderFocusStyles({ siteVariables, @@ -77,7 +94,7 @@ const listItemStyles: ComponentSlotStylesPrepared = { background: '#000', }, }), - ...((p.header || p.content) && { + ...((p.hasHeader || p.hasContent) && { marginRight: pxToRem(8), }), }), @@ -88,7 +105,7 @@ const listItemStyles: ComponentSlotStylesPrepared = { lineHeight: v.headerLineHeight, ...(p.truncateHeader && truncateStyle), - ...((!p.content || p.headerMedia) && { + ...((!p.hasContent || p.hasHeaderMedia) && { marginRight: pxToRem(8), }), }), @@ -106,12 +123,12 @@ const listItemStyles: ComponentSlotStylesPrepared = { lineHeight: v.contentLineHeight, ...(p.truncateContent && truncateStyle), - ...((!p.header || p.contentMedia) && { + ...((!p.hasHeader || p.hasContentMedia) && { marginRight: pxToRem(8), }), }), - contentMedia: ({ props: p, variables: v }) => ({ + contentMedia: ({ variables: v }) => ({ fontSize: v.contentMediaFontSize, lineHeight: v.contentMediaLineHeight, }), diff --git a/packages/react/src/themes/teams/components/List/listStyles.ts b/packages/react/src/themes/teams/components/List/listStyles.ts index 7d52fa2e0c..3fbf78429c 100644 --- a/packages/react/src/themes/teams/components/List/listStyles.ts +++ b/packages/react/src/themes/teams/components/List/listStyles.ts @@ -1,13 +1,14 @@ import { debugRoot } from '../../../../styles/debugStyles' import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '@fluentui/styles' import { ListProps } from '../../../../components/List/List' -import { WithAsProp } from '../../../../types' -const listStyles: ComponentSlotStylesPrepared> = { +export type ListStylesProps = Pick & { isListTag: boolean } + +const listStyles: ComponentSlotStylesPrepared = { root: ({ props: p }): ICSSInJSStyle => ({ ...(p.debug && debugRoot()), display: p.horizontal ? 'inline-flex' : 'block', - ...((p.as === 'ul' || p.as === 'ol') && { + ...(p.isListTag && { listStyle: 'none', padding: 0, margin: 0, diff --git a/packages/react/src/themes/teams/types.ts b/packages/react/src/themes/teams/types.ts index a93507b1db..c5c9615bf5 100644 --- a/packages/react/src/themes/teams/types.ts +++ b/packages/react/src/themes/teams/types.ts @@ -36,8 +36,8 @@ import { InputProps } from '../../components/Input/Input' import { ItemLayoutProps } from '../../components/ItemLayout/ItemLayout' import { LabelProps } from '../../components/Label/Label' import { LayoutProps } from '../../components/Layout/Layout' -import { ListItemProps } from '../../components/List/ListItem' -import { ListProps } from '../../components/List/List' +import { ListStylesProps } from './components/List/listStyles' +import { ListItemStylesProps } from './components/List/listItemStyles' import { LoaderProps } from '../../components/Loader/Loader' import { MenuItemProps } from '../../components/Menu/MenuItem' import { MenuProps } from '../../components/Menu/Menu' @@ -93,8 +93,8 @@ export type TeamsThemeStylesProps = { ItemLayout?: ItemLayoutProps Label?: LabelProps Layout?: LayoutProps - List?: ListProps - ListItem?: ListItemProps + List?: ListStylesProps + ListItem?: ListItemStylesProps Loader?: LoaderProps Menu?: MenuProps MenuItem?: MenuItemProps diff --git a/packages/react/test/specs/components/List/List-test.tsx b/packages/react/test/specs/components/List/List-test.tsx index c4916f31b4..98f00b23cd 100644 --- a/packages/react/test/specs/components/List/List-test.tsx +++ b/packages/react/test/specs/components/List/List-test.tsx @@ -10,7 +10,9 @@ import ListItem, { ListItemProps } from 'src/components/List/ListItem' const listImplementsCollectionShorthandProp = implementsCollectionShorthandProp(List) describe('List', () => { - isConformant(List) + isConformant(List, { + constructorName: 'List', + }) handlesAccessibility(List, { defaultRootRole: 'list' }) listImplementsCollectionShorthandProp('items', ListItem, { mapsValueToProp: 'content' }) diff --git a/packages/react/test/specs/components/List/ListItem-test.tsx b/packages/react/test/specs/components/List/ListItem-test.tsx index 3181238710..b9a7c4e4e4 100644 --- a/packages/react/test/specs/components/List/ListItem-test.tsx +++ b/packages/react/test/specs/components/List/ListItem-test.tsx @@ -7,7 +7,9 @@ import { mountWithProvider } from 'test/utils' import ListItem from 'src/components/List/ListItem' describe('ListItem', () => { - isConformant(ListItem) + isConformant(ListItem, { + constructorName: 'ListItem', + }) handlesAccessibility(ListItem, { defaultRootRole: 'listitem' }) test('handleClick is executed when Enter is pressed for selectable list', () => { diff --git a/packages/state/src/index.ts b/packages/state/src/index.ts index 9085e4a1b9..72dd19ab3e 100644 --- a/packages/state/src/index.ts +++ b/packages/state/src/index.ts @@ -1,5 +1,6 @@ export * from './managers/dialogManager' export * from './managers/dropdownManager' +export * from './managers/listManager' export { default as createManager } from './createManager' export * from './types' diff --git a/packages/state/src/managers/listManager.ts b/packages/state/src/managers/listManager.ts new file mode 100644 index 0000000000..4ff9c73926 --- /dev/null +++ b/packages/state/src/managers/listManager.ts @@ -0,0 +1,26 @@ +import createManager from '../createManager' +import { Manager, ManagerConfig } from '../types' + +export type ListState = { + selectedIndex?: number +} + +export type ListActions = { + select: (index: number) => void +} + +export type ListManager = Manager + +export const createListManager = ( + config: Partial> = {}, +): ListManager => + createManager({ + ...config, + actions: { + select: index => () => ({ selectedIndex: index }), + }, + state: { + selectedIndex: -1, + ...config.state, + }, + })