From a92d1da256bfc86cf40659822805873ea9837c15 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 3 Jan 2020 17:05:15 +0100 Subject: [PATCH 01/16] chore(List): move to hooks --- packages/accessibility/src/behaviors/index.ts | 7 + packages/react/src/components/List/List.tsx | 260 +++++++-------- .../react/src/components/List/ListItem.tsx | 306 +++++++++++------- .../teams/components/List/listItemStyles.ts | 29 +- packages/state/src/index.ts | 2 + packages/state/src/managers/listManager.ts | 26 ++ 6 files changed, 383 insertions(+), 247 deletions(-) create mode 100644 packages/state/src/managers/listManager.ts diff --git a/packages/accessibility/src/behaviors/index.ts b/packages/accessibility/src/behaviors/index.ts index a8584fe8dc..3b8bae18f0 100644 --- a/packages/accessibility/src/behaviors/index.ts +++ b/packages/accessibility/src/behaviors/index.ts @@ -8,14 +8,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..37bfb899e3 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, +} 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, +} from '../../types' import { childrenExist, - AutoControlledComponent, UIComponentProps, ChildrenComponentProps, commonPropTypes, rtlTextContainer, - applyAccessibilityKeyHandlers, createShorthandFactory, ShorthandFactory, } from '../../utils' +import { Provider, ListContextValue } from './context' 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,122 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps { wrap?: (children: ReactChildren) => React.ReactNode } -export interface ListState { - selectedIndex?: number -} - -class List extends AutoControlledComponent, ListState> { - static displayName = 'List' - - static className = 'ui-list' - - static slotClassNames: ListSlotClassNames = { - item: `${List.className}__item`, - } - - 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())} - - ) +const List: React.FC> & { + className: string + create: ShorthandFactory + handledProps: string[] + + Item: typeof ListItem +} = props => { + const { + accessibility, + as, + children, + className, + debug, + defaultSelectedIndex, + design, + horizontal, + items, + navigable, + selectable, + selectedIndex, + styles, + truncateContent, + truncateHeader, + variables, + wrap, + } = props + + const context: ProviderContextPrepared = React.useContext(ThemeContext) + + const { state, actions } = useStateManager(createListManager, { + mapPropsToInitialState: () => ({ selectedIndex: defaultSelectedIndex }), + mapPropsToState: () => ({ selectedIndex }), + }) + const getA11Props = useAccessibility(accessibility, { + debugName: List.displayName, + mapPropsToBehavior: () => ({}), + rtl: context.rtl, + }) + const { classes } = useStyles(List.displayName, { + className: List.className, + mapPropsToStyles: () => ({ as: String(as), debug, horizontal }), + mapPropsToInlineStyles: () => ({ className, design, styles, variables }), + rtl: context.rtl, + }) + + const ElementType = getElementType(props) + const unhandledProps = getUnhandledProps(List.handledProps as any, props) + + const hasContent = childrenExist(children) || (items && items.length > 0) + const value: ListContextValue = { + debug, + navigable, + selectable, + selectedIndex: state.selectedIndex, + truncateContent, + truncateHeader, + variables, + onItemClick: (e, itemIndex) => { + if (selectable) { + actions.select(itemIndex) + _.invoke(props, 'onSelectedIndexChange', e, { ...props, selectedIndex: itemIndex }) + } + }, } - renderItems() { - const { items, selectable } = this.props - const { selectedIndex } = this.state + return ( + + + {hasContent && + wrap( + childrenExist(children) + ? children + : _.map(items, (item, index) => + ListItem.create(item, { + defaultProps: () => ({ index }), + }), + ), + )} + + + ) +} - return _.map(items, (item, index) => { - const maybeSelectableItemProps = {} as any +List.className = 'ui-list' +List.displayName = 'List' - if (selectable) { - maybeSelectableItemProps.selected = index === selectedIndex - } - - const itemProps = () => ({ - className: List.slotClassNames.item, - ..._.pick(this.props, List.itemProps), - ...maybeSelectableItemProps, - index, - }) - - return ListItem.create(item, { - defaultProps: itemProps, - overrideProps: this.handleItemOverrides, - }) - }) - } +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) +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..eb9638b524 100644 --- a/packages/react/src/components/List/ListItem.tsx +++ b/packages/react/src/components/List/ListItem.tsx @@ -1,19 +1,34 @@ -import { Accessibility, listItemBehavior } from '@fluentui/accessibility' +import { Accessibility, listItemBehavior, ListItemBehaviorProps } from '@fluentui/accessibility' +import { + getElementType, + getUnhandledProps, + useAccessibility, + useStyles, +} from '@fluentui/react-bindings' +import { useContextSelectors } from '@fluentui/react-context-selector' 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, +} 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' +import { ListContext } from './context' export interface ListItemSlotClassNames { header: string @@ -31,7 +46,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 +78,191 @@ export interface ListItemProps onClick?: ComponentEventHandler } -class ListItem extends UIComponent> { - static create: ShorthandFactory +const ListItem: React.FC & { index: number }> & { + className: string + create: ShorthandFactory + handledProps: string[] + slotClassNames: ListItemSlotClassNames +} = props => { + const { + accessibility, + className, + content, + contentMedia, + design, + endMedia, + header, + important, + index, + headerMedia, + media, + styles, + } = props + + const context: ProviderContextPrepared = React.useContext(ThemeContext) - static displayName = 'ListItem' + const parentProps = 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, - static className = 'ui-list__item' + 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 - static slotClassNames: ListItemSlotClassNames + const getA11Props = useAccessibility(accessibility, { + debugName: ListItem.displayName, + actionHandlers: { + performClick: e => { + e.preventDefault() + handleClick(e) + }, + }, + mapPropsToBehavior: () => ({}), + rtl: context.rtl, + }) + const { classes, styles: resolvedStyles } = useStyles(ListItem.displayName, { + 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 = getUnhandledProps(ListItem.handledProps as any, props) - static propTypes = { - ...commonPropTypes.createCommon({ - content: false, + const handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { + _.invoke(props, 'onClick', e, props) + parentProps.onItemClick(e, index) + } + + 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, }), - contentMedia: PropTypes.any, - content: PropTypes.any, + }) + 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, + }), + }) - debug: PropTypes.bool, + return ( + + {mediaElement} - header: PropTypes.any, - endMedia: PropTypes.any, - headerMedia: PropTypes.any, +
+ {(headerElement || headerMediaElement) && ( +
+ {headerElement} + {headerMediaElement} +
+ )} + {(contentElement || contentMediaElement) && ( +
+ {contentElement} + {contentMediaElement} +
+ )} +
- important: PropTypes.bool, - media: PropTypes.any, + {endMediaElement} +
+ ) +} - selectable: PropTypes.bool, - navigable: PropTypes.bool, - index: PropTypes.number, - selected: PropTypes.bool, +ListItem.className = 'ui-list__item' +ListItem.displayName = 'ListItem' - truncateContent: PropTypes.bool, - truncateHeader: PropTypes.bool, +ListItem.defaultProps = { + as: 'li', + accessibility: listItemBehavior, +} - onClick: PropTypes.func, - } +ListItem.propTypes = { + ...commonPropTypes.createCommon({ + content: false, + }), + contentMedia: PropTypes.any, + content: PropTypes.any, - static defaultProps = { - as: 'li', - accessibility: listItemBehavior as Accessibility, - } + debug: PropTypes.bool, - actionHandlers = { - performClick: event => { - this.handleClick(event) - event.preventDefault() - }, - } + header: PropTypes.any, + endMedia: PropTypes.any, + headerMedia: PropTypes.any, - handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { - _.invoke(this.props, 'onClick', e, this.props) - } + important: PropTypes.bool, + media: PropTypes.any, - 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} -
- ) - } + selectable: PropTypes.bool, + navigable: PropTypes.bool, + index: PropTypes.number.isRequired, + 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' }) ListItem.slotClassNames = { header: `${ListItem.className}__header`, headerMedia: `${ListItem.className}__headerMedia`, @@ -199,6 +275,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..dd85586dd1 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 'src/themes/teams/components/List/listItemVariables' + +type ListItemStyleProps = 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: ListItemStyleProps, 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/state/src/index.ts b/packages/state/src/index.ts index 9085e4a1b9..9a57b19e4f 100644 --- a/packages/state/src/index.ts +++ b/packages/state/src/index.ts @@ -1,5 +1,7 @@ export * from './managers/dialogManager' export * from './managers/dropdownManager' +export * from './managers/listManager' +export * from './managers/menuManager' 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, + }, + }) From 97e9c5d4536eef29b2f20d208fdcf0d53ca82082 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 24 Jan 2020 12:44:04 +0100 Subject: [PATCH 02/16] wip --- packages/react/src/components/List/List.tsx | 68 +++++++++++-------- .../react/src/components/List/ListItem.tsx | 32 ++------- packages/state/src/index.ts | 1 - 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 37bfb899e3..c780b1dda1 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -31,7 +31,6 @@ import { createShorthandFactory, ShorthandFactory, } from '../../utils' -import { Provider, ListContextValue } from './context' import ListItem, { ListItemProps } from './ListItem' export interface ListProps extends UIComponentProps, ChildrenComponentProps { @@ -76,6 +75,16 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps { wrap?: (children: ReactChildren) => React.ReactNode } +// List props that are passed to each individual Item props +const itemProps = [ + 'debug', + 'selectable', + 'navigable', + 'truncateContent', + 'truncateHeader', + 'variables', +] + const List: React.FC> & { className: string create: ShorthandFactory @@ -93,12 +102,9 @@ const List: React.FC> & { design, horizontal, items, - navigable, selectable, selectedIndex, styles, - truncateContent, - truncateHeader, variables, wrap, } = props @@ -125,21 +131,38 @@ const List: React.FC> & { const unhandledProps = getUnhandledProps(List.handledProps as any, props) const hasContent = childrenExist(children) || (items && items.length > 0) - const value: ListContextValue = { - debug, - navigable, - selectable, - selectedIndex: state.selectedIndex, - truncateContent, - truncateHeader, - variables, - onItemClick: (e, itemIndex) => { + + const handleItemOverrides = (predefinedProps: ListItemProps) => ({ + onClick: (e: React.SyntheticEvent, itemProps: ListItemProps) => { + _.invoke(predefinedProps, 'onClick', e, itemProps) + if (selectable) { - actions.select(itemIndex) - _.invoke(props, 'onSelectedIndexChange', e, { ...props, selectedIndex: itemIndex }) + 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 === state.selectedIndex + } + + return ListItem.create(item, { + defaultProps: () => ({ + ..._.pick(props, itemProps), + ...maybeSelectableItemProps, + index, + }), + overrideProps: handleItemOverrides, + }) + }) return ( > & { ...unhandledProps, })} > - - {hasContent && - wrap( - childrenExist(children) - ? children - : _.map(items, (item, index) => - ListItem.create(item, { - defaultProps: () => ({ index }), - }), - ), - )} - + {hasContent && wrap(childrenExist(children) ? children : renderItems())} ) } diff --git a/packages/react/src/components/List/ListItem.tsx b/packages/react/src/components/List/ListItem.tsx index eb9638b524..728b16fe5b 100644 --- a/packages/react/src/components/List/ListItem.tsx +++ b/packages/react/src/components/List/ListItem.tsx @@ -5,7 +5,6 @@ import { useAccessibility, useStyles, } 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' @@ -28,7 +27,6 @@ import { ContentComponentProps, ShorthandFactory, } from '../../utils' -import { ListContext } from './context' export interface ListItemSlotClassNames { header: string @@ -93,35 +91,20 @@ const ListItem: React.FC & { index: number }> & { endMedia, header, important, - index, headerMedia, media, styles, + debug, + navigable, + selectable, + selected, + truncateContent, + truncateHeader, + variables, } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) - const parentProps = 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: { @@ -157,7 +140,6 @@ const ListItem: React.FC & { index: number }> & { const handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { _.invoke(props, 'onClick', e, props) - parentProps.onItemClick(e, index) } const contentElement = Box.create(content, { diff --git a/packages/state/src/index.ts b/packages/state/src/index.ts index 9a57b19e4f..72dd19ab3e 100644 --- a/packages/state/src/index.ts +++ b/packages/state/src/index.ts @@ -1,7 +1,6 @@ export * from './managers/dialogManager' export * from './managers/dropdownManager' export * from './managers/listManager' -export * from './managers/menuManager' export { default as createManager } from './createManager' export * from './types' From 70905e0b1eb8b420e1d1fa499551d1847cae770f Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 24 Jan 2020 15:14:07 +0100 Subject: [PATCH 03/16] fix UTs --- packages/react/src/components/List/ListItem.tsx | 17 +++++++++++++---- .../test/specs/components/List/List-test.tsx | 4 +++- .../specs/components/List/ListItem-test.tsx | 4 +++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/react/src/components/List/ListItem.tsx b/packages/react/src/components/List/ListItem.tsx index 728b16fe5b..07f0509332 100644 --- a/packages/react/src/components/List/ListItem.tsx +++ b/packages/react/src/components/List/ListItem.tsx @@ -4,6 +4,7 @@ import { getUnhandledProps, useAccessibility, useStyles, + useTelemetry, } from '@fluentui/react-bindings' import cx from 'classnames' import * as _ from 'lodash' @@ -82,6 +83,11 @@ const ListItem: React.FC & { index: number }> & { handledProps: string[] slotClassNames: ListItemSlotClassNames } = props => { + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const { setStart, setEnd } = useTelemetry(ListItem.displayName, context.telemetry) + + setStart() + const { accessibility, className, @@ -103,8 +109,6 @@ const ListItem: React.FC & { index: number }> & { variables, } = props - const context: ProviderContextPrepared = React.useContext(ThemeContext) - const getA11Props = useAccessibility(accessibility, { debugName: ListItem.displayName, actionHandlers: { @@ -117,6 +121,7 @@ const ListItem: React.FC & { index: number }> & { rtl: context.rtl, }) const { classes, styles: resolvedStyles } = useStyles(ListItem.displayName, { + className: ListItem.className, mapPropsToStyles: () => ({ debug, navigable, @@ -179,7 +184,7 @@ const ListItem: React.FC & { index: number }> & { }), }) - return ( + const element = ( & { index: number }> & { {endMediaElement} ) + + setEnd() + + return element } ListItem.className = 'ui-list__item' @@ -235,7 +244,7 @@ ListItem.propTypes = { selectable: PropTypes.bool, navigable: PropTypes.bool, - index: PropTypes.number.isRequired, + index: PropTypes.number, selected: PropTypes.bool, truncateContent: PropTypes.bool, 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', () => { From 24bfeacc708b0a6c154890cbdd227e0cf38d3c1d Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 24 Jan 2020 15:17:08 +0100 Subject: [PATCH 04/16] fix UTs --- packages/react/src/components/List/List.tsx | 17 +++++++---------- packages/react/src/components/List/ListItem.tsx | 14 ++++++-------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index c780b1dda1..18f489e21b 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -21,6 +21,7 @@ import { ShorthandCollection, ReactChildren, ProviderContextPrepared, + FluentComponentStaticProps, } from '../../types' import { childrenExist, @@ -29,7 +30,6 @@ import { commonPropTypes, rtlTextContainer, createShorthandFactory, - ShorthandFactory, } from '../../utils' import ListItem, { ListItemProps } from './ListItem' @@ -85,13 +85,10 @@ const itemProps = [ 'variables', ] -const List: React.FC> & { - className: string - create: ShorthandFactory - handledProps: string[] - - Item: typeof ListItem -} = props => { +const List: React.FC> & + FluentComponentStaticProps & { + Item: typeof ListItem + } = props => { const { accessibility, as, @@ -128,7 +125,7 @@ const List: React.FC> & { }) const ElementType = getElementType(props) - const unhandledProps = getUnhandledProps(List.handledProps as any, props) + const unhandledProps = getUnhandledProps(List.handledProps, props) const hasContent = childrenExist(children) || (items && items.length > 0) @@ -202,7 +199,7 @@ List.propTypes = { wrap: PropTypes.func, } -List.handledProps = Object.keys(List.propTypes) +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 07f0509332..fcaf672bd5 100644 --- a/packages/react/src/components/List/ListItem.tsx +++ b/packages/react/src/components/List/ListItem.tsx @@ -20,13 +20,13 @@ import { ComponentEventHandler, withSafeTypeForAs, ProviderContextPrepared, + FluentComponentStaticProps, } from '../../types' import { createShorthandFactory, UIComponentProps, commonPropTypes, ContentComponentProps, - ShorthandFactory, } from '../../utils' export interface ListItemSlotClassNames { @@ -77,12 +77,10 @@ export interface ListItemProps onClick?: ComponentEventHandler } -const ListItem: React.FC & { index: number }> & { - className: string - create: ShorthandFactory - handledProps: string[] - slotClassNames: ListItemSlotClassNames -} = props => { +const ListItem: React.FC & { index: number }> & + FluentComponentStaticProps & { + slotClassNames: ListItemSlotClassNames + } = props => { const context: ProviderContextPrepared = React.useContext(ThemeContext) const { setStart, setEnd } = useTelemetry(ListItem.displayName, context.telemetry) @@ -141,7 +139,7 @@ const ListItem: React.FC & { index: number }> & { }) const ElementType = getElementType(props) - const unhandledProps = getUnhandledProps(ListItem.handledProps as any, props) + const unhandledProps = getUnhandledProps(ListItem.handledProps, props) const handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { _.invoke(props, 'onClick', e, props) From 8c2fda97c8bfb4eee594b82c99ea2398b5536261 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 24 Jan 2020 16:21:43 +0100 Subject: [PATCH 05/16] fix import --- .../react/src/themes/teams/components/List/listItemStyles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/themes/teams/components/List/listItemStyles.ts b/packages/react/src/themes/teams/components/List/listItemStyles.ts index dd85586dd1..76139826f9 100644 --- a/packages/react/src/themes/teams/components/List/listItemStyles.ts +++ b/packages/react/src/themes/teams/components/List/listItemStyles.ts @@ -3,7 +3,7 @@ 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 'src/themes/teams/components/List/listItemVariables' +import { ListItemVariables } from './listItemVariables' type ListItemStyleProps = Pick< ListItemProps, From bf7bca22ba5fabc35836c1ea4adff4233a6058e3 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 24 Jan 2020 16:26:46 +0100 Subject: [PATCH 06/16] add props to behavior --- packages/react/src/components/List/List.tsx | 7 ++++++- packages/react/src/components/List/ListItem.tsx | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 18f489e21b..5bd7b03d9b 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -98,6 +98,7 @@ const List: React.FC> & defaultSelectedIndex, design, horizontal, + navigable, items, selectable, selectedIndex, @@ -114,7 +115,11 @@ const List: React.FC> & }) const getA11Props = useAccessibility(accessibility, { debugName: List.displayName, - mapPropsToBehavior: () => ({}), + mapPropsToBehavior: () => ({ + horizontal, + navigable, + selectable, + }), rtl: context.rtl, }) const { classes } = useStyles(List.displayName, { diff --git a/packages/react/src/components/List/ListItem.tsx b/packages/react/src/components/List/ListItem.tsx index fcaf672bd5..6af5e1aed2 100644 --- a/packages/react/src/components/List/ListItem.tsx +++ b/packages/react/src/components/List/ListItem.tsx @@ -115,7 +115,11 @@ const ListItem: React.FC & { index: number }> & handleClick(e) }, }, - mapPropsToBehavior: () => ({}), + mapPropsToBehavior: () => ({ + navigable, + selectable, + selected, + }), rtl: context.rtl, }) const { classes, styles: resolvedStyles } = useStyles(ListItem.displayName, { From 913dc50f8748899c7b26abecb818184956045378 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 24 Jan 2020 16:38:40 +0100 Subject: [PATCH 07/16] fix FZ --- packages/react-bindings/src/FocusZone/FocusZone.types.ts | 4 +--- packages/react-bindings/src/hooks/useAccessibility.ts | 1 + packages/react/src/components/List/List.tsx | 6 ++++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/react-bindings/src/FocusZone/FocusZone.types.ts b/packages/react-bindings/src/FocusZone/FocusZone.types.ts index a979df15b4..3286fc0f6f 100644 --- a/packages/react-bindings/src/FocusZone/FocusZone.types.ts +++ b/packages/react-bindings/src/FocusZone/FocusZone.types.ts @@ -38,9 +38,7 @@ export interface IFocusZone { /** * FocusZone component props. */ -export interface FocusZoneProps - extends FocusZoneProperties, - React.HTMLAttributes { +export interface FocusZoneProps extends FocusZoneProperties, React.HTMLAttributes { /** @docSiteIgnore */ as?: React.ReactType /** diff --git a/packages/react-bindings/src/hooks/useAccessibility.ts b/packages/react-bindings/src/hooks/useAccessibility.ts index 3115d95dde..99f7cedc88 100644 --- a/packages/react-bindings/src/hooks/useAccessibility.ts +++ b/packages/react-bindings/src/hooks/useAccessibility.ts @@ -23,6 +23,7 @@ const mergeProps = >( ): MergedProps => { const finalProps: MergedProps = { ...definition.attributes[slotName], + ...(slotName === 'root' && definition.focusZone && definition.focusZone.props), ...slotProps, } const slotHandlers = definition.keyHandlers[slotName] diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 5bd7b03d9b..bf933dfa17 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -1,5 +1,6 @@ import { Accessibility, listBehavior, ListBehaviorProps } from '@fluentui/accessibility' import { + FocusZone, getElementType, getUnhandledProps, useAccessibility, @@ -167,15 +168,16 @@ const List: React.FC> & }) return ( - {hasContent && wrap(childrenExist(children) ? children : renderItems())} - + ) } From f7bac86837fa82c874868ce761051eafa184c77b Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 27 Jan 2020 13:44:59 +0100 Subject: [PATCH 08/16] add FZ support --- .../src/hooks/useAccessibility.ts | 33 +++++++++- .../test/hooks/useAccessibility-test.tsx | 63 ++++++++++++++++++- packages/react/src/components/List/List.tsx | 8 +-- 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/packages/react-bindings/src/hooks/useAccessibility.ts b/packages/react-bindings/src/hooks/useAccessibility.ts index 3d7130b796..a40cfad9fb 100644 --- a/packages/react-bindings/src/hooks/useAccessibility.ts +++ b/packages/react-bindings/src/hooks/useAccessibility.ts @@ -3,6 +3,7 @@ import * as React from 'react' import getAccessibility from '../accessibility/getAccessibility' import { ReactAccessibilityBehavior, AccessibilityActionHandlers } from '../accessibility/types' +import FocusZone from '../FocusZone/FocusZone' type UseAccessibilityOptions = { actionHandlers?: AccessibilityActionHandlers @@ -11,6 +12,13 @@ type UseAccessibilityOptions = { rtl?: boolean } +type UseAccessibilityResult = (>( + slotName: string, + slotProps: SlotProps, +) => MergedProps) & { + unstable_withFocusZone: (children: React.ReactElement) => React.ReactElement +} + type MergedProps> = SlotProps & Partial & { onKeyDown?: (e: React.KeyboardEvent, ...args: any[]) => void @@ -23,7 +31,6 @@ const mergeProps = >( ): MergedProps => { const finalProps: MergedProps = { ...definition.attributes[slotName], - ...(slotName === 'root' && definition.focusZone && definition.focusZone.props), ...slotProps, } const slotHandlers = definition.keyHandlers[slotName] @@ -63,8 +70,30 @@ const useAccessibility = ( actionHandlers, ) - return >(slotName: string, slotProps: SlotProps) => + const getA11Props: UseAccessibilityResult = (slotName, slotProps) => mergeProps(slotName, slotProps, definition) + + // Provides an experimental handling for FocusZone definition in behaviors + getA11Props.unstable_withFocusZone = (children: React.ReactElement) => { + if (definition.focusZone) { + let element: React.ReactElement = children + + if (process.env.NODE_ENV !== 'production') { + element = React.Children.only(children) + } + + return React.createElement(FocusZone, { + ...definition.focusZone.props, + ...element.props, + as: element.type, + isRtl: rtl, + }) + } + + return children + } + + return getA11Props } export default useAccessibility diff --git a/packages/react-bindings/test/hooks/useAccessibility-test.tsx b/packages/react-bindings/test/hooks/useAccessibility-test.tsx index 7fd0d34f0e..8582e1b047 100644 --- a/packages/react-bindings/test/hooks/useAccessibility-test.tsx +++ b/packages/react-bindings/test/hooks/useAccessibility-test.tsx @@ -29,6 +29,15 @@ const testBehavior: Accessibility = props => ({ }, }) +const focusZoneBehavior: Accessibility = () => ({ + focusZone: { + props: { + disabled: true, + shouldFocusOnMount: true, + }, + }, +}) + type TestComponentProps = { disabled?: boolean onClick?: (e: React.KeyboardEvent, slotName: string) => void @@ -48,14 +57,28 @@ const TestComponent: React.FunctionComponent = props => { }, }) - return ( + return getA11Props.unstable_withFocusZone(
-
+ , + ) +} + +type FocusZoneComponentProps = { + as?: React.ElementType + rtl?: boolean +} + +const FocusZoneComponent: React.FunctionComponent = props => { + const { as: ElementType = 'div', children, rtl = false } = props + const getA11Props = useAccessibility(focusZoneBehavior, { rtl }) + + return getA11Props.unstable_withFocusZone( + {children}, ) } @@ -115,4 +138,40 @@ describe('useAccessibility', () => { 'root', ) }) + + describe('FocusZone', () => { + it('do not render FocusZone without the definition in a behavior', () => { + expect(shallow().find('FocusZone')).toHaveLength(0) + }) + + it('renders FocusZone with the definition in a behavior', () => { + expect(shallow().find('FocusZone')).toHaveLength(1) + }) + + it('applies props from the behavior to a FocusZone component', () => { + expect( + shallow() + .find('FocusZone') + .props(), + ).toEqual( + expect.objectContaining({ + disabled: true, + shouldFocusOnMount: true, + }), + ) + }) + + it.only('passes "rtl" value', () => { + expect( + shallow() + .find('FocusZone') + .prop('isRtl'), + ).toBe(false) + expect( + shallow() + .find('FocusZone') + .prop('isRtl'), + ).toBe(true) + }) + }) }) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index bf933dfa17..5cecd1cfc6 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -1,6 +1,5 @@ import { Accessibility, listBehavior, ListBehaviorProps } from '@fluentui/accessibility' import { - FocusZone, getElementType, getUnhandledProps, useAccessibility, @@ -167,17 +166,16 @@ const List: React.FC> & }) }) - return ( - {hasContent && wrap(childrenExist(children) ? children : renderItems())} - + , ) } From f63ae5887c9539ec77f4545f9e60214d3da10f27 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 27 Jan 2020 14:07:27 +0100 Subject: [PATCH 09/16] improve types --- packages/react/src/components/List/List.tsx | 13 ++++++++++--- .../themes/teams/components/List/listItemStyles.ts | 6 +++--- .../src/themes/teams/components/List/listStyles.ts | 5 +++-- packages/react/src/themes/teams/types.ts | 8 ++++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 5cecd1cfc6..3599afe865 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -32,6 +32,7 @@ import { createShorthandFactory, } from '../../utils' import ListItem, { ListItemProps } from './ListItem' +import { useTelemetry } from '@fluentui/react-bindings/src' export interface ListProps extends UIComponentProps, ChildrenComponentProps { /** Accessibility behavior if overridden by the user. */ @@ -89,6 +90,10 @@ 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, @@ -106,8 +111,7 @@ const List: React.FC> & variables, wrap, } = props - - const context: ProviderContextPrepared = React.useContext(ThemeContext) + setStart() const { state, actions } = useStateManager(createListManager, { mapPropsToInitialState: () => ({ selectedIndex: defaultSelectedIndex }), @@ -166,7 +170,7 @@ const List: React.FC> & }) }) - return getA11Props.unstable_withFocusZone( + const element = getA11Props.unstable_withFocusZone( > & {hasContent && wrap(childrenExist(children) ? children : renderItems())} , ) + setEnd() + + return element } List.className = 'ui-list' diff --git a/packages/react/src/themes/teams/components/List/listItemStyles.ts b/packages/react/src/themes/teams/components/List/listItemStyles.ts index 76139826f9..96721ebdf7 100644 --- a/packages/react/src/themes/teams/components/List/listItemStyles.ts +++ b/packages/react/src/themes/teams/components/List/listItemStyles.ts @@ -5,7 +5,7 @@ import { default as ListItem, ListItemProps } from '../../../../components/List/ import getBorderFocusStyles from '../../getBorderFocusStyles' import { ListItemVariables } from './listItemVariables' -type ListItemStyleProps = Pick< +export type ListItemStylesProps = Pick< ListItemProps, | 'debug' | 'important' @@ -27,7 +27,7 @@ const truncateStyle: ICSSInJSStyle = { whiteSpace: 'nowrap', } -const selectableHoverStyle = (p: ListItemStyleProps, v): ICSSInJSStyle => ({ +const selectableHoverStyle = (p: ListItemStylesProps, v): ICSSInJSStyle => ({ background: v.selectableFocusHoverBackgroundColor, color: v.selectableFocusHoverColor, cursor: 'pointer', @@ -51,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, diff --git a/packages/react/src/themes/teams/components/List/listStyles.ts b/packages/react/src/themes/teams/components/List/listStyles.ts index 7d52fa2e0c..e8b07d6c28 100644 --- a/packages/react/src/themes/teams/components/List/listStyles.ts +++ b/packages/react/src/themes/teams/components/List/listStyles.ts @@ -1,9 +1,10 @@ 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 & { as: string } + +const listStyles: ComponentSlotStylesPrepared = { root: ({ props: p }): ICSSInJSStyle => ({ ...(p.debug && debugRoot()), display: p.horizontal ? 'inline-flex' : 'block', 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 From 24c24dd785600b95ad28a038b1e61a005302db55 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 27 Jan 2020 14:25:10 +0100 Subject: [PATCH 10/16] improve types --- packages/react/src/components/List/List.tsx | 2 +- packages/react/src/themes/teams/components/List/listStyles.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 3599afe865..2a4a5448db 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -128,7 +128,7 @@ const List: React.FC> & }) const { classes } = useStyles(List.displayName, { className: List.className, - mapPropsToStyles: () => ({ as: String(as), debug, horizontal }), + mapPropsToStyles: () => ({ isListTag: as === 'ol' || as === 'ul', debug, horizontal }), mapPropsToInlineStyles: () => ({ className, design, styles, variables }), rtl: context.rtl, }) diff --git a/packages/react/src/themes/teams/components/List/listStyles.ts b/packages/react/src/themes/teams/components/List/listStyles.ts index e8b07d6c28..3fbf78429c 100644 --- a/packages/react/src/themes/teams/components/List/listStyles.ts +++ b/packages/react/src/themes/teams/components/List/listStyles.ts @@ -2,13 +2,13 @@ import { debugRoot } from '../../../../styles/debugStyles' import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '@fluentui/styles' import { ListProps } from '../../../../components/List/List' -export type ListStylesProps = Pick & { as: string } +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, From 874eb4a41f181ece2312f4c7246c2db247197446 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 27 Jan 2020 15:56:58 +0100 Subject: [PATCH 11/16] fix hook name --- packages/react/src/components/List/List.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 2a4a5448db..e3c686d04f 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -170,7 +170,7 @@ const List: React.FC> & }) }) - const element = getA11Props.unstable_withFocusZone( + const element = getA11Props.unstable_wrapWithFocusZone( Date: Mon, 27 Jan 2020 15:57:23 +0100 Subject: [PATCH 12/16] remove double start --- packages/react/src/components/List/List.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index e3c686d04f..6e69ffe26d 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -111,7 +111,6 @@ const List: React.FC> & variables, wrap, } = props - setStart() const { state, actions } = useStateManager(createListManager, { mapPropsToInitialState: () => ({ selectedIndex: defaultSelectedIndex }), From 640a57f4d9931672d242174a49c166a60e764ddb Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 27 Jan 2020 19:01:20 +0100 Subject: [PATCH 13/16] fix import --- packages/react/src/components/List/List.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 6e69ffe26d..10df6c5e41 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -5,6 +5,7 @@ import { useAccessibility, useStateManager, useStyles, + useTelemetry, } from '@fluentui/react-bindings' import { createListManager } from '@fluentui/state' import * as customPropTypes from '@fluentui/react-proptypes' @@ -32,7 +33,6 @@ import { createShorthandFactory, } from '../../utils' import ListItem, { ListItemProps } from './ListItem' -import { useTelemetry } from '@fluentui/react-bindings/src' export interface ListProps extends UIComponentProps, ChildrenComponentProps { /** Accessibility behavior if overridden by the user. */ From c7a371d9c7bb5c4072eb3b6f47040abd9c47e8b4 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 28 Jan 2020 16:53:47 +0100 Subject: [PATCH 14/16] make props optional --- .../src/themes/teams/components/List/listItemStyles.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/src/themes/teams/components/List/listItemStyles.ts b/packages/react/src/themes/teams/components/List/listItemStyles.ts index 96721ebdf7..a013736dd1 100644 --- a/packages/react/src/themes/teams/components/List/listItemStyles.ts +++ b/packages/react/src/themes/teams/components/List/listItemStyles.ts @@ -15,10 +15,10 @@ export type ListItemStylesProps = Pick< | 'truncateContent' | 'truncateHeader' > & { - hasContent: boolean - hasContentMedia: boolean - hasHeader: boolean - hasHeaderMedia: boolean + hasContent?: boolean + hasContentMedia?: boolean + hasHeader?: boolean + hasHeaderMedia?: boolean } const truncateStyle: ICSSInJSStyle = { From 85b6f7623b7a1188a42323d35a4d10e8120769aa Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 28 Jan 2020 17:19:50 +0100 Subject: [PATCH 15/16] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e77a19614a..8678627942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - 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)) From 6003ad640294e4a3accc4706cad5659d85c9ea32 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 28 Jan 2020 17:21:27 +0100 Subject: [PATCH 16/16] update changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8678627942..213690e61b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,6 @@ 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))