Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

Commit c488504

Browse files
authored
chore(List): move to hooks (#2267)
* chore(List): move to hooks * wip * fix UTs * fix UTs * fix import * add props to behavior * fix FZ * add FZ support * improve types * improve types * fix hook name * remove double start * fix import * make props optional * update changelog * update changelog
1 parent 35da256 commit c488504

File tree

11 files changed

+405
-251
lines changed

11 files changed

+405
-251
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2323
- `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))
2424
- `FocusZoneMode` and `FOCUSZONE_WRAP_ATTRIBUTE` are no longer exported @layershifter ([#2265](https://github.com/microsoft/fluent-ui-react/pull/2265))
2525
- Returned function from `useAccessibility` no longer keeps the same reference @layershifter ([#2268](https://github.com/microsoft/fluent-ui-react/pull/2268))
26-
- 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))
2726
- 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))
27+
- 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))
28+
- Restricted prop set in the `List` & `ListItem` @layershifter ([#2238](https://github.com/microsoft/fluent-ui-react/pull/2238))
2829

2930
### Fixes
3031
- Fix event listener leak in `FocusZone` @miroslavstastny ([#2227](https://github.com/microsoft/fluent-ui-react/pull/2227))

packages/accessibility/src/behaviors/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,21 @@ export { default as menuBehavior } from './Menu/menuBehavior'
99
export { default as menuItemBehavior } from './Menu/menuItemBehavior'
1010
export { default as menuDividerBehavior } from './Menu/menuDividerBehavior'
1111
export { default as submenuBehavior } from './Menu/submenuBehavior'
12+
1213
export { default as basicListBehavior } from './List/listBehavior'
1314
export { default as basicListItemBehavior } from './List/basicListItemBehavior'
15+
1416
export { default as listBehavior } from './List/listBehavior'
17+
export * from './List/listBehavior'
1518
export { default as listItemBehavior } from './List/listItemBehavior'
19+
export * from './List/listItemBehavior'
20+
1621
export { default as navigableListBehavior } from './List/navigableListBehavior'
1722
export { default as navigableListItemBehavior } from './List/navigableListItemBehavior'
23+
1824
export { default as selectableListBehavior } from './List/selectableListBehavior'
1925
export { default as selectableListItemBehavior } from './List/selectableListItemBehavior'
26+
2027
export { default as loaderBehavior } from './Loader/loaderBehavior'
2128
export { default as inputBehavior } from './Input/inputBehavior'
2229
export { default as iconBehavior } from './Icon/iconBehavior'

packages/react/src/components/List/List.tsx

Lines changed: 146 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,42 @@
1-
import { Accessibility, listBehavior } from '@fluentui/accessibility'
1+
import { Accessibility, listBehavior, ListBehaviorProps } from '@fluentui/accessibility'
2+
import {
3+
getElementType,
4+
getUnhandledProps,
5+
useAccessibility,
6+
useStateManager,
7+
useStyles,
8+
useTelemetry,
9+
} from '@fluentui/react-bindings'
10+
import { createListManager } from '@fluentui/state'
211
import * as customPropTypes from '@fluentui/react-proptypes'
312
import * as _ from 'lodash'
4-
import * as React from 'react'
513
import * as PropTypes from 'prop-types'
14+
import * as React from 'react'
15+
// @ts-ignore
16+
import { ThemeContext } from 'react-fela'
617

18+
import {
19+
WithAsProp,
20+
ComponentEventHandler,
21+
withSafeTypeForAs,
22+
ShorthandCollection,
23+
ReactChildren,
24+
ProviderContextPrepared,
25+
FluentComponentStaticProps,
26+
} from '../../types'
727
import {
828
childrenExist,
9-
AutoControlledComponent,
1029
UIComponentProps,
1130
ChildrenComponentProps,
1231
commonPropTypes,
1332
rtlTextContainer,
14-
applyAccessibilityKeyHandlers,
1533
createShorthandFactory,
16-
ShorthandFactory,
1734
} from '../../utils'
1835
import ListItem, { ListItemProps } from './ListItem'
19-
import {
20-
WithAsProp,
21-
ComponentEventHandler,
22-
withSafeTypeForAs,
23-
ShorthandCollection,
24-
ReactChildren,
25-
} from '../../types'
26-
27-
export interface ListSlotClassNames {
28-
item: string
29-
}
3036

3137
export interface ListProps extends UIComponentProps, ChildrenComponentProps {
3238
/** Accessibility behavior if overridden by the user. */
33-
accessibility?: Accessibility
39+
accessibility?: Accessibility<ListBehaviorProps>
3440

3541
/** Toggle debug mode */
3642
debug?: boolean
@@ -70,122 +76,142 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps {
7076
wrap?: (children: ReactChildren) => React.ReactNode
7177
}
7278

73-
export interface ListState {
74-
selectedIndex?: number
75-
}
79+
// List props that are passed to each individual Item props
80+
const itemProps = [
81+
'debug',
82+
'selectable',
83+
'navigable',
84+
'truncateContent',
85+
'truncateHeader',
86+
'variables',
87+
]
88+
89+
const List: React.FC<WithAsProp<ListProps>> &
90+
FluentComponentStaticProps<ListProps> & {
91+
Item: typeof ListItem
92+
} = props => {
93+
const context: ProviderContextPrepared = React.useContext(ThemeContext)
94+
const { setStart, setEnd } = useTelemetry(List.displayName, context.telemetry)
95+
setStart()
96+
97+
const {
98+
accessibility,
99+
as,
100+
children,
101+
className,
102+
debug,
103+
defaultSelectedIndex,
104+
design,
105+
horizontal,
106+
navigable,
107+
items,
108+
selectable,
109+
selectedIndex,
110+
styles,
111+
variables,
112+
wrap,
113+
} = props
114+
115+
const { state, actions } = useStateManager(createListManager, {
116+
mapPropsToInitialState: () => ({ selectedIndex: defaultSelectedIndex }),
117+
mapPropsToState: () => ({ selectedIndex }),
118+
})
119+
const getA11Props = useAccessibility(accessibility, {
120+
debugName: List.displayName,
121+
mapPropsToBehavior: () => ({
122+
horizontal,
123+
navigable,
124+
selectable,
125+
}),
126+
rtl: context.rtl,
127+
})
128+
const { classes } = useStyles(List.displayName, {
129+
className: List.className,
130+
mapPropsToStyles: () => ({ isListTag: as === 'ol' || as === 'ul', debug, horizontal }),
131+
mapPropsToInlineStyles: () => ({ className, design, styles, variables }),
132+
rtl: context.rtl,
133+
})
76134

77-
class List extends AutoControlledComponent<WithAsProp<ListProps>, ListState> {
78-
static displayName = 'List'
135+
const ElementType = getElementType(props)
136+
const unhandledProps = getUnhandledProps(List.handledProps, props)
79137

80-
static className = 'ui-list'
138+
const hasContent = childrenExist(children) || (items && items.length > 0)
81139

82-
static slotClassNames: ListSlotClassNames = {
83-
item: `${List.className}__item`,
84-
}
140+
const handleItemOverrides = (predefinedProps: ListItemProps) => ({
141+
onClick: (e: React.SyntheticEvent, itemProps: ListItemProps) => {
142+
_.invoke(predefinedProps, 'onClick', e, itemProps)
85143

86-
static propTypes = {
87-
...commonPropTypes.createCommon({
88-
content: false,
89-
}),
90-
debug: PropTypes.bool,
91-
items: customPropTypes.collectionShorthand,
92-
selectable: customPropTypes.every([customPropTypes.disallow(['navigable']), PropTypes.bool]),
93-
navigable: customPropTypes.every([customPropTypes.disallow(['selectable']), PropTypes.bool]),
94-
truncateContent: PropTypes.bool,
95-
truncateHeader: PropTypes.bool,
96-
selectedIndex: PropTypes.number,
97-
defaultSelectedIndex: PropTypes.number,
98-
onSelectedIndexChange: PropTypes.func,
99-
horizontal: PropTypes.bool,
100-
wrap: PropTypes.func,
101-
}
102-
103-
static defaultProps = {
104-
as: 'ul',
105-
accessibility: listBehavior as Accessibility,
106-
wrap: children => children,
107-
}
108-
109-
static autoControlledProps = ['selectedIndex']
110-
111-
getInitialAutoControlledState() {
112-
return { selectedIndex: -1 }
113-
}
114-
115-
static Item = ListItem
116-
117-
// List props that are passed to each individual Item props
118-
static itemProps = [
119-
'debug',
120-
'selectable',
121-
'navigable',
122-
'truncateContent',
123-
'truncateHeader',
124-
'variables',
125-
]
126-
127-
static create: ShorthandFactory<ListProps>
128-
129-
handleItemOverrides = (predefinedProps: ListItemProps) => {
130-
const { selectable } = this.props
131-
132-
return {
133-
onClick: (e: React.SyntheticEvent, itemProps: ListItemProps) => {
134-
_.invoke(predefinedProps, 'onClick', e, itemProps)
135-
136-
if (selectable) {
137-
this.setState({ selectedIndex: itemProps.index })
138-
_.invoke(this.props, 'onSelectedIndexChange', e, {
139-
...this.props,
140-
...{ selectedIndex: itemProps.index },
141-
})
142-
}
143-
},
144-
}
145-
}
146-
147-
renderComponent({ ElementType, classes, accessibility, unhandledProps }) {
148-
const { children, items, wrap } = this.props
149-
const hasContent = childrenExist(children) || (items && items.length > 0)
150-
151-
return (
152-
<ElementType
153-
{...accessibility.attributes.root}
154-
{...rtlTextContainer.getAttributes({ forElements: [children] })}
155-
{...unhandledProps}
156-
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
157-
className={classes.root}
158-
>
159-
{hasContent && wrap(childrenExist(children) ? children : this.renderItems())}
160-
</ElementType>
161-
)
162-
}
163-
164-
renderItems() {
165-
const { items, selectable } = this.props
166-
const { selectedIndex } = this.state
167-
168-
return _.map(items, (item, index) => {
144+
if (selectable) {
145+
actions.select(itemProps.index)
146+
_.invoke(props, 'onSelectedIndexChange', e, {
147+
...props,
148+
...{ selectedIndex: itemProps.index },
149+
})
150+
}
151+
},
152+
})
153+
154+
const renderItems = () =>
155+
_.map(items, (item, index) => {
169156
const maybeSelectableItemProps = {} as any
170157

171158
if (selectable) {
172-
maybeSelectableItemProps.selected = index === selectedIndex
159+
maybeSelectableItemProps.selected = index === state.selectedIndex
173160
}
174161

175-
const itemProps = () => ({
176-
className: List.slotClassNames.item,
177-
..._.pick(this.props, List.itemProps),
178-
...maybeSelectableItemProps,
179-
index,
180-
})
181-
182162
return ListItem.create(item, {
183-
defaultProps: itemProps,
184-
overrideProps: this.handleItemOverrides,
163+
defaultProps: () => ({
164+
..._.pick(props, itemProps),
165+
...maybeSelectableItemProps,
166+
index,
167+
}),
168+
overrideProps: handleItemOverrides,
185169
})
186170
})
187-
}
171+
172+
const element = getA11Props.unstable_wrapWithFocusZone(
173+
<ElementType
174+
{...getA11Props('root', {
175+
className: classes.root,
176+
...rtlTextContainer.getAttributes({ forElements: [children] }),
177+
...unhandledProps,
178+
})}
179+
>
180+
{hasContent && wrap(childrenExist(children) ? children : renderItems())}
181+
</ElementType>,
182+
)
183+
setEnd()
184+
185+
return element
186+
}
187+
188+
List.className = 'ui-list'
189+
List.displayName = 'List'
190+
191+
List.defaultProps = {
192+
as: 'ul',
193+
accessibility: listBehavior,
194+
wrap: children => children,
188195
}
196+
List.propTypes = {
197+
...commonPropTypes.createCommon({
198+
content: false,
199+
}),
200+
debug: PropTypes.bool,
201+
items: customPropTypes.collectionShorthand,
202+
selectable: customPropTypes.every([customPropTypes.disallow(['navigable']), PropTypes.bool]),
203+
navigable: customPropTypes.every([customPropTypes.disallow(['selectable']), PropTypes.bool]),
204+
truncateContent: PropTypes.bool,
205+
truncateHeader: PropTypes.bool,
206+
selectedIndex: PropTypes.number,
207+
defaultSelectedIndex: PropTypes.number,
208+
onSelectedIndexChange: PropTypes.func,
209+
horizontal: PropTypes.bool,
210+
wrap: PropTypes.func,
211+
}
212+
213+
List.handledProps = Object.keys(List.propTypes) as any
214+
List.Item = ListItem
189215

190216
List.create = createShorthandFactory({ Component: List, mappedArrayProp: 'items' })
191217

0 commit comments

Comments
 (0)