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

Commit 3850afe

Browse files
authored
feat(List+Dropdown): wrapper for custom scrollbar in list (#2092)
wrapper allowing injecting custom scrollbar in list
1 parent 6e8ab70 commit 3850afe

File tree

4 files changed

+70
-15
lines changed

4 files changed

+70
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2727
### Features
2828
- Allow `useRef` hook used for storing debugging data to be defined in any order with other hooks in functional components @layershifter, @mnajdova ([#2236](https://github.com/microsoft/fluent-ui-react/pull/2236))
2929
- Add `useStyles()` hook to use theming capabilities in custom components @layershifter, @mnajdova ([#2217](https://github.com/microsoft/fluent-ui-react/pull/2217))
30+
- Add optional wrapper function to `List` which can be used to inject custom scrollbars to `Dropdown` @jurokapsiar ([#2092](https://github.com/microsoft/fluent-ui-react/pull/2092))
3031

3132
### Documentation
3233
- Add per-component performance charts @miroslavstastny ([#2240](https://github.com/microsoft/fluent-ui-react/pull/2240))

docs/src/prototypes/customScrollbar/index.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react'
22
import * as _ from 'lodash'
33
import Scrollbars from 'react-custom-scrollbars'
4-
import { Text, Menu, List, Button, Popup, Dialog } from '@fluentui/react'
4+
import { Text, Menu, List, Button, Popup, Dialog, Dropdown } from '@fluentui/react'
55
import { PrototypeSection, ComponentPrototype } from '../Prototypes'
66

77
const ScrollbarMenuPrototype = () => {
@@ -71,6 +71,28 @@ const ScrollbarListPrototype = () => {
7171
)
7272
}
7373

74+
const ScrollbarDropdownPrototype = () => {
75+
const items = _.range(50).map((i: number) => ({
76+
header: `Header ${i}`,
77+
content: `Content ${i}`,
78+
key: `item-${i}`,
79+
}))
80+
81+
return (
82+
<div>
83+
<Dropdown
84+
items={items}
85+
list={{ wrap: children => <Scrollbars style={{ height: '20rem' }}>{children}</Scrollbars> }}
86+
/>
87+
<Dropdown
88+
search
89+
items={items}
90+
list={{ wrap: children => <Scrollbars style={{ height: '20rem' }}>{children}</Scrollbars> }}
91+
/>
92+
</div>
93+
)
94+
}
95+
7496
const CustomScrollbarPrototypes: React.FC = () => {
7597
return (
7698
<PrototypeSection title="Custom Scrollbar">
@@ -93,6 +115,9 @@ const CustomScrollbarPrototypes: React.FC = () => {
93115
<ComponentPrototype title="List" description="Scrollbar can be integrated in selectable List">
94116
<ScrollbarListPrototype />
95117
</ComponentPrototype>
118+
<ComponentPrototype title="Dropdown" description="Scrollbar can be integrated in Dropdown">
119+
<ScrollbarDropdownPrototype />
120+
</ComponentPrototype>
96121
</PrototypeSection>
97122
)
98123
}

packages/react/src/components/Dropdown/Dropdown.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
UIComponentProps,
3333
isFromKeyboard,
3434
} from '../../utils'
35-
import List from '../List/List'
35+
import List, { ListProps } from '../List/List'
3636
import DropdownItem, { DropdownItemProps } from './DropdownItem'
3737
import DropdownSelectedItem, { DropdownSelectedItemProps } from './DropdownSelectedItem'
3838
import DropdownSearchInput, { DropdownSearchInputProps } from './DropdownSearchInput'
@@ -136,6 +136,9 @@ export interface DropdownProps
136136
/** Used when comparing two items in multiple selection. Default comparison is by the header prop. */
137137
itemToValue?: (item: ShorthandValue<DropdownItemProps>) => any
138138

139+
/** A slot for dropdown list. */
140+
list?: ShorthandValue<ListProps>
141+
139142
/** A dropdown can show that it is currently loading data. */
140143
loading?: boolean
141144

@@ -280,6 +283,7 @@ class Dropdown extends AutoControlledComponent<WithAsProp<DropdownProps>, Dropdo
280283
items: customPropTypes.collectionShorthand,
281284
itemToString: PropTypes.func,
282285
itemToValue: PropTypes.func,
286+
list: customPropTypes.itemShorthand,
283287
loading: PropTypes.bool,
284288
loadingMessage: customPropTypes.itemShorthand,
285289
moveFocusOnTab: PropTypes.bool,
@@ -327,6 +331,7 @@ class Dropdown extends AutoControlledComponent<WithAsProp<DropdownProps>, Dropdo
327331
// targets DropdownItem shorthand objects
328332
return (item as any).header || String(item)
329333
},
334+
list: {},
330335
position: 'below',
331336
toggleIndicator: {},
332337
triggerButton: {},
@@ -653,7 +658,7 @@ class Dropdown extends AutoControlledComponent<WithAsProp<DropdownProps>, Dropdo
653658
getInputProps: (options?: GetInputPropsOptions) => any,
654659
rtl: boolean,
655660
) {
656-
const { align, offset, position, search, unstable_pinned } = this.props
661+
const { align, offset, position, search, unstable_pinned, list } = this.props
657662
const { open } = this.state
658663
const items = open ? this.renderItems(styles, variables, getItemProps, highlightedIndex) : []
659664
const { innerRef, ...accessibilityMenuProps } = getMenuProps(
@@ -695,16 +700,27 @@ class Dropdown extends AutoControlledComponent<WithAsProp<DropdownProps>, Dropdo
695700
unstable_pinned={unstable_pinned}
696701
positioningDependencies={[items.length]}
697702
>
698-
<List
699-
className={Dropdown.slotClassNames.itemsList}
700-
{...accessibilityMenuProps}
701-
styles={styles.list}
702-
tabIndex={search ? undefined : -1} // needs to be focused when trigger button is activated.
703-
aria-hidden={!open}
704-
onFocus={this.handleTriggerButtonOrListFocus}
705-
onBlur={this.handleListBlur}
706-
items={items}
707-
/>
703+
{List.create(list, {
704+
defaultProps: () => ({
705+
className: Dropdown.slotClassNames.itemsList,
706+
...accessibilityMenuProps,
707+
styles: styles.list,
708+
items,
709+
tabIndex: search ? undefined : -1, // needs to be focused when trigger button is activated.
710+
'aria-hidden': !open,
711+
}),
712+
713+
overrideProps: (predefinedProps: ListProps) => ({
714+
onFocus: (e: React.SyntheticEvent<HTMLElement>, listProps: IconProps) => {
715+
this.handleTriggerButtonOrListFocus()
716+
_.invoke(predefinedProps, 'onClick', e, listProps)
717+
},
718+
onBlur: (e: React.SyntheticEvent<HTMLElement>, listProps: IconProps) => {
719+
this.handleListBlur(e)
720+
_.invoke(predefinedProps, 'onBlur', e, listProps)
721+
},
722+
}),
723+
})}
708724
</Popper>
709725
</Ref>
710726
)

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import {
1212
commonPropTypes,
1313
rtlTextContainer,
1414
applyAccessibilityKeyHandlers,
15+
createShorthandFactory,
16+
ShorthandFactory,
1517
} from '../../utils'
1618
import ListItem, { ListItemProps } from './ListItem'
1719
import {
1820
WithAsProp,
1921
ComponentEventHandler,
2022
withSafeTypeForAs,
2123
ShorthandCollection,
24+
ReactChildren,
2225
} from '../../types'
2326

2427
export interface ListSlotClassNames {
@@ -62,6 +65,9 @@ export interface ListProps extends UIComponentProps, ChildrenComponentProps {
6265

6366
/** A horizontal list displays elements horizontally. */
6467
horizontal?: boolean
68+
69+
/** An optional wrapper function. */
70+
wrap?: (children: ReactChildren) => React.ReactNode
6571
}
6672

6773
export interface ListState {
@@ -91,11 +97,13 @@ class List extends AutoControlledComponent<WithAsProp<ListProps>, ListState> {
9197
defaultSelectedIndex: PropTypes.number,
9298
onSelectedIndexChange: PropTypes.func,
9399
horizontal: PropTypes.bool,
100+
wrap: PropTypes.func,
94101
}
95102

96103
static defaultProps = {
97104
as: 'ul',
98105
accessibility: listBehavior as Accessibility,
106+
wrap: children => children,
99107
}
100108

101109
static autoControlledProps = ['selectedIndex']
@@ -116,6 +124,8 @@ class List extends AutoControlledComponent<WithAsProp<ListProps>, ListState> {
116124
'variables',
117125
]
118126

127+
static create: ShorthandFactory<ListProps>
128+
119129
handleItemOverrides = (predefinedProps: ListItemProps) => {
120130
const { selectable } = this.props
121131

@@ -135,7 +145,8 @@ class List extends AutoControlledComponent<WithAsProp<ListProps>, ListState> {
135145
}
136146

137147
renderComponent({ ElementType, classes, accessibility, unhandledProps }) {
138-
const { children } = this.props
148+
const { children, items, wrap } = this.props
149+
const hasContent = childrenExist(children) || (items && items.length > 0)
139150

140151
return (
141152
<ElementType
@@ -145,7 +156,7 @@ class List extends AutoControlledComponent<WithAsProp<ListProps>, ListState> {
145156
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
146157
className={classes.root}
147158
>
148-
{childrenExist(children) ? children : this.renderItems()}
159+
{hasContent && wrap(childrenExist(children) ? children : this.renderItems())}
149160
</ElementType>
150161
)
151162
}
@@ -176,6 +187,8 @@ class List extends AutoControlledComponent<WithAsProp<ListProps>, ListState> {
176187
}
177188
}
178189

190+
List.create = createShorthandFactory({ Component: List, mappedArrayProp: 'items' })
191+
179192
/**
180193
* A List displays a group of related sequential items.
181194
*

0 commit comments

Comments
 (0)