-
Notifications
You must be signed in to change notification settings - Fork 377
feat(DataToolbar): Add Data Toolbar component to experimental #2861
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
Changes from all commits
45d3001
c3b0153
ab00d13
926dc17
ba067ab
a5d6dca
80204b0
8e6e359
194da1f
2b50f75
6c92f99
e98968e
bb66af1
c678b01
0ad8af3
b112b4f
eb272c0
a103af9
04ffdfc
f44297e
f2ccde8
abe78d6
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 |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import * as React from 'react'; | ||
| import styles from '@patternfly/react-styles/css/components/DataToolbar/data-toolbar'; | ||
| import { css } from '@patternfly/react-styles'; | ||
| import { DataToolbarExpandableContent } from './DataToolbarExpandableContent'; | ||
| import { DataToolbarContext } from './DataToolbarUtils'; | ||
|
|
||
| export interface DataToolbarProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Classes applied to root element of the Data toolbar */ | ||
| className?: string; | ||
| /** Content to be rendered as rows in the Data toolbar */ | ||
| children?: React.ReactNode; | ||
| /** Flag indicating if a Data toolbar toggle group's expandable content is expanded */ | ||
| isExpanded?: boolean; | ||
| /** A callback for setting the isExpanded flag */ | ||
| toggleIsExpanded?: () => void; | ||
| /** Id of the Data toolbar */ | ||
| id: string; | ||
| } | ||
|
|
||
| export interface DataToolbarState { | ||
nicolethoen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /** Flag indicating the if the expandable content's expanded state is consumer managed or not */ | ||
| isConsumerManagedToggleGroup: boolean; | ||
| /** Flag indicating if the component managed state has expanded content or not */ | ||
| componentManagedIsExpanded: boolean; | ||
| } | ||
|
|
||
| export class DataToolbar extends React.Component<DataToolbarProps, DataToolbarState> { | ||
| private expandableContentRef = React.createRef<HTMLDivElement>(); | ||
|
|
||
| static defaultProps = { | ||
| isExpanded: false | ||
| }; | ||
|
|
||
| constructor(props: DataToolbarProps) { | ||
nicolethoen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| super(props); | ||
|
|
||
| this.state = { | ||
| isConsumerManagedToggleGroup: props.isExpanded || !!props.toggleIsExpanded, | ||
| componentManagedIsExpanded: false | ||
| }; | ||
| } | ||
|
|
||
| toggleIsExpanded = () => { | ||
nicolethoen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| this.setState((prevState) => ({ | ||
| componentManagedIsExpanded: !prevState.componentManagedIsExpanded | ||
| })); | ||
| } | ||
|
|
||
| closeExpandableContent = () => { | ||
| this.setState(() => ({ | ||
| componentManagedIsExpanded: false | ||
| })); | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| const { isConsumerManagedToggleGroup } = this.state; | ||
|
|
||
| if ( !isConsumerManagedToggleGroup ) { | ||
| window.addEventListener('resize', this.closeExpandableContent); | ||
| } | ||
| } | ||
|
|
||
| componentWillUnmount() { | ||
| const { isConsumerManagedToggleGroup } = this.state; | ||
| if (isConsumerManagedToggleGroup) { | ||
| window.removeEventListener('resize', this.closeExpandableContent); | ||
| } | ||
| } | ||
|
|
||
| render() { | ||
|
|
||
| const { className, children, isExpanded, toggleIsExpanded, id, ...props} = this.props; | ||
| const { isConsumerManagedToggleGroup, componentManagedIsExpanded } = this.state; | ||
|
|
||
| const expandableContentId = `${id}-expandable-content`; | ||
|
|
||
| return ( | ||
| <div className={css(styles.dataToolbar, className)} id={id} {...props}> | ||
| <DataToolbarContext.Provider | ||
| value={ | ||
| { | ||
| isExpanded: isConsumerManagedToggleGroup ? isExpanded : componentManagedIsExpanded, | ||
| toggleIsExpanded: isConsumerManagedToggleGroup ? toggleIsExpanded : this.toggleIsExpanded, | ||
| expandableContentRef: this.expandableContentRef, | ||
| expandableContentId | ||
| } | ||
| } | ||
| > | ||
| {children} | ||
| </DataToolbarContext.Provider> | ||
| <DataToolbarExpandableContent | ||
| id={expandableContentId} | ||
| isExpanded={isConsumerManagedToggleGroup ? isExpanded : componentManagedIsExpanded} | ||
| expandableContentRef={this.expandableContentRef} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import * as React from 'react'; | ||
| import styles from '@patternfly/react-styles/css/components/DataToolbar/data-toolbar'; | ||
| import { css } from '@patternfly/react-styles'; | ||
|
|
||
| import { DataToolbarBreakpointMod, formatBreakpointMods } from './DataToolbarUtils'; | ||
|
|
||
| export interface DataToolbarContentProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Classes applied to root element of the Data toolbar content row */ | ||
| className?: string; | ||
| /** An array of objects representing the various modifiers to apply to the content row at various breakpoints */ | ||
| breakpointMods?: DataToolbarBreakpointMod[]; | ||
| /** Content to be rendered as children of the content row */ | ||
| children?: React.ReactNode; | ||
| } | ||
|
|
||
| export const DataToolbarContent: React.FunctionComponent<DataToolbarContentProps> = ({ | ||
| className, | ||
| children, | ||
| breakpointMods = [] as DataToolbarBreakpointMod[], | ||
| ...props | ||
| }: DataToolbarContentProps) => { | ||
|
|
||
| return ( | ||
| <div | ||
| className={ | ||
| css(styles.dataToolbarContent, | ||
| formatBreakpointMods(breakpointMods), | ||
| className)} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import * as React from 'react'; | ||
| import styles from '@patternfly/react-styles/css/components/DataToolbar/data-toolbar'; | ||
| import { css, getModifier } from '@patternfly/react-styles'; | ||
|
|
||
| import { RefObject } from 'react'; | ||
|
|
||
| export interface DataToolbarExpandableContentProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Classes added to the root element of the Data toolbar expandable content */ | ||
| className?: string; | ||
| /** Flag indicating the expandable content is expanded */ | ||
| isExpanded?: boolean; | ||
| /** Expandable content reference for passing to Data toolbar children */ | ||
| expandableContentRef: RefObject<HTMLDivElement>; | ||
| } | ||
|
|
||
| export const DataToolbarExpandableContent: React.FunctionComponent<DataToolbarExpandableContentProps> = ({ | ||
| className, | ||
| isExpanded = false, | ||
| expandableContentRef, | ||
| ...props | ||
| }: DataToolbarExpandableContentProps) => { | ||
|
|
||
| return ( | ||
| <div | ||
| className={css( | ||
| styles.dataToolbarExpandableContent, | ||
| isExpanded && getModifier(styles, 'expanded'), | ||
| className)} | ||
| ref={expandableContentRef} | ||
| {...props} | ||
| /> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import * as React from 'react'; | ||
| import styles from '@patternfly/react-styles/css/components/DataToolbar/data-toolbar'; | ||
| import { css, getModifier } from '@patternfly/react-styles'; | ||
|
|
||
| import { | ||
| DataToolbarBreakpointMod, | ||
| DataToolbarSpacer, | ||
| formatBreakpointMods, | ||
| formatSpacers | ||
| } from './DataToolbarUtils'; | ||
|
|
||
| export enum DataToolbarGroupVariant { | ||
| 'filter-group' = 'filter-group', | ||
| 'icon-button-group' = 'icon-button-group', | ||
| 'button-group' = 'button-group', | ||
| } | ||
|
|
||
| export interface DataToolbarGroupProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Classes applied to root element of the Data toolbar group */ | ||
| className?: string; | ||
| /** A type modifier which modifies spacing specifically depending on the type of group */ | ||
| variant?: DataToolbarGroupVariant | 'filter-group' | 'icon-button-group' | 'button-group'; | ||
| /** Array of objects representing the various modifiers to apply to the Data toolbar group at various breakpoints */ | ||
| breakpointMods?: DataToolbarBreakpointMod[]; | ||
| /** Array of objects representing the various spacers to apply to the Data toolbar group at various breakpoints */ | ||
| spacers?: DataToolbarSpacer[]; | ||
| /** Array of objects representing the spacers to apply to the items in this group at various breakpoints */ | ||
| itemSpacers?: DataToolbarSpacer[]; | ||
| /** Content to be rendered inside the Data toolbar group */ | ||
| children?: React.ReactNode; | ||
| } | ||
|
|
||
| export const DataToolbarGroup: React.FunctionComponent<DataToolbarGroupProps> = ({ | ||
| breakpointMods = [] as DataToolbarBreakpointMod[], | ||
| spacers = [] as DataToolbarSpacer[], | ||
| itemSpacers = [] as DataToolbarSpacer[], | ||
| className, | ||
| variant, | ||
| children, | ||
| ...props | ||
| }: DataToolbarGroupProps) => { | ||
|
|
||
| return ( | ||
| <div | ||
| className={css( | ||
| styles.dataToolbarGroup, | ||
| variant && getModifier(styles, variant), | ||
| formatBreakpointMods(breakpointMods), | ||
| formatSpacers(itemSpacers, 'pf-m-space-items'), | ||
| formatSpacers(spacers), | ||
| className)} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </div> | ||
| ); | ||
|
|
||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import * as React from 'react'; | ||
| import styles from '@patternfly/react-styles/css/components/DataToolbar/data-toolbar'; | ||
| import { css, getModifier } from '@patternfly/react-styles'; | ||
|
|
||
| import { | ||
| DataToolbarBreakpointMod, | ||
| DataToolbarSpacer, | ||
| formatBreakpointMods, | ||
| formatSpacers | ||
| } from './DataToolbarUtils'; | ||
|
|
||
| export enum DataToolbarItemVariant { | ||
| separator = 'separator', | ||
| 'bulk-select' = 'bulk-select', | ||
| 'overflow-menu' = 'overflow-menu', | ||
| pagination = 'pagination', | ||
| 'search-filter' = 'search-filter', | ||
| label = 'label', | ||
| } | ||
|
|
||
| export interface DataToolbarItemProps extends React.HTMLProps<HTMLDivElement> { | ||
| /** Classes applied to root element of the Data toolbar item */ | ||
| className?: string; | ||
| /** A type modifier which modifies spacing specifically depending on the type of item */ | ||
| variant?: DataToolbarItemVariant | | ||
| 'separator' | 'bulk-select' | 'overflow-menu' | 'pagination' | 'search-filter' | 'label'; | ||
| /** An array of objects representing the various modifiers to apply to the Data toolbar item at various breakpoints */ | ||
| breakpointMods?: DataToolbarBreakpointMod[]; | ||
| /** An array of objects representing the various spacers to apply to the Data toolbar item at various breakpoints */ | ||
| spacers?: DataToolbarSpacer[]; | ||
| /** id for this Data toolbar item */ | ||
| id?: string; | ||
| /** Content to be rendered inside the Data toolbar item */ | ||
| children?: React.ReactNode; | ||
| } | ||
|
|
||
| export const DataToolbarItem: React.FunctionComponent<DataToolbarItemProps> = ({ | ||
| className, | ||
| variant, | ||
| breakpointMods = [] as DataToolbarBreakpointMod[], | ||
| spacers = [] as DataToolbarSpacer[], | ||
| id, | ||
| children, | ||
| ...props | ||
| }: DataToolbarItemProps) => { | ||
|
|
||
| const labelVariant = variant === 'label'; | ||
|
|
||
| return ( | ||
| <div | ||
| className={css( | ||
| styles.dataToolbarItem, | ||
| variant && getModifier(styles, variant), | ||
| formatBreakpointMods(breakpointMods), | ||
| formatSpacers(spacers), | ||
| className)} | ||
| {...labelVariant && { 'aria-hidden': true }} | ||
| id={id} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import * as React from 'react'; | ||
| import * as ReactDOM from 'react-dom'; | ||
| import styles from '@patternfly/react-styles/css/components/DataToolbar/data-toolbar'; | ||
| import { css, getModifier } from '@patternfly/react-styles'; | ||
| import { DataToolbarGroupProps } from './DataToolbarGroup'; | ||
| import { DataToolbarContext } from './DataToolbarUtils'; | ||
| import { Button } from '../../../components/Button'; | ||
|
|
||
| import { | ||
| DataToolbarBreakpointMod, | ||
| DataToolbarSpacer, | ||
| formatBreakpointMods, | ||
| formatSpacers | ||
| } from './DataToolbarUtils'; | ||
|
|
||
| export interface DataToolbarToggleGroupProps extends DataToolbarGroupProps { | ||
| /** An Icon to be rendered when the toggle group has collapsed down */ | ||
| toggleIcon: React.ReactNode; | ||
| /** The breakpoint at which the toggle group is collapsed down */ | ||
| breakpoint: 'md' | 'lg' | 'xl' | '2xl'; | ||
| } | ||
|
|
||
| export class DataToolbarToggleGroup extends React.Component<DataToolbarToggleGroupProps> { | ||
|
|
||
| static defaultProps = { | ||
| breakpointMods: [] as DataToolbarBreakpointMod[], | ||
| spacers: [] as DataToolbarSpacer[], | ||
| }; | ||
|
|
||
| render() { | ||
| const { toggleIcon, breakpoint, variant, breakpointMods, spacers, className, children, ...props } = this.props; | ||
|
|
||
| return ( | ||
| <DataToolbarContext.Consumer> | ||
nicolethoen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| {({ isExpanded, toggleIsExpanded, expandableContentRef, expandableContentId}) => { | ||
| return ( | ||
| <div | ||
| className={css( | ||
| styles.dataToolbarGroup, | ||
| variant && getModifier(styles, variant), | ||
| formatBreakpointMods(breakpointMods), | ||
| formatSpacers(spacers, 'pf-m-space-items'), | ||
| getModifier(styles, 'toggle-group'), | ||
| getModifier(styles, `reveal-on-${breakpoint}`), | ||
| className)} | ||
| {...props} | ||
| > | ||
| <div className={css(styles.dataToolbarToggle)}> | ||
| <Button | ||
| variant="plain" | ||
| onClick={toggleIsExpanded} | ||
| {...isExpanded && { 'aria-expanded': true }} | ||
| // TODO aria-haspopup when isExpanded = true && viewport is smaller than lg global breakpoint | ||
|
Collaborator
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. Is this something we need to implement with this PR, and if not can we open an issue to track it?
Contributor
Author
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. good thought
Contributor
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. Let's open an issue for this.
Contributor
Author
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. Issue created |
||
| aria-controls={expandableContentId} | ||
| > | ||
| {toggleIcon} | ||
| </Button> | ||
| </div> | ||
| {isExpanded ? ReactDOM.createPortal(children, expandableContentRef.current) : children} | ||
| </div> | ||
| ); | ||
| }} | ||
| </DataToolbarContext.Consumer> | ||
| ); | ||
| } | ||
| } | ||
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.
We should add a comment that setting this means that the consumer will be responsible for managing the toogle group expansion. Also how would a consumer find out if the toogle group is currently expanded after construction??? Can we use a getter to return the correct value.
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.
If the consumer is managing the expansion, they would already know, right? If the consumer is not managing the expansion, why do they need to be able to find out if it's currently expanded?
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.
There is some documentation just before the toggle group examples that i believe explains this? should I add more info to that documentation?