|
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' |
2 | 11 | import * as customPropTypes from '@fluentui/react-proptypes'
|
3 | 12 | import * as _ from 'lodash'
|
4 |
| -import * as React from 'react' |
5 | 13 | import * as PropTypes from 'prop-types'
|
| 14 | +import * as React from 'react' |
| 15 | +// @ts-ignore |
| 16 | +import { ThemeContext } from 'react-fela' |
6 | 17 |
|
| 18 | +import { |
| 19 | + WithAsProp, |
| 20 | + ComponentEventHandler, |
| 21 | + withSafeTypeForAs, |
| 22 | + ShorthandCollection, |
| 23 | + ReactChildren, |
| 24 | + ProviderContextPrepared, |
| 25 | + FluentComponentStaticProps, |
| 26 | +} from '../../types' |
7 | 27 | import {
|
8 | 28 | childrenExist,
|
9 |
| - AutoControlledComponent, |
10 | 29 | UIComponentProps,
|
11 | 30 | ChildrenComponentProps,
|
12 | 31 | commonPropTypes,
|
13 | 32 | rtlTextContainer,
|
14 |
| - applyAccessibilityKeyHandlers, |
15 | 33 | createShorthandFactory,
|
16 |
| - ShorthandFactory, |
17 | 34 | } from '../../utils'
|
18 | 35 | 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 |
| -} |
30 | 36 |
|
31 | 37 | export interface ListProps extends UIComponentProps, ChildrenComponentProps {
|
32 | 38 | /** Accessibility behavior if overridden by the user. */
|
33 |
| - accessibility?: Accessibility |
| 39 | + accessibility?: Accessibility<ListBehaviorProps> |
34 | 40 |
|
35 | 41 | /** Toggle debug mode */
|
36 | 42 | debug?: boolean
|
@@ -70,122 +76,142 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps {
|
70 | 76 | wrap?: (children: ReactChildren) => React.ReactNode
|
71 | 77 | }
|
72 | 78 |
|
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 | + }) |
76 | 134 |
|
77 |
| -class List extends AutoControlledComponent<WithAsProp<ListProps>, ListState> { |
78 |
| - static displayName = 'List' |
| 135 | + const ElementType = getElementType(props) |
| 136 | + const unhandledProps = getUnhandledProps(List.handledProps, props) |
79 | 137 |
|
80 |
| - static className = 'ui-list' |
| 138 | + const hasContent = childrenExist(children) || (items && items.length > 0) |
81 | 139 |
|
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) |
85 | 143 |
|
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) => { |
169 | 156 | const maybeSelectableItemProps = {} as any
|
170 | 157 |
|
171 | 158 | if (selectable) {
|
172 |
| - maybeSelectableItemProps.selected = index === selectedIndex |
| 159 | + maybeSelectableItemProps.selected = index === state.selectedIndex |
173 | 160 | }
|
174 | 161 |
|
175 |
| - const itemProps = () => ({ |
176 |
| - className: List.slotClassNames.item, |
177 |
| - ..._.pick(this.props, List.itemProps), |
178 |
| - ...maybeSelectableItemProps, |
179 |
| - index, |
180 |
| - }) |
181 |
| - |
182 | 162 | 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, |
185 | 169 | })
|
186 | 170 | })
|
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, |
188 | 195 | }
|
| 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 |
189 | 215 |
|
190 | 216 | List.create = createShorthandFactory({ Component: List, mappedArrayProp: 'items' })
|
191 | 217 |
|
|
0 commit comments