Table
What is this?
Table containers allow merchants to view a vertical list of items with property values and related actions.
Composition
The Table
is designed to easily integrate with other components to create the view. You must understand the concept of the DataView Pattern before reading this section.
Anatomy
Normally you will encounter Table as the data-rendering part of the data-view, for example:
DataView
|__ DataViewHeader
| |__ Search
| |__ Toolbar
| | |__ Button
| |__ Pagination
|
|__ Table
|__ .Head
| |__ .Cell
|__ .Body
|__ .Row
|__ .Cell
Pagination
Example using the Pagination component.
Reacting to DataView status
By dispatch the setStatus
function of the DataView
, the Table
reacts to it.
Examples
This section presents a series of examples that may be useful.
Filters
Displaying filters and filtering data
Topbar
Mixing the concepts of Search
, Toolbar
and Pagination
Data fetching
Example with a simulated data fetching
Selectable row
Example with selectable rows
Fixed columns
It is possible to set columns to be fixed when scrolling horizontally. You just need to set the column prop fixed
to true
.
Usage
import {
Table,
TBody,
TBodyRow,
TBodyCell,
THead,
THeadCell,
useTableState,
} from '@vtex/admin-ui'
const columns = createColumns([
{
id: 'productName',
header: 'Product name',
},
{
id: 'inStock',
header: 'In Stock',
},
{
id: 'price',
header: 'Price',
},
{
id: 'skus',
header: 'SKUs',
},
])
function Example() {
/**
* The hook returns the Table state
*/
const { data, getBodyCell, getHeadCell, getTable } = useTableState({
/**
* Columns shape, read more about it on the rendering section
*/
columns,
/**
* List of items to render
*/
items: [
{
id: 1,
productName: 'Orange',
inStock: 380,
skus: 0,
price: 120,
},
],
})
/**
* You must use the `state` prop so that your Table comes to life
* This is the only prop that is required
*/
return (
<Table {...getTable()}>
<THead>
{columns.map((column) => (
<THeadCell {...getHeadCell(column)} />
))}
</THead>
<TBody>
{data.map((item) => {
return (
<TBodyRow
key={item.id}
onClick={() => alert(`Item: ${item.productName}`)}
>
{columns.map((column) => {
return <TBodyCell {...getBodyCell(column, item)} />
})}
</TBodyRow>
)
})}
</TBody>
</Table>
)
}
State
The state hook useTableState
contains all business logic needed for the component.
useTableState
Name | Type | Description | Required | Default |
---|---|---|---|---|
columns | Column<T>[] | Table column spec | ✅ | - |
status | DataViewStatus | Related DataView status | 🚫 | - |
context | ResolverContext | Resolver context | 🚫 | - |
items | T[] | Table items | 🚫 | [] |
length | number | Expected items length, this will also control the number of skeleton items | 🚫 | 5 |
sort | UseTableSortParams<T> | useTableSort hook params | 🚫 | - |
It returns an object with the following types:
export interface UseTableStateReturn<T> {
/**
* Collection rendered while loading
*/
skeletonCollection: T[]
/**
* Resolves the cell content
*/
resolveCell: (args: ResolverCallee<ResolveCellArgs<T>>) => ReactNode
/**
* Resolvers the header content
*/
resolveHeader: (
args: ResolverCallee<ResolveHeaderArgs<T>>
) => ResolveHeaderReturn
/**
* Items to render
*/
data: T[]
/**
* Grid columns
*/
columns: Array<TableColumn<T>>
/**
* Current sorting state
*/
sortState: UseSortReturn
/**
* Table ref
*/
tableRef: RefObject<HTMLTableElement>
/**
TBody cell props
*/
getBodyCell: (
column: TableColumn<T, BaseResolvers<T>>,
item: T
) => TableBodyCellProps<T>
/**
THead cell props
*/
getHeadCell: (
column: TableColumn<T, BaseResolvers<T>>
) => TableHeadCellProps<T>
/**
Table props
*/
getTable: () => TableProps<T>
/**
DataView related status
*/
status: DataViewStatus
}
Rendering
The main objective of Table
is to provide a flexible render to support any kind of data type.
Attribute | Type | Description | Required |
---|---|---|---|
id | string | String that defines the property name that the column represents. | ✅ |
header | ((column: Column<T>) => ReactNode), or string | Controls the title which appears on the table Header. It can receive either a string or an element. | 🚫 |
accessor | ((item: T) => ReactNode), or string | Defines how to access a property | 🚫 |
resolver | R | Resolvers api Will select the plain resolver by default | 🚫 |
width | number | Defines a fixed width for the specific column. Receives either a string or number. By default, the column's width is defined to fit the available space without breaking the content. | 🚫 |
sortable | (a: T, b: T) => number | Defines if that column is sortable or not, passing true to this prop won't sort items by itself, the sorting will still need to be handled using the sort prop inside the StatelessTable sort prop. Check Sorting | 🚫 |
compare | boolean | The function provided to handle the sorting of this column of the table, if this function is provided the table items will be sorted based on this function result. Check Sorting | 🚫 |
Accessor
Some properties may be nested within objects and arrays. The accessor
properties provide an easy way to access those.
Resolvers
Resolvers are rendering functions that target a specific data type. The main usage is to render the same data types consistently along with admin applications.
Render function
All resolvers accept a render function, that returns a component. It controls the data rendering, which may be treated by the resolver or not.
{
type: 'resolver name',
/**
* You have 3 render props here:
* { item, data, context }
*/
render: function Render({ item, data, context }) {
return <></>
}
}
Name | Type | Description |
---|---|---|
item | T | the item displayed for the row |
data | unknown | extracted column data from the item, you need to cast it before use |
context | { loading: boolean } | relevant global information about the table current state |
Root
This is the parent of all other resolvers. It does not treat the data at all - even the loading state is completely up to you. Use it if you want complete control over what's being rendered on the cell, and don't mind the complexity that it brings.
Name | Type | Description | Required |
---|---|---|---|
type | root | Root resolver type | ✅ |
render | (props: ResolverRenderProps<null, T>) => ReactNode | Resolver render function | ✅ |
Plain
The plain resolver is the default for all columns. It means that if you don't select a resolver, this is what you're rendering. It should be mainly used to render raw data like strings or numbers that don't need treatment.
Name | Type | Description | Required |
---|---|---|---|
type | plain | Plain resolver type | ✅ |
render | (props: ResolverRenderProps<ReactNode, T>) => ReactNode | Resolver render function | 🚫 |
Text
The text resolver should be mainly used to render a text and an optional description just below it. For descriptions that are too long, it is possible to truncante it by setting the overflow
prop.
This resolver must be used on the name
column of the table.
Name | Type | Description | Required | Default |
---|---|---|---|---|
type | text | Text resolver type | ✅ | - |
columnType | name or text | Column text type | 🚫 | text |
mapText | (item: T) => ReactNode | The map function which returns the text to be rendered | ✅ | - |
mapDescription | (item: T) => ReactNode | The map Function which returns the description to be rendered | 🚫 | - |
overflow | ellipsis or auto | It specifies how overflowed text should be signaled to the user. | 🚫 | - |
render | (props: ResolverRenderProps<ReactNode, T>) => ReactNode | Resolver render function | 🚫 | - |
Menu
The menu resolver should be used when you want to easily render a Menu component and a set of actions.
Name | Type | Description | Required | Default |
---|---|---|---|---|
type | menu | Menu resolver type | ✅ | - |
actions | MenuAction[] | A set of actions to be rendered as MenuItems | ✅ | - |
render | (props: ResolverRenderProps<JSX.Element, T>) => ReactNode | Resolver render function | 🚫 | - |
MenuAction
Name | Type | Description | Required | Default |
---|---|---|---|---|
label | string | MenuItem label | ✅ | - |
onClick | (item: T) => void | MenuItem onClick handler | ✅ | - |
icon | ReactNode | MenuItem icon | 🚫 | - |
disabled | boolean | Whether the MenuItem is disabled or not | 🚫 | false |
critical | boolean | Whether the MenuItem is critical or not | 🚫 | false |
Currency
Name | Type | Description | Required |
---|---|---|---|
type | currency | Currency resolver type | ✅ |
locale | string | Currency locale | ✅ |
currency | string | Currency type | ✅ |
render | (props: ResolverRenderProps<string, T>) => ReactNode | Resolver render function | 🚫 |
Date
Name | Type | Description | Required |
---|---|---|---|
type | date | Date resolver type | ✅ |
locale | string | Date locale | ✅ |
options | Intl.DateTimeFormatOptions | Date options | 🚫 |
render | (props: ResolverRenderProps<string, T>) => ReactNode | Resolver render function | 🚫 |
Image
Name | Type | Description | Required |
---|---|---|---|
type | image | Image resolver type | ✅ |
alt | string | HTML img alt | 🚫 |
render | (props: ResolverRenderProps<JSX.Element, T>) => ReactNode | Resolver render function | 🚫 |
Bulk
The bulk resolver should be used when you want to add a bulk actions to the table. It is a easier way of using the BulkActions component within the Table.
When using this resolver there are two things you should follow in order to work properly: wrap the Table component with the SelectionTree
component and avoid adding more than 25 items per page.
Name | Type | Description | Required |
---|---|---|---|
type | bulk | Bulk resolver type | ✅ |
state | BulkActionsState<T> | The useBulkActions hook state return | ✅ |
render | (props: ResolverRenderProps<ReactNode, T>) => ReactNode | Resolver render function | 🚫 |
Selection
The selection resolver should be used when it is necessary to have rows selectable and to have control of which rows are selected.
Name | Type | Description | Required | |
---|---|---|---|---|
type | selection | Selection resolver type | ✅ | |
mapId | `mapId: (item: T) => string | number` | The map function which returns the id to be used by the checkbox | 🚫 |
render | (props: ResolverRenderProps<ReactNode, T>) => ReactNode | Resolver render function | 🚫 |
Sorting
To use the base sorting configuration, that matches the majority of use cases, you just need to pass the compare
function to the columns that you want to sort by. Two params are accepted, representing two items - you must return a boolean that proves their equality.
type Compare = (a: T, b: T) => boolean
Configuration
The following example allows ordering by name
, creation
and price
. By using the sort
property within useTableState
you can configure the sorting to match specific use cases.
initialValue
Defines the table's initial sorting value.
{ order?: 'ASC' | 'DESC', by?: string }
The
order
prop is related to the sorting order andby
indicates which column is being sorted, this value should be the id of the column.
directions
- Defines the sorting order of the table.
- It accepts an array with
ASC
andDESC
as possible values. You can pass an array with one or two sorting directions. If you pass an array with only one sorting direction the table will only sort in one direction.
reducer
Receives the reducer that will be used inside of the
useReducer
that handles the sorting state, it is not required and if not provided the default reducer function will be used.The reducer function is called with the current sort state
{ order?: SortOrder, by?: string }
and the sorting action{ type: SortOrder | 'RESET', columnId?: string }
.
callback
Receives a function that will be fired when the user clicks the table header cell of a column.
This function is called with an object containing the current sort state, the dispatch of the current
useReducer
that handles the sorting state, the column id of the column that was clicked, and the current sort directions being used.