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.
Componentsβ
When to use a Card grid instead of a Table?
- Include a List layout with a Table when there are important item properties that are textual and may need to be compared. For example, a list of products, promotions, or orders.
- Include a Grid layout with Cards when there are important item properties that are visual. For example, app logos in a list of apps or thumbnails in a media gallery.
When to use the Bulk Actions component with a Table?
When to use the DataView component with a Table?
Variantsβ
When to include a Menu in each row?
- When rows are clickable, always add a Menu including a View details action with the same purpose as clicking the row, even if there are no other actions for the item.
- If the action is very important, it can be outside the Menu. For example, activating or deactivating an item such as SKU Bindings or approving or refusing a request such as Invoices.
- Destructive actions should always be inside a Menu.
- If the Table fits all the information and actions, or if the line canβt be clicked, there is no need to include a Menu.
- Donβt include more than two actions outside the Menu on each Table row.
When to include rows that can be clicked?
- If there is a way to open a Modal or a Page to view or edit an item, trigger this action when merchants click the row.
- Donβt open a Popover or Menu when a row is clicked. Only open the Menu if the merchant clicks the Menu directly.
When to include fixed columns?
When should a column be sortable?
- When a Name column exists, always allow it to be sorted alphabetically.
- Consider supporting sorting in all columns where the content has a clear order. For example, dates, texts and numbers can be sorted by ascending or descending order. Images, however, canβt be sorted.
What column type should I use for each property type?
- Name: Dedicated 1-line Text column or 2-line Text column along with ID or Description.
- Image: In left-to-right languages, the Image should always be displayed to the left of the Name in a Thumbnail column.
- ID: Dedicated 1-line Text column or 2-line Text column along with the Name.
- Description: 2-line Text column that includes both Name and Description.
- Tertiary actions: Menu.
- Secondary actions: Button.
- Bulk Actions: Checkbox.
- Status: Tag.
- Value or Price: Number.
- Creation or Update date: 1-line Text.
- Country or payment flag: Icon.
- Type of object: 1-line Text, with or without an Icon before it.
- Percentage value or tendency: Dedicated 1-line Number column or in a 2-line Number column along with the numeric value.
- Donβt display columns with personal information, such as email addresses, personal names, addresses or phone numbers.
Positionβ
Where should a Table be positioned in a container?
What should be the alignment of content in a Table?
What should be the column width?
fr
), which represents a fraction of the available space in the row. For example, the Name column can have the width set to 2 fr
and the Status column can have the width set to 1 fr
.Besides representing column width in this responsive unit, it is important to set a minimum comfortable width in
rem
to avoid line breaks in tighter viewports.What should be the order of columns?
- Position properties that identify list items first, such as ID and Name.
- Position the Menu as the last column and immediately before it a tag with the itemβs status, when it exists.
What should be the height of the table row?
Behaviorβ
When to use a loading Table?
- Only use the loading state of the Table during the initial load of the Page or during Pagination.
- During a search, keep the previous results until the new ones load and donβt show the loading state of a Table.
What should be the default sorting of a Table?
Contentβ
What should be the column header labels?
- Write the label so that it describes the property type whose values are in the column.
- Use sentence case.
- Use short labels. Prefer two words maximum.
- Don't write labels that vary a lot in length between options.
- Don't include redundant words. For example, use only Name instead of Product name.
What should be the actions for each row?
Conduct UX research with merchants to determine actions that need to be available. Common actions on Table rows include:
- View details
- Edit
- Delete
- Rename
- Duplicate
- Share
- Archive
- Don't include more than five actions for each row.
What should be the formatting of text in a cell?
- The text that best identifies items should use the
$action1
typography token. For example, the Name or the ID when items don't have a Name. - Percentage values and tendencies should use semantic colors to visually represent information.
- Descriptions and IDs (when there is a separate Name column) should use the
$fg.secondary
token.