forked from patternfly/react-component-groups
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BulkSelect component (patternfly#146)
- Loading branch information
Showing
9 changed files
with
525 additions
and
0 deletions.
There are no files selected for viewing
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,97 @@ | ||
import React, { useState } from 'react'; | ||
import BulkSelect, { BulkSelectProps, BulkSelectValue } from '../../packages/module/dist/dynamic/BulkSelect'; | ||
|
||
interface DataItem { | ||
name: string | ||
}; | ||
|
||
const BulkSelectTestComponent = ({ canSelectAll, isDataPaginated }: Omit<BulkSelectProps, 'onSelect' | 'selectedCount' >) => { | ||
const [ selected, setSelected ] = useState<DataItem[]>([]); | ||
|
||
const allData = [ { name: '1' }, { name: '2' }, { name: '3' }, { name: '4' }, { name: '5' }, { name: '6' } ]; | ||
const pageData = [ { name: '1' }, { name: '2' }, { name: '3' }, { name: '4' }, { name: '5' } ]; | ||
const pageDataNames = pageData.map((item) => item.name); | ||
const pageSelected = pageDataNames.every(item => selected.find(selectedItem => selectedItem.name === item)); | ||
|
||
const handleBulkSelect = (value: BulkSelectValue) => { | ||
value === BulkSelectValue.none && setSelected([]); | ||
value === BulkSelectValue.page && setSelected(pageData); | ||
value === BulkSelectValue.all && setSelected(allData); | ||
value === BulkSelectValue.nonePage && setSelected(selected.filter(item => !pageDataNames.includes(item.name)))}; | ||
|
||
return ( | ||
<BulkSelect | ||
isDataPaginated={isDataPaginated} | ||
canSelectAll={canSelectAll} | ||
pageCount={pageData.length} | ||
totalCount={allData.length} | ||
selectedCount={selected.length} | ||
pageSelected={pageSelected} | ||
pagePartiallySelected={pageDataNames.some(item => selected.find(selectedItem => selectedItem.name === item)) && !pageSelected} | ||
onSelect={handleBulkSelect} | ||
/> | ||
); | ||
}; | ||
|
||
describe('BulkSelect', () => { | ||
it('renders the bulk select without all', () => { | ||
cy.mount( | ||
<BulkSelectTestComponent /> | ||
); | ||
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').should('exist'); | ||
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').click(); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-all"]').should('not.exist'); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-page"]').should('exist'); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-none"]').should('exist'); | ||
|
||
cy.contains('0 selected').should('not.exist'); | ||
}); | ||
|
||
it('renders the bulk select with all and without page', () => { | ||
cy.mount( | ||
<BulkSelectTestComponent canSelectAll isDataPaginated={false} /> | ||
); | ||
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').should('exist'); | ||
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').click(); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-all"]').should('exist'); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-page"]').should('not.exist'); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-none"]').should('exist'); | ||
|
||
cy.contains('0 selected').should('not.exist'); | ||
}); | ||
|
||
it('renders the bulk select with data', () => { | ||
cy.mount( | ||
<BulkSelectTestComponent canSelectAll /> | ||
); | ||
|
||
// Initial state | ||
cy.get('input[type="checkbox"]').each(($checkbox) => { | ||
cy.wrap($checkbox).should('not.be.checked'); | ||
}); | ||
|
||
// Checkbox select | ||
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').first().click(); | ||
cy.get('input[type="checkbox"]').should('be.checked'); | ||
cy.contains('5 selected').should('exist'); | ||
|
||
// Select none | ||
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').first().click({ force: true }); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-none"]').first().click(); | ||
cy.get('input[type="checkbox"]').should('not.be.checked'); | ||
|
||
// Select all | ||
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').first().click({ force: true }); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-all"]').first().click(); | ||
cy.contains('6 selected').should('exist'); | ||
|
||
// Checkbox deselect | ||
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').first().click({ force: true }); | ||
cy.contains('1 selected').should('exist'); | ||
|
||
// Select page | ||
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').first().click({ force: true }); | ||
cy.get('[data-ouia-component-id="BulkSelect-select-page"]').first().click(); | ||
cy.contains('5 selected').should('exist'); | ||
}); | ||
}); |
37 changes: 37 additions & 0 deletions
37
...nfly-docs/content/extensions/component-groups/examples/BulkSelect/BulkSelect.md
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,37 @@ | ||
--- | ||
# Sidenav top-level section | ||
# should be the same for all markdown files | ||
section: extensions | ||
subsection: Component groups | ||
# Sidenav secondary level section | ||
# should be the same for all markdown files | ||
id: Bulk select | ||
# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility) | ||
source: react | ||
# If you use typescript, the name of the interface to display props for | ||
# These are found through the sourceProps function provided in patternfly-docs.source.js | ||
propComponents: ['BulkSelect'] | ||
sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/BulkSelect/BulkSelect.md | ||
--- | ||
import { useState } from 'react'; | ||
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect'; | ||
|
||
The **bulk select** provides a way of selecting data records in batches. You can select all data at once, all data on current page or deselect all. | ||
|
||
## Examples | ||
|
||
### Basic paginated bulk select | ||
|
||
To display a default bulk select, you need to pass number of selected items using `selectedCount`, the `onSelect` callback accepting bulk select option values and selecting data accordingly, `pageCount` defining number of items on the current page, `pageSelected` and `pagePartiallySelected` boolean flags to define the state os the bulk select checkbox.. | ||
|
||
```js file="./BulkSelectExample.tsx" | ||
|
||
``` | ||
|
||
### Bulk select with all option | ||
|
||
To display an option for selecting all data at once, pass `canSelectAll` flag together with `totalCount` of data entries. You can also remove the page select option by setting `isDataPaginated` to `false`, | ||
|
||
```js file="./BulkSelectAllExample.tsx" | ||
|
||
``` |
28 changes: 28 additions & 0 deletions
28
...fly-docs/content/extensions/component-groups/examples/BulkSelect/BulkSelectAllExample.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,28 @@ | ||
import React, { useState } from 'react'; | ||
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect'; | ||
|
||
const allData = [ "Item 1", "Item 2" , "Item 3", "Item4", "Item 5" ]; | ||
const pageData = [ "Item 1", "Item 2" ]; | ||
|
||
export const BasicExample: React.FunctionComponent = () => { | ||
const [ selected, setSelected ] = useState<string[]>(pageData); | ||
|
||
const handleBulkSelect = (value: BulkSelectValue) => { | ||
value === BulkSelectValue.none && setSelected([]); | ||
value === BulkSelectValue.all && setSelected(allData); | ||
value === BulkSelectValue.nonePage && setSelected(selected.filter(item => !pageData.includes(item))); | ||
value === BulkSelectValue.page && setSelected(pageData); | ||
}; | ||
|
||
return ( | ||
<BulkSelect | ||
canSelectAll | ||
selectedCount={selected.length} | ||
pageCount={pageData.length} | ||
totalCount={allData.length} | ||
onSelect={handleBulkSelect} | ||
pageSelected={pageData.every(item => selected.includes(item))} | ||
pagePartiallySelected={pageData.some(item => selected.includes(item)) && !pageData.every(item => selected.includes(item))} | ||
/> | ||
); | ||
} |
26 changes: 26 additions & 0 deletions
26
...ernfly-docs/content/extensions/component-groups/examples/BulkSelect/BulkSelectExample.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,26 @@ | ||
import React, { useState } from 'react'; | ||
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect'; | ||
|
||
const allData = [ "Item 1", "Item 2" , "Item 3", "Item4", "Item 5" ]; | ||
const pageData = [ "Item 1", "Item 2" ]; | ||
|
||
export const BasicExample: React.FunctionComponent = () => { | ||
const [ selected, setSelected ] = useState<string[]>([]); | ||
|
||
const handleBulkSelect = (value: BulkSelectValue) => { | ||
value === BulkSelectValue.none && setSelected([]); | ||
value === BulkSelectValue.all && setSelected(allData); | ||
value === BulkSelectValue.nonePage && setSelected(selected.filter(item => !pageData.includes(item))); | ||
value === BulkSelectValue.page && setSelected(pageData); | ||
}; | ||
|
||
return ( | ||
<BulkSelect | ||
selectedCount={selected.length} | ||
pageCount={pageData.length} | ||
onSelect={handleBulkSelect} | ||
pageSelected={pageData.every(item => selected.includes(item))} | ||
pagePartiallySelected={pageData.some(item => selected.includes(item)) && !pageData.every(item => selected.includes(item))} | ||
/> | ||
); | ||
} |
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,18 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import BulkSelect from './BulkSelect'; | ||
|
||
describe('BulkSelect component', () => { | ||
test('should render', () => { | ||
expect(render( | ||
<BulkSelect | ||
canSelectAll | ||
pageCount={5} | ||
totalCount={10} | ||
selectedCount={2} | ||
pageSelected={false} | ||
pagePartiallySelected={true} | ||
onSelect={() => null} | ||
/>)).toMatchSnapshot(); | ||
}); | ||
}); |
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,136 @@ | ||
import React, { useMemo, useState } from 'react'; | ||
import { | ||
Dropdown, | ||
DropdownItem, | ||
DropdownList, | ||
DropdownProps, | ||
MenuToggle, | ||
MenuToggleCheckbox, | ||
MenuToggleCheckboxProps, | ||
MenuToggleElement, | ||
Text | ||
} from '@patternfly/react-core'; | ||
|
||
export const BulkSelectValue = { | ||
all: 'all', | ||
none: 'none', | ||
page: 'page', | ||
nonePage: 'nonePage' | ||
} as const; | ||
|
||
export type BulkSelectValue = typeof BulkSelectValue[keyof typeof BulkSelectValue]; | ||
|
||
export interface BulkSelectProps extends Omit<DropdownProps, 'toggle' | 'onSelect'> { | ||
/** BulkSelect className */ | ||
className?: string; | ||
/** Indicates whether selectable items are paginated */ | ||
isDataPaginated?: boolean; | ||
/** Indicates whether "Select all" option should be available */ | ||
canSelectAll?: boolean; | ||
/** Number of entries present in current page */ | ||
pageCount?: number; | ||
/** Number of selected entries */ | ||
selectedCount: number; | ||
/** Number of all entries */ | ||
totalCount?: number; | ||
/** Indicates if ALL current page items are selected */ | ||
pageSelected?: boolean; | ||
/** Indicates if ONLY some current page items are selected */ | ||
pagePartiallySelected?: boolean; | ||
/** Callback called on item select */ | ||
onSelect: (value: BulkSelectValue) => void; | ||
/** Custom OUIA ID */ | ||
ouiaId?: string; | ||
/** Additional props for MenuToggleCheckbox */ | ||
menuToggleCheckboxProps?: Omit<MenuToggleCheckboxProps, 'onChange' | 'isChecked' | 'instance' | 'ref'>; | ||
} | ||
|
||
export const BulkSelect: React.FC<BulkSelectProps> = ({ | ||
isDataPaginated = true, | ||
canSelectAll, | ||
pageSelected, | ||
pagePartiallySelected, | ||
pageCount, | ||
selectedCount = 0, | ||
totalCount, | ||
ouiaId = 'BulkSelect', | ||
onSelect, | ||
menuToggleCheckboxProps, | ||
...props | ||
}: BulkSelectProps) => { | ||
const [ isOpen, setOpen ] = useState(false); | ||
|
||
const splitButtonDropdownItems = useMemo( | ||
() => ( | ||
<> | ||
<DropdownItem ouiaId={`${ouiaId}-select-none`} value={BulkSelectValue.none} key={BulkSelectValue.none}> | ||
Select none (0) | ||
</DropdownItem> | ||
{isDataPaginated && ( | ||
<DropdownItem ouiaId={`${ouiaId}-select-page`} value={BulkSelectValue.page} key={BulkSelectValue.page}> | ||
{`Select page${pageCount ? ` (${pageCount})` : ''}`} | ||
</DropdownItem> | ||
)} | ||
{canSelectAll && ( | ||
<DropdownItem ouiaId={`${ouiaId}-select-all`} value={BulkSelectValue.all} key={BulkSelectValue.all}> | ||
{`Select all${totalCount ? ` (${totalCount})` : ''}`} | ||
</DropdownItem> | ||
)} | ||
</> | ||
), | ||
[ isDataPaginated, canSelectAll, ouiaId, pageCount, totalCount ] | ||
); | ||
|
||
const allOption = isDataPaginated ? BulkSelectValue.page : BulkSelectValue.all; | ||
const noneOption = isDataPaginated ? BulkSelectValue.nonePage : BulkSelectValue.none; | ||
|
||
return ( | ||
<Dropdown | ||
shouldFocusToggleOnSelect | ||
ouiaId={`${ouiaId}-dropdown`} | ||
onSelect={(_e, value) => { | ||
setOpen(!isOpen); | ||
onSelect?.(value as BulkSelectValue); | ||
}} | ||
isOpen={isOpen} | ||
onOpenChange={(isOpen: boolean) => setOpen(isOpen)} | ||
toggle={(toggleRef: React.Ref<MenuToggleElement>) => ( | ||
<MenuToggle | ||
ref={toggleRef} | ||
isExpanded={isOpen} | ||
onClick={() => setOpen(!isOpen)} | ||
aria-label="Bulk select toggle" | ||
data-ouia-component-id={`${ouiaId}-toggle`} | ||
splitButtonOptions={{ | ||
items: [ | ||
<MenuToggleCheckbox | ||
ouiaId={`${ouiaId}-checkbox`} | ||
id={`${ouiaId}-checkbox`} | ||
key="bulk-select-checkbox" | ||
aria-label={`Select ${allOption}`} | ||
isChecked={ | ||
(isDataPaginated && pagePartiallySelected) || | ||
(!isDataPaginated && selectedCount > 0) | ||
? null | ||
: pageSelected || selectedCount === totalCount | ||
} | ||
onChange={(checked) => onSelect?.(!checked || checked === null ? noneOption : allOption)} | ||
{...menuToggleCheckboxProps} | ||
/>, | ||
selectedCount > 0 ? ( | ||
<Text ouiaId={`${ouiaId}-text`} key="bulk-select-text"> | ||
{`${selectedCount} selected`} | ||
</Text> | ||
) : null | ||
] | ||
}} | ||
/> | ||
)} | ||
{...props} | ||
> | ||
<DropdownList>{splitButtonDropdownItems}</DropdownList> | ||
</Dropdown> | ||
); | ||
}; | ||
|
||
export default BulkSelect; |
Oops, something went wrong.