-
Notifications
You must be signed in to change notification settings - Fork 227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(menu): add component #786
Changes from 30 commits
cae6b6d
e237d30
f6cadef
14a1183
e80d458
8d0b52a
cb1694b
aafb2d1
4900f4d
3abb5f4
61b076b
05c48dd
8a2196f
6ee5ad7
85c0b93
93bf167
c605cef
bb4c79b
06e2ba6
8ff2d9f
667ed80
f2c0805
9ded538
7521f67
b10563c
395520d
1b9b7a1
8ac8668
a5fe91e
cb75d24
949fd9e
e70dee4
3a8a01e
c6930a4
ed4054f
d074619
9c50842
f93bcdf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,24 +23,24 @@ | |
import * as React from 'react'; | ||
import classnames from 'classnames'; | ||
import {MDCListFoundation} from '@material/list/foundation'; | ||
import {ListItemContext, ListItemContextShape} from './index'; | ||
import {closest} from '@material/dom/ponyfill'; | ||
|
||
export interface ListItemProps<T> extends React.HTMLProps<T> { | ||
export interface ListItemProps<T extends HTMLElement = HTMLElement> extends React.HTMLProps<T>, ListItemContextShape { | ||
checkboxList?: boolean; | ||
radioList?: boolean; | ||
onKeyDown?: React.KeyboardEventHandler<T>; | ||
onClick?: React.MouseEventHandler<T>; | ||
onFocus?: React.FocusEventHandler<T>; | ||
onBlur?: React.FocusEventHandler<T>; | ||
tag?: string; | ||
activated?: boolean; | ||
selected?: boolean; | ||
onDestroy?: () => void; | ||
ref?: React.Ref<any>; | ||
}; | ||
|
||
export interface ListItemState { | ||
tabIndex?: number; | ||
} | ||
|
||
export default class ListItem<T extends HTMLElement = HTMLElement> extends React.Component< | ||
ListItemProps<T>, | ||
{} | ||
> { | ||
export class ListItemBase<T extends HTMLElement = HTMLElement> extends React.Component< | ||
ListItemProps<T>, ListItemState> { | ||
private listItemElement = React.createRef<T>(); | ||
|
||
static defaultProps: Partial<ListItemProps<HTMLElement>> = { | ||
|
@@ -54,15 +54,54 @@ export default class ListItem<T extends HTMLElement = HTMLElement> extends React | |
onBlur: () => {}, | ||
onDestroy: () => {}, | ||
tag: 'li', | ||
handleClick: () => {}, | ||
handleKeyDown: () => {}, | ||
handleBlur: () => {}, | ||
handleFocus: () => {}, | ||
getListItemInitialTabIndex: () => -1, | ||
getClassNamesFromList: () => ({}), | ||
}; | ||
|
||
state = { | ||
tabIndex: this.props.tabIndex, | ||
}; | ||
|
||
get listElements(): Element[] { | ||
if (this.listItemElement.current) { | ||
const listElement = closest(this.listItemElement.current, `.${MDCListFoundation.cssClasses.ROOT}`); | ||
if (!listElement) return []; | ||
return [].slice.call( | ||
listElement.querySelectorAll(MDCListFoundation.strings.ENABLED_ITEMS_SELECTOR) | ||
); | ||
} | ||
return []; | ||
} | ||
|
||
componentDidMount() { | ||
this.initializeTabIndex(); | ||
} | ||
|
||
componentDidUpdate(prevProps: ListItemProps) { | ||
if (prevProps.tabIndex !== this.props.tabIndex) { | ||
this.setState({tabIndex: this.props.tabIndex}); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
this.props.onDestroy!(); | ||
if (this.listItemElement.current) { | ||
const index = this.getIndex(this.listItemElement.current); | ||
this.props.onDestroy!(index); | ||
} | ||
} | ||
|
||
get classes() { | ||
const {className, activated, disabled, selected} = this.props; | ||
return classnames('mdc-list-item', className, { | ||
const {className, activated, disabled, selected, getClassNamesFromList} = this.props; | ||
let classesFromList = ['']; | ||
if (this.listItemElement.current) { | ||
const index = this.getIndex(this.listItemElement.current); | ||
classesFromList = getClassNamesFromList!()[index]; | ||
} | ||
return classnames('mdc-list-item', className, classesFromList, { | ||
[MDCListFoundation.cssClasses.LIST_ITEM_ACTIVATED_CLASS]: activated, | ||
[MDCListFoundation.cssClasses.LIST_ITEM_SELECTED_CLASS]: selected, | ||
'mdc-list-item--disabled': disabled, | ||
|
@@ -81,6 +120,42 @@ export default class ListItem<T extends HTMLElement = HTMLElement> extends React | |
return null; | ||
} | ||
|
||
private initializeTabIndex = () => { | ||
if (this.listItemElement.current) { | ||
const index = this.getIndex(this.listItemElement.current); | ||
const tabIndex = this.props.getListItemInitialTabIndex!(index); | ||
this.setState({tabIndex}); | ||
} | ||
} | ||
|
||
getIndex = (listElement: Element) => { | ||
return this.listElements.indexOf(listElement); | ||
} | ||
|
||
handleClick = (e: React.MouseEvent<any>) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are moved from list/index.tsx |
||
const {onClick} = this.props; | ||
onClick!(e); | ||
this.props.handleClick!(e, this.getIndex(e.currentTarget)); | ||
} | ||
|
||
handleKeyDown = (e: React.KeyboardEvent<any>) => { | ||
const {onKeyDown} = this.props; | ||
onKeyDown!(e); | ||
this.props.handleKeyDown!(e, this.getIndex(e.currentTarget)); | ||
} | ||
|
||
handleFocus = (e: React.FocusEvent<any>) => { | ||
const {onFocus} = this.props; | ||
onFocus!(e); | ||
this.props.handleFocus!(e, this.getIndex(e.currentTarget)); | ||
} | ||
|
||
handleBlur = (e: React.FocusEvent<any>) => { | ||
const {onBlur} = this.props; | ||
onBlur!(e); | ||
this.props.handleBlur!(e, this.getIndex(e.currentTarget)); | ||
} | ||
|
||
render() { | ||
const { | ||
/* eslint-disable no-unused-vars */ | ||
|
@@ -90,21 +165,51 @@ export default class ListItem<T extends HTMLElement = HTMLElement> extends React | |
checkboxList, | ||
radioList, | ||
onDestroy, | ||
onClick, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused, what is the difference between onClick and handleClick ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
onKeyDown, | ||
onFocus, | ||
onBlur, | ||
handleClick, | ||
handleKeyDown, | ||
handleFocus, | ||
handleBlur, | ||
getListItemInitialTabIndex, | ||
getClassNamesFromList, | ||
tabIndex, | ||
/* eslint-enable no-unused-vars */ | ||
tag: Tag, | ||
...otherProps | ||
} = this.props; | ||
|
||
return ( | ||
// https://github.com/Microsoft/TypeScript/issues/28892 | ||
// @ts-ignore | ||
<Tag | ||
{...otherProps} | ||
{...this.context} | ||
role={this.role} | ||
className={this.classes} | ||
ref={this.listItemElement} | ||
{...otherProps} | ||
onClick={this.handleClick} | ||
onKeyDown={this.handleKeyDown} | ||
onFocus={this.handleFocus} | ||
onBlur={this.handleBlur} | ||
tabIndex={this.state.tabIndex} | ||
> | ||
{this.props.children} | ||
{children} | ||
</Tag> | ||
); | ||
} | ||
} | ||
|
||
const ListItem: React.FunctionComponent<ListItemProps> = (props) => { | ||
return ( | ||
<ListItemContext.Consumer> | ||
{(context) => ( | ||
<ListItemBase {...context} {...props}/> | ||
)} | ||
</ListItemContext.Consumer> | ||
); | ||
}; | ||
|
||
export default ListItem; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The naming here seems inconsistent. You're using
onBlur
andhandleClick
. I recommend being consistent with your event handler names (and generally preferon{EventName}
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onClick
is a native React click handler. I don't want to collide and override this behavior since its native to React. Therefore I must use a different name (handleClick) so that I don't collide with the naming.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍