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(SimpleList): add simple list #3645
Merged
Merged
Changes from 9 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f9fb57f
feat(SimpleList): add simple list
kmcfaul 73a8be0
feat(SimpleList): track current item
kmcfaul 7a15142
feat(SimpleList): refine features, add onSelect, add tests & integration
kmcfaul 5aad0e3
feat(SimpleList): add id to group. add type, href to item. update exa…
kmcfaul 3befdba
feat(SimpleList): move tests, add alternate callback props
kmcfaul 5c5bc93
feat(SimpleList): fix lint errors, fix custom class not being assigne…
kmcfaul 1acffc4
feat(SimpleList): move simple list to components, rebase with master
kmcfaul fd13d0c
fix(misc- SimpleList, FilterTableDemo): update imports for docs
kmcfaul ff94d3b
feat(SimpleList): add pr feedback
kmcfaul c3131a6
feat(SimpleList): move aria-labelledby to ul
kmcfaul 6ee92df
feat(SimpleList): add pr feedback, aria tags
kmcfaul c0174ee
feat(SimpleList): assign role=list only when grouped
kmcfaul File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
85 changes: 85 additions & 0 deletions
85
packages/patternfly-4/react-core/src/components/SimpleList/SimpleList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import * as React from 'react'; | ||
import { css } from '@patternfly/react-styles'; | ||
import styles from '@patternfly/react-styles/css/components/SimpleList/simple-list'; | ||
import { SimpleListGroup } from './SimpleListGroup'; | ||
import { SimpleListItemProps } from './SimpleListItem'; | ||
import { Omit } from '../../helpers/typeUtils'; | ||
|
||
export interface SimpleListProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onSelect'> { | ||
/** Content rendered inside the SimpleList */ | ||
children?: React.ReactNode; | ||
/** Additional classes added to the SimpleList container */ | ||
className?: string; | ||
/** Callback when an item is selected */ | ||
onSelect?: ( | ||
ref: React.RefObject<HTMLButtonElement> | React.RefObject<HTMLAnchorElement>, | ||
props: SimpleListItemProps | ||
) => void; | ||
} | ||
|
||
export interface SimpleListState { | ||
/** Ref of the current SimpleListItem */ | ||
currentRef: React.RefObject<HTMLButtonElement> | React.RefObject<HTMLAnchorElement>; | ||
} | ||
|
||
interface SimpleListContextProps { | ||
currentRef: React.RefObject<HTMLButtonElement> | React.RefObject<HTMLAnchorElement>; | ||
updateCurrentRef: ( | ||
id: React.RefObject<HTMLButtonElement> | React.RefObject<HTMLAnchorElement>, | ||
props: SimpleListItemProps | ||
) => void; | ||
} | ||
|
||
export const SimpleListContext = React.createContext<Partial<SimpleListContextProps>>({}); | ||
|
||
export class SimpleList extends React.Component<SimpleListProps, SimpleListState> { | ||
static hasWarnBeta = false; | ||
state = { | ||
currentRef: null as React.RefObject<HTMLButtonElement> | React.RefObject<HTMLAnchorElement> | ||
}; | ||
|
||
static defaultProps: SimpleListProps = { | ||
children: null as React.ReactNode, | ||
className: '' | ||
}; | ||
|
||
componentDidMount() { | ||
if (!SimpleList.hasWarnBeta && process.env.NODE_ENV !== 'production') { | ||
// eslint-disable-next-line no-console | ||
console.warn('This component is in beta and subject to change.'); | ||
SimpleList.hasWarnBeta = true; | ||
} | ||
} | ||
|
||
handleCurrentUpdate = ( | ||
newCurrentRef: React.RefObject<HTMLButtonElement> | React.RefObject<HTMLAnchorElement>, | ||
itemProps: SimpleListItemProps | ||
) => { | ||
this.setState({ currentRef: newCurrentRef }); | ||
const { onSelect } = this.props; | ||
onSelect && onSelect(newCurrentRef, itemProps); | ||
}; | ||
|
||
render() { | ||
const { children, className, onSelect, ...props } = this.props; | ||
|
||
let isGrouped = false; | ||
if (children) { | ||
isGrouped = (React.Children.toArray(children)[0] as React.ReactElement).type === SimpleListGroup; | ||
} | ||
|
||
return ( | ||
<SimpleListContext.Provider | ||
value={{ | ||
currentRef: this.state.currentRef, | ||
updateCurrentRef: this.handleCurrentUpdate | ||
}} | ||
> | ||
<div className={css(styles.simpleList, className)} {...props}> | ||
{isGrouped && children} | ||
{!isGrouped && <ul>{children}</ul>} | ||
</div> | ||
</SimpleListContext.Provider> | ||
); | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
packages/patternfly-4/react-core/src/components/SimpleList/SimpleListGroup.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import * as React from 'react'; | ||
import { css } from '@patternfly/react-styles'; | ||
import styles from '@patternfly/react-styles/css/components/SimpleList/simple-list'; | ||
import { Omit } from '../../helpers/typeUtils'; | ||
|
||
export interface SimpleListGroupProps extends Omit<React.HTMLProps<HTMLTableSectionElement>, 'title'> { | ||
/** Content rendered inside the SimpleList group */ | ||
children?: React.ReactNode; | ||
/** Additional classes added to the SimpleList <ul> */ | ||
className?: string; | ||
/** Additional classes added to the SimpleList group title */ | ||
titleClassName?: string; | ||
/** Title of the SimpleList group */ | ||
title?: React.ReactNode; | ||
/** ID of SimpleList group */ | ||
id?: string; | ||
} | ||
|
||
export const SimpleListGroup: React.FunctionComponent<SimpleListGroupProps> = ({ | ||
children = null, | ||
className = '', | ||
title = '', | ||
titleClassName = '', | ||
id = '', | ||
...props | ||
}: SimpleListGroupProps) => ( | ||
<section className={css(styles.simpleListSection)} aria-labelledby={id} {...props}> | ||
<h2 id={id} className={css(styles.simpleListTitle, titleClassName)}> | ||
{title} | ||
</h2> | ||
<ul className={css(className)}>{children}</ul> | ||
</section> | ||
); |
93 changes: 93 additions & 0 deletions
93
packages/patternfly-4/react-core/src/components/SimpleList/SimpleListItem.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import * as React from 'react'; | ||
import { css } from '@patternfly/react-styles'; | ||
import styles from '@patternfly/react-styles/css/components/SimpleList/simple-list'; | ||
import { SimpleListContext } from './SimpleList'; | ||
|
||
export interface SimpleListItemProps { | ||
/** Content rendered inside the SimpleList item */ | ||
children?: React.ReactNode; | ||
/** Additional classes added to the SimpleList <li> */ | ||
className?: string; | ||
/** Component type of the SimpleList item */ | ||
component?: 'button' | 'a'; | ||
/** Additional classes added to the SimpleList <a> or <button> */ | ||
componentClassName?: string; | ||
/** Additional props added to the SimpleList <a> or <button> */ | ||
componentProps?: any; | ||
/** Indicates if the link is current/highlighted */ | ||
isCurrent?: boolean; | ||
/** OnClick callback for the SimpleList item */ | ||
onClick?: (event: React.MouseEvent | React.ChangeEvent) => void; | ||
/** Type of button SimpleList item */ | ||
type?: 'button' | 'submit' | 'reset'; | ||
/** Default hyperlink location */ | ||
href?: string; | ||
} | ||
|
||
export class SimpleListItem extends React.Component<SimpleListItemProps> { | ||
ref = React.createRef<any>(); | ||
static defaultProps: SimpleListItemProps = { | ||
children: null, | ||
className: '', | ||
isCurrent: false, | ||
component: 'button', | ||
componentClassName: '', | ||
type: 'button', | ||
href: '', | ||
onClick: () => {} | ||
}; | ||
|
||
render() { | ||
const { | ||
children, | ||
isCurrent, | ||
className, | ||
component: Component, | ||
componentClassName, | ||
componentProps, | ||
onClick, | ||
type, | ||
href, | ||
...props | ||
} = this.props; | ||
|
||
return ( | ||
<SimpleListContext.Consumer> | ||
{({ currentRef, updateCurrentRef }) => { | ||
const isButton = Component === 'button'; | ||
const isCurrentItem = this.ref && currentRef ? currentRef.current === this.ref.current : isCurrent; | ||
|
||
const additionalComponentProps = isButton | ||
? { | ||
type | ||
} | ||
: { | ||
tabIndex: 0, | ||
href | ||
}; | ||
|
||
return ( | ||
<li className={css(className)} {...props}> | ||
<Component | ||
className={css( | ||
styles.simpleListItemLink, | ||
isCurrentItem && styles.modifiers.current, | ||
componentClassName | ||
)} | ||
onClick={(evt: React.MouseEvent) => { | ||
onClick(evt); | ||
updateCurrentRef(this.ref, this.props); | ||
}} | ||
ref={this.ref} | ||
{...componentProps} | ||
{...additionalComponentProps} | ||
> | ||
{children} | ||
</Component> | ||
</li> | ||
); | ||
}} | ||
</SimpleListContext.Consumer> | ||
); | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
packages/patternfly-4/react-core/src/components/SimpleList/__tests__/SimpleList.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import * as React from 'react'; | ||
import { mount } from 'enzyme'; | ||
import { SimpleList } from '../SimpleList'; | ||
import { SimpleListGroup } from '../SimpleListGroup'; | ||
import { SimpleListItem } from '../SimpleListItem'; | ||
|
||
const items = [ | ||
<SimpleListItem key="i1">Item 1</SimpleListItem>, | ||
<SimpleListItem key="i2">Item 2</SimpleListItem>, | ||
<SimpleListItem key="i3">Item 3</SimpleListItem> | ||
]; | ||
|
||
const anchors = [ | ||
<SimpleListItem key="i1" component="a"> | ||
Item 1 | ||
</SimpleListItem>, | ||
<SimpleListItem key="i2" component="a"> | ||
Item 2 | ||
</SimpleListItem>, | ||
<SimpleListItem key="i3" component="a"> | ||
Item 3 | ||
</SimpleListItem> | ||
]; | ||
|
||
describe('SimpleList', () => { | ||
test('renders content', () => { | ||
const view = mount(<SimpleList>{items}</SimpleList>); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
|
||
test('renders grouped content', () => { | ||
const view = mount( | ||
<SimpleList> | ||
<SimpleListGroup title="Group 1">{items}</SimpleListGroup> | ||
</SimpleList> | ||
); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
|
||
test('onSelect is called when item is selected', () => { | ||
const selectSpy = jest.fn(); | ||
const view = mount(<SimpleList onSelect={selectSpy}>{items}</SimpleList>); | ||
view | ||
.find('button') | ||
.first() | ||
.simulate('click', { target: { value: 'r' } }); | ||
view.update(); | ||
expect(selectSpy).toBeCalled(); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
|
||
test('renders anchor content', () => { | ||
const view = mount(<SimpleList>{anchors}</SimpleList>); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
|
||
test('onSelect is called when anchor item is selected', () => { | ||
const selectSpy = jest.fn(); | ||
const view = mount(<SimpleList onSelect={selectSpy}>{anchors}</SimpleList>); | ||
view | ||
.find('a') | ||
.first() | ||
.simulate('click', { target: { value: 'r' } }); | ||
view.update(); | ||
expect(selectSpy).toBeCalled(); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
}); | ||
|
||
describe('SimpleListGroup', () => { | ||
test('renders content', () => { | ||
const view = mount(<SimpleListGroup title="Group 1">{items}</SimpleListGroup>); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
}); | ||
|
||
describe('SimpleListItem', () => { | ||
test('renders content', () => { | ||
const view = mount(<SimpleListItem>Item 1</SimpleListItem>); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
|
||
test('renders anchor', () => { | ||
const view = mount(<SimpleListItem component="a">Item 1</SimpleListItem>); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
just making sure this is intended, but type can be
submit
orreset
as well if that's what's passed in as propThere 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.
we want to make sure the default type is "button", this line lets the user change that if they wish. otherwise adding a
type
property manually would be caught by the component and not actually set.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.
I'm not sure either way if we want to allow this
@mcarrano