@vtex/styleguide
Version:
> VTEX Styleguide React components ([Docs](https://vtex.github.io/styleguide))
1,075 lines (939 loc) โข 30 kB
Markdown
<div className="center mw7 pv6">

</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 />
```