UNPKG

@vtex/styleguide

Version:

> VTEX Styleguide React components ([Docs](https://vtex.github.io/styleguide))

1,075 lines (939 loc) โ€ข 30 kB
<div className="center mw7 pv6"> ![](./table.png) </div> ### Toolbar The toolbar is a bundle of features, including search input, autocomplete, columns visibility toggle, density controls, import and export buttons, extra actions menu using ActionMenu component and a newLine button to help with entry creation (you can see the illustrative diagram in the beginning of the page for a better visualization of this structure) #### InputSearch - A wrapper around `InputSearch` component. The props are the same. #### InputAutocomplete - A wrapper around `AutocompleteInput` component. The props are the same. #### Button Group Represents the group of buttons located at the right. It has other composites that are described down below. ##### Columns - A button that toggles columns visibility. - It is recommended to combine it with the `useTableVisibility` hook. ```ts enum Alignment { Left = 'left', Right = 'right', } ``` | Property | Type | Required | Default | Description | | ------------ | ---------------------------- | -------- | ------- | -------------------------------- | | label | string | โœ… | ๐Ÿšซ | General label | | showAllLabel | string | โœ… | ๐Ÿšซ | Label for the show all button | | hideAllLabel | string | โœ… | ๐Ÿšซ | Label for the hide all button | | visibility | Return of useTableVisibility | โœ… | ๐Ÿšซ | Visibility of the columns | | alignMenu | Alignment | ๐Ÿšซ | ๐Ÿšซ | Menu alignment | | disabled | boolean | ๐Ÿšซ | false | If the button is disabled or not | ##### Density - A button that changes the row's density. - It is recommended to combine it with the `useTableMeasures` hook. ```ts enum Alignment { Left = 'left', Right = 'right', } enum Density { Compact = 'compact', Regular = 'regular', Comfortable = 'comfortable', } ``` | Property | Type | Required | Default | Description | | ---------------- | -------------------------- | -------- | ------- | -------------------------------- | | compactLabel | string | โœ… | ๐Ÿšซ | Label of the compact option | | regularLabel | string | โœ… | ๐Ÿšซ | Label of the regular option | | comfortableLabel | string | โœ… | ๐Ÿšซ | Label of the comfortable option | | alignMenu | Alignment | ๐Ÿšซ | ๐Ÿšซ | Menu alignment | | handleCallback | (density: Density) => void | ๐Ÿšซ | ๐Ÿšซ | Triggered on change density | | disabled | boolean | ๐Ÿšซ | false | If the button is disabled or not | ##### Download - Button to handle download or export actions. | Property | Type | Required | Default | Description | | -------- | ---------- | -------- | ------- | -------------------------------- | | onClick | () => void | โœ… | ๐Ÿšซ | Action on click button | | label | string | ๐Ÿšซ | "" | Button text | | disabled | boolean | ๐Ÿšซ | false | If the button is disabled or not | ##### Upload - Button to handle upload or import actions. | Property | Type | Required | Default | Description | | -------- | ---------- | -------- | ------- | -------------------------------- | | onClick | () => void | โœ… | ๐Ÿšซ | Action on click button | | label | string | ๐Ÿšซ | ๐Ÿšซ | Button text | | disabled | boolean | ๐Ÿšซ | false | If the button is disabled or not | ##### ExtraActions - Button to perform extra actions. ```ts enum Alignment { Left = 'left', Right = 'right', } type MenuAction = { label: string onClick: Function toggle?: { checked: boolean semantic: boolean } id?: number | string } ``` | Property | Type | Required | Default | Description | | --------- | ------------ | -------- | ------- | -------------------------------- | | actions | MenuAction[] | โœ… | ๐Ÿšซ | Action on click button | | label | string | ๐Ÿšซ | ๐Ÿšซ | Button label | | isLoading | boolean | ๐Ÿšซ | false | If the button is loading or not | | disabled | boolean | ๐Ÿšซ | false | If the button is disabled or not | | alignMenu | Alignment | ๐Ÿšซ | ๐Ÿšซ | Menu alignment | ##### NewLine - A button that represents creational purposes. ```ts type MenuAction = { label: string onClick: Function toggle?: { checked: boolean semantic: boolean } id?: number | string } ``` | Property | Type | Required | Default | Description | | --------- | ------------ | -------- | ------- | -------------------------------- | | onClick | () => void | โœ… | ๐Ÿšซ | Action on click button | | label | string | ๐Ÿšซ | ๐Ÿšซ | Button text | | actions | MenuAction[] | ๐Ÿšซ | ๐Ÿšซ | Action on click button | | isLoading | boolean | ๐Ÿšซ | false | If the button is loading or not | | disabled | boolean | ๐Ÿšซ | false | If the button is disabled or not | ##### Working example ```js import Table from '../index' import useTableMeasures from '../hooks/useTableMeasures' import useTableVisibility from '../hooks/useTableVisibility' import Tag from '../../Tag' import Icons from 'react-icons/fa' import data from './sampleData' const columns = [ { id: 'id', title: 'ID', }, { id: 'icon', title: 'Icon', cellRenderer: ({ data, rowHeight, motion }) => ( <Icon name={data} style={motion} height={rowHeight} /> ), }, { id: 'name', title: 'Name', }, { id: 'status', title: 'Status', cellRenderer: ({ data }) => <Status status={data} />, }, ] function Icon({ name, height, style }) { const SelectedIcon = Icons[name] return <SelectedIcon style={style} className="c-muted-1" size={height - 5} /> } function Status({ status }) { const type = status === 'ACTIVE' ? 'success' : 'neutral' return <Tag type={type}>{status}</Tag> } const items = data.payments function reducer(state, action) { switch (action.type) { case 'change': { const { value } = action return { ...state, inputValue: value, } } case 'clear': { return { inputValue: '', displayItems: items, } } case 'submit': { const inputClear = state.input === '' const filterFn = item => item.name.toLowerCase().includes(state.inputValue.toLowerCase()) return { ...state, displayItems: inputClear ? items : items.filter(filterFn), } } default: { return state } } } function ToolbarExample() { const [state, dispatch] = React.useReducer(reducer, { inputValue: '', displayItems: items, }) const visibility = useTableVisibility({ columns, items, hiddenColumns: ['id'], }) const measures = useTableMeasures({ size: state.displayItems.length, }) const inputSearch = { value: state.inputValue, placeholder: 'Search stuff...', onChange: e => dispatch({ type: 'change', value: e.currentTarget.value }), onClear: () => { dispatch({ type: 'clear' }) }, onSubmit: e => { e.preventDefault() dispatch({ type: 'submit' }) }, } const buttonColumns = { label: 'Toggle visible fields', showAllLabel: 'Show All', hideAllLabel: 'Hide All', visibility, } const density = { label: 'Line density', compactLabel: 'Compact', regularLabel: 'Regular', comfortableLabel: 'Comfortable', } const download = { label: 'Export', onClick: () => alert('Clicked EXPORT'), } const upload = { label: 'Import', onClick: () => alert('Clicked IMPORT'), } const extraActions = { label: 'More options', actions: [ { label: 'An action', onClick: () => alert('An action'), }, { label: 'Another action', onClick: () => alert('Another action'), }, { label: 'A third action', onClick: () => alert('A third action'), }, ], } const newLine = { label: 'New', onClick: () => alert('handle new line callback'), actions: [ 'General', 'Desktop & Screen Saver', 'Dock', 'Language & Region', ].map(label => ({ label, onClick: () => alert(`Clicked ${label}`), })), } const emptyState = { label: 'The table is empty', } const empty = React.useMemo( () => state.displayItems.length === 0 || Object.keys(visibility.visibleColumns).length === 0, [visibility.visibleColumns, state.displayItems] ) return ( <Table empty={empty} measures={measures} items={state.displayItems} columns={visibility.visibleColumns} emptyState={emptyState}> <Table.Toolbar> <Table.Toolbar.InputSearch {...inputSearch} /> <Table.Toolbar.ButtonGroup> <Table.Toolbar.ButtonGroup.Columns {...buttonColumns} /> <Table.Toolbar.ButtonGroup.Density {...density} /> <Table.Toolbar.ButtonGroup.Download {...download} /> <Table.Toolbar.ButtonGroup.Upload {...upload} /> <Table.Toolbar.ButtonGroup.ExtraActions {...extraActions} /> <Table.Toolbar.ButtonGroup.NewLine {...newLine} /> </Table.Toolbar.ButtonGroup> </Table.Toolbar> </Table> ) } ;<ToolbarExample /> ``` ### Totalizer This uses the Totalizer component between the toolbar and the table content. You can find the full specs on the [Totalizer specific docs](https://styleguide.vtex.com/#/Components/Display/Totalizer). ```js import Table from '../index' import useTableMeasures from '../hooks/useTableMeasures' import data from './sampleData' import ArrowUp from '../../icon/ArrowUp' import ArrowDown from '../../icon/ArrowDown' const columns = [ { id: 'name', title: 'Name', }, { id: 'manufacturer', title: 'Manufacturer', }, { id: 'qty', title: 'Qty', }, { id: 'costPrice', title: 'Cost', cellRenderer: ({ data }) => <Currency value={data} />, }, { id: 'retailPrice', title: 'Retail', cellRenderer: ({ data }) => <Currency value={data} />, }, ] const formatCurrency = value => parseFloat(value).toFixed(2) function Currency({ value }) { return <span>$ {formatCurrency(value)}</span> } const items = data.products function TotalizerExample() { const measures = useTableMeasures({ size: items.length, }) const totalizer = React.useMemo(() => { const sum = items.reduce( (acc, item) => { const { stock, cost, retail } = acc return { stock: stock + item.qty, cost: cost + item.qty * item.costPrice, retail: retail + item.qty * item.retailPrice, } }, { stock: 0, cost: 0, retail: 0, } ) return { items: [ { label: 'Stock', value: sum.stock, }, { label: 'Total cost', value: `$ ${formatCurrency(sum.cost)}`, iconBackgroundColor: '#fda4a4', icon: <ArrowDown color="#dd1616" size={14} />, }, { label: 'Total retail', value: `$ ${formatCurrency(sum.retail)}`, iconBackgroundColor: '#eafce3', icon: <ArrowUp color="#79B03A" size={14} />, }, { label: 'Expected Profit', value: `$ ${formatCurrency(sum.retail - sum.cost)}`, }, ], } }, [items]) return ( <Table measures={measures} columns={columns} items={items}> <Table.Totalizer {...totalizer} /> </Table> ) } ;<TotalizerExample /> ``` ### Bulk Actions - Bulk actions allow the user to select some or all the rows to apply an action. - It is recommended the usage along with the `EXPERIMENTAL_useCheckboxTree` hook which [has its docs](https://styleguide.vtex.com/#/Components/%F0%9F%91%BB%20Experimental/EXPERIMENTALUseCheckboxTree). - Like the `Toolbar`, `BulkActions` is a compound component. #### Actions ##### Primary - Button to handle primary. | Property | Type | Required | Default | Description | | -------- | ---------- | -------- | ------- | ---------------------- | | label | string | โœ… | ๐Ÿšซ | Button text | | onClick | () => void | โœ… | ๐Ÿšซ | Action on click button | ##### Secondary - Button to handle secondary actions. | Property | Type | Required | Default | Description | | ------------- | --------------------------------- | -------- | ------- | ----------------------- | | label | string | โœ… | ๐Ÿšซ | Button text | | onClick | () => void | โœ… | ๐Ÿšซ | Action on click button | | isDangerous | boolean | ๐Ÿšซ | ๐Ÿšซ | Mark whether the action performs a dangerous option or not | | onActionClick | (e: SecondaryActionProps) => void | ๐Ÿšซ | ๐Ÿšซ | Action on click actions | #### Tail ##### Info - Displays information of any kind. - Often used to display selected rows count. | Property | Type | Required | Default | Description | | -------- | -------------- | -------- | ------- | --------------- | | children | React.ReacNode | ๐Ÿšซ | ๐Ÿšซ | Info to display | ##### Toggle - Action that hidden when active, showing it's children. - It is inactive, shows a Button. ```ts type Button = { text: string onClick: () => void } ``` | Property | Type | Required | Default | Description | | -------- | -------------- | -------- | ------- | ----------------------------- | | button | Button | โœ… | ๐Ÿšซ | Button props | | active | boolean | ๐Ÿšซ | false | Action on click button | | children | React.ReacNode | ๐Ÿšซ | ๐Ÿšซ | Item to show when is inactive | ##### Dismiss - Button to handle download or export actions. | Property | Type | Required | Default | Description | | -------- | ---------- | -------- | ------- | ---------------------- | | onClick | () => void | โœ… | ๐Ÿšซ | Action on click button | ##### Working Example ```js import Table from '../index' import useTableMeasures from '../hooks/useTableMeasures' import data from './sampleData' import useCheckboxTree from '../../EXPERIMENTAL_useCheckboxTree' import Checkbox from '../../Checkbox' const columns = [ { id: 'name', title: 'Name', }, { id: 'manufacturer', title: 'Manufacturer', }, { id: 'qty', title: 'Qty', }, { id: 'costPrice', title: 'Cost', cellRenderer: props => <Currency {...props} />, }, { id: 'wholesalePrice', title: 'Wholesale', cellRenderer: props => <Currency {...props} />, }, { id: 'retailPrice', title: 'Retail', cellRenderer: props => <Currency {...props} />, }, ] function Currency({ data }) { return <>$ {parseFloat(data).toFixed(2)}</> } function BulkFullExample() { const { items, applyDiscount, increaseQty, decreaseQty } = useProducts() const primaryAction = { label: 'Apply 50% Discount', onClick: () => applyDiscount(checkboxes.checkedItems, 0.5), } const secondaryActions = { label: 'Quantity', actions: [ { label: 'Increase 50', onClick: checked => increaseQty(checked, 50), }, { label: 'Decrease 50', onClick: checked => decreaseQty(checked, 50), isDangerous: true }, ], onActionClick: action => action.onClick(checkboxes.checkedItems), } const measures = useTableMeasures({ size: items.length, }) const [withCheckboxes, isRowActive, checkboxes] = useColumnsWithCheckboxes({ columns, items, }) return ( <Table measures={measures} columns={withCheckboxes} items={items}> <Table.Bulk active={checkboxes.someChecked}> <Table.Bulk.Actions> <Table.Bulk.Actions.Primary {...primaryAction} /> <Table.Bulk.Actions.Secondary {...secondaryActions} /> </Table.Bulk.Actions> <Table.Bulk.Tail> {!checkboxes.allChecked && ( <Table.Bulk.Tail.Info> All rows selected: {checkboxes.checkedItems.length} </Table.Bulk.Tail.Info> )} <Table.Bulk.Tail.Toggle button={{ text: `Select all ${items.length}`, onClick: checkboxes.checkAll, }} active={checkboxes.allChecked}> Selected rows: {items.length} </Table.Bulk.Tail.Toggle> <Table.Bulk.Tail.Dismiss onClick={checkboxes.uncheckAll} /> </Table.Bulk.Tail> </Table.Bulk> </Table> ) } //encapsule the behavior of item change function useProducts() { const [items, setItems] = React.useState(data.products) const bulkUpdate = group => positive => { const groupIds = group.map(item => item.id) setItems(oldItems => oldItems.map(item => (groupIds.includes(item.id) ? positive(item) : item)) ) } const discountCurry = amt => value => value - value * amt const applyDiscount = React.useCallback( (group, amt) => { const calcDiscount = discountCurry(amt) const update = bulkUpdate(group) update(item => ({ ...item, retailPrice: calcDiscount(item.retailPrice), wholesalePrice: calcDiscount(item.wholesalePrice), })) }, [items] ) const increaseQty = React.useCallback( (group, amt) => { const update = bulkUpdate(group) update(item => ({ ...item, qty: item.qty + amt, })) }, [items] ) const decreaseQty = React.useCallback( (group, amt) => { const update = bulkUpdate(group) update(item => ({ ...item, qty: item.qty - amt, })) }, [items] ) return { items, applyDiscount, increaseQty, decreaseQty } } //hook to handle checkboxes function useColumnsWithCheckboxes({ columns, items }) { const checkboxes = useCheckboxTree({ items, onToggle: ({ checkedItems }) => console.table(checkedItems), }) // maps the checkboxes from itemTree to actual elements const mappedCheckboxes = checkboxes.itemTree.children.map(item => { const id = `${item.id}` return ( <Checkbox key={id} id={id} checked={checkboxes.isChecked(item)} partial={checkboxes.isPartiallyChecked(item)} disabled={checkboxes.isDisabled(item)} onChange={() => checkboxes.toggle(item)} /> ) }) // adds the checkboxes column const withCheckboxes = [ { id: 'checkbox', title: ( <Checkbox id={`${checkboxes.itemTree.id}`} checked={checkboxes.allChecked} partial={checkboxes.someChecked} onChange={checkboxes.toggleAll} /> ), width: 32, extended: true, cellRenderer: ({ data }) => { return <div>{mappedCheckboxes[data.id - 1]}</div> }, }, ...columns, ] // [parsed columns, isRowActive function, checked items, allChecked] return [withCheckboxes, data => checkboxes.isChecked(data), checkboxes] } ;<BulkFullExample /> ``` ### Autocomplete, Filters & Pagination Each component has it's own documentation ([Autocomplete](https://styleguide.vtex.com/#/Components/Forms/AutocompleteInput), [Filters](https://styleguide.vtex.com/#/Components/Display/FilterBar), [Pagination](https://styleguide.vtex.com/#/Components/Navigation/Pagination)), so it's important to you check it out to see the full capabilities. ```js import Table from '../index' import useTableMeasures from '../hooks/useTableMeasures' import useTableProportion from '../hooks/useTableProportion' import Input from '../../Input' import Checkbox from '../../Checkbox' import data from './sampleData' const columns = [ { id: 'name', title: 'Name', }, { id: 'email', title: 'Email', }, { id: 'location', title: 'Location', }, ] const items = data.customers /** hook that handles pagination state */ function usePagination(initialSize, items) { const [state, setState] = React.useState({ tableSize: initialSize, currentPage: 1, currentItemFrom: 1, currentItemTo: initialSize, slicedItems: [...items].slice(0, initialSize), }) /** resets state on items change */ React.useEffect(() => { setState({ tableSize: initialSize, currentPage: 1, currentItemFrom: 1, currentItemTo: initialSize, slicedItems: [...items].slice(0, initialSize), }) }, [items]) /** gets the next page */ const onNextClick = React.useCallback(() => { const newPage = state.currentPage + 1 const itemFrom = state.currentItemTo + 1 const itemTo = state.tableSize * newPage const newItems = [...items].slice(itemFrom - 1, itemTo) setState(state => ({ ...state, currentPage: newPage, currentItemFrom: itemFrom, currentItemTo: itemTo, slicedItems: newItems, })) }, [state, items]) /** gets the previous page */ const onPrevClick = React.useCallback(() => { if (state.currentPage === 0) return const newPage = state.currentPage - 1 const itemFrom = state.currentItemFrom - state.tableSize const itemTo = state.currentItemFrom - 1 const newItems = [...items].slice(itemFrom - 1, itemTo) setState(state => ({ ...state, currentPage: newPage, currentItemFrom: itemFrom, currentItemTo: itemTo, slicedItems: newItems, })) }, [state, items]) /** deals rows change of Pagination component */ const onRowsChange = React.useCallback( (e, value) => { const rowValue = parseInt(value) setState(state => ({ ...state, tableSize: rowValue, currentItemTo: rowValue, slicedItems: [...items].slice(state.currentItemFrom - 1, rowValue), })) }, [state, items] ) return { onNextClick, onPrevClick, onRowsChange, ...state, } } function PaginationExample() { const ITEMS_PER_PAGE = 5 /** handles sizes */ const measures = useTableMeasures({ size: ITEMS_PER_PAGE }) /** handles filtering */ const [filteredItems, setFilteredItems] = React.useState(items) const [filterStatements, setFilterStatements] = React.useState([]) /* handles pagination */ const { slicedItems, ...paginationProps } = usePagination( ITEMS_PER_PAGE, filteredItems ) /** handles proportion consistency on change pages */ const { sizedColumns } = useTableProportion({ columns, ratio: [1, 1] }) /** handles autocomplete */ const [term, setTerm] = React.useState('') const [loading, setLoading] = React.useState(false) const timeoutRef = React.useRef(null) /** props of the PaginationComponent */ const pagination = { ...paginationProps, textOf: 'of', rowsOptions: [5, 10, 15], textShowRows: 'Show rows', totalItems: filteredItems.length, } /** function to handle filter changes more info @ Styleguides FilterBar docs */ function handleFiltersChange(statements = []) { let newData = items.slice() statements.forEach(st => { if (!st || !st.object) return const { subject, verb, object } = st switch (subject) { case 'name': case 'email': if (verb === 'contains') { newData = newData.filter(item => item[subject].includes(object)) } else if (verb === '=') { newData = newData.filter(item => item[subject] === object) } else if (verb === '!=') { newData = newData.filter(item => item[subject] !== object) } break case 'location': if (!object) return const selectedLocations = Object.keys(object).reduce( (acc, item) => (object[item] ? [...acc, item] : acc), [] ) newData = newData.filter(item => selectedLocations.includes(item[subject]) ) break } }) setFilteredItems(newData) setFilterStatements(statements) } /** FilterBar props */ const filters = { alwaysVisibleFilters: ['name', 'location'], statements: filterStatements, onChangeStatements: handleFiltersChange, clearAllFiltersButtonLabel: 'Clear Filters', collapseLeft: true, options: { name: { label: 'Name', ...simpleInputVerbsAndLabel(), }, email: { label: 'Email', ...simpleInputVerbsAndLabel(), }, location: { label: 'Location', renderFilterLabel: st => { if (!st || !st.object) { return 'All' } const keys = st.object ? Object.keys(st.object) : {} const isAllTrue = !keys.some(key => !st.object[key]) const isAllFalse = !keys.some(key => st.object[key]) const trueKeys = keys.filter(key => st.object[key]) let trueKeysLabel = '' trueKeys.forEach((key, index) => { trueKeysLabel += `${key}${ index === trueKeys.length - 1 ? '' : ', ' }` }) return `${ isAllTrue ? 'All' : isAllFalse ? 'None' : `${trueKeysLabel}` }` }, verbs: [ { label: 'includes', value: 'includes', object: { renderFn: locationSelectorObject, extraParams: {}, }, }, ], }, }, } /** Autocomplete options prop */ const options = { onSelect: (...args) => console.log('onSelect: ', ...args), loading, value: !term.length ? [] : filteredItems .map(item => item.name) .filter(name => typeof name === 'string' ? name.toLowerCase().includes(term.toLowerCase()) : name.label.toLowerCase().includes(term.toLowerCase()) ), } /** Autocomplete input prop */ const input = { onChange: term => { if (term) { setLoading(true) if (timeoutRef.current) { clearTimeout(timeoutRef.current) } timeoutRef.current = setTimeout(() => { setLoading(false) setTerm(term) timeoutRef.current = null }, 1000) } else { setTerm(term) } }, onSearch: (...args) => console.log('onSearch:', ...args), onClear: () => setTerm(''), placeholder: 'Search name... (e.g.: Peter)', value: term, } return ( <Table measures={measures} items={slicedItems} columns={sizedColumns}> <Table.Toolbar> <Table.Toolbar.InputAutocomplete input={input} options={options} /> </Table.Toolbar> <Table.FilterBar {...filters} /> <Table.Pagination {...pagination} /> </Table> ) } /** simple input renderer */ function simpleInputObject({ values, onChangeObjectCallback }) { return ( <Input value={values || ''} onChange={e => onChangeObjectCallback(e.target.value)} /> ) } /** simple input verbs renderer */ function simpleInputVerbsAndLabel() { return { renderFilterLabel: st => { if (!st || !st.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'Any' } return `${ st.verb === '=' ? 'is' : st.verb === '!=' ? 'is not' : 'contains' } ${st.object}` }, verbs: [ { label: 'is', value: '=', object: { renderFn: simpleInputObject, extraParams: {}, }, }, { label: 'is not', value: '!=', object: { renderFn: simpleInputObject, extraParams: {}, }, }, { label: 'contains', value: 'contains', object: { renderFn: simpleInputObject, extraParams: {}, }, }, ], } } /** location input renderer */ function locationSelectorObject({ values, onChangeObjectCallback }) { const initialValue = { '๐Ÿ‡ฐ๐Ÿ‡ชWakanda': true, '๐Ÿ‡บ๐Ÿ‡ธUSA': true, '๐Ÿ‡จ๐Ÿ‡ณChina': true, '๐Ÿ‡ท๐Ÿ‡บRussia': true, '๐Ÿ‡ฌ๐Ÿ‡งGreat Britain': true, '๐Ÿ‡ธ๐Ÿ‡ฆSaudi Arabia': true, '๐Ÿ‡จ๐Ÿ‡บCuba': true, ...(values || {}), } const toggleValueByKey = key => { const newValues = { ...(values || initialValue), [key]: values ? !values[key] : false, } return newValues } return ( <div> {Object.keys(initialValue).map((opt, index) => { return ( <div className="mb3" key={`class-statment-object-${opt}-${index}`}> <Checkbox checked={values ? values[opt] : initialValue[opt]} label={opt} name="default-checkbox-group" onChange={() => { const newValue = toggleValueByKey(`${opt}`) const newValueKeys = Object.keys(newValue) const isEmptyFilter = !newValueKeys.some(key => !newValue[key]) onChangeObjectCallback(isEmptyFilter ? null : newValue) }} value={opt} /> </div> ) })} </div> ) } ;<PaginationExample /> ```