This is a single article from a series about creating of an advanced Data table component using React, TanStack Table 8, Tailwind CSS and Headless UI.
In the previous episode, we implemented a column filtering feature for the table columns.
This article will focus on a collection of smaller, yet impactful enhancements to our project. Think of it as an Interdimensional Cable episode in Rick and Morty, addressing a few important features and refactorings that, while not the main event, are crucial for improving the overall code quality, maintainability, and user experience.
Tokenization
Tokenization refers to the practice of representing design elements and their properties as reusable variables (tokens).
Since we are using sizes and paddings from Tailwind, we need to tokenize only color values. Instead of hard-coding specific values (e.g., teal-600
), we will assign meaningful names to these elements (primary
, secondary
, etc.).
This way, changes to a token affect all elements that use it, ensuring consistency across the entire application. This will be particularly useful for the dark theme.
We put our tokenized colors into ./tailwind.config.js
file as a theme.
const colors = require('tailwindcss/colors');
export default {
//...
theme: {
extend: {
colors: {
primary: colors.stone['600'],
secondary: colors.cyan['800'],
backgroundLight: colors.white,
textDark: colors.stone['100'],
textLight: colors.stone['950'],
hoverColor: colors.cyan['100'],
borderColor: colors.stone['300'],
},
},
},
};
Then we apply these theme variables instead of directly using Tailwind colors. So bg-stone-600
CSS class becomes bg-primary
and so on.
Dark theme
In the past, Windows 95 graced our screens with its predominantly white-blueish backgrounds, a hallmark of its time. As an advanced operating system, it came equipped with state-of-the-art hardware and software, which garnered much appreciation from my generation. WYSYWIG interface paradigm was a refreshing departure from the older, terminal-based environments dominated by black and green hues. Interestingly, today's trend has shifted in the opposite direction.
Now when we have our colors organized we can start making dark theme to respect modern generation's aesthetic preferences.
Modern browsers provide built-in support for dark mode preferences through the prefers-color-scheme
media query. This media query acts as a bridge between the user's system-wide dark mode setting and your application's styles.
Tailwind CSS offers a convenient way to implement dark mode with its dark:
helper class. This class allows you to easily define styles specific to the dark mode presentation.
Here are our new dark color tokens. We have background
token assigned #000
(pure black) value to make the table dark. For the rest of the tokens, we adjusted saturation to achieve the same contrast as we had with a white background.
To achieve a seamless dark mode experience, we'll begin by extending your existing theme to include dark variants of your color tokens. These dark color tokens will be used to style the Data table in dark mode. For instance, we can set the background token to #000 (pure black) to create a dark background for the table.
For the remaining color tokens, it's essential to adjust their saturation levels. This ensures that the text and other elements maintain sufficient contrast against the dark background, preserving readability.
primaryDark: colors.stone['700'],
backgroundDark: colors.black,
textDark: colors.stone['100'],
hoverColorDark: colors.cyan['600'],
borderColorDark: colors.stone['600'],
Here is how we apply the dark theme to the component.
<Input
//...
className={classNames(
//..
// focus styles light
'focus:bg-backgroundLight/85 focus:text-textLight focus:placeholder-transparent',
// focus styles dark
'dark:focus:bg-backgroundDark/85 dark:focus:text-textDark',
'transition-all duration-200',
)}
/>
Columns permissions
To improve the User Experience of the table, we are going to fine tune column capabilities. The goal is to disable some features for some columns.
We will extend the existing hook in src/DataTable/features/useColumnActions.tsx
to include a new property: disabled
.
This property will be a boolean flag indicating whether a specific feature is disabled for the current column. The necessary information about the column's configuration will be retrieved from the TanStack Table Context API.
type ColumnAction = {
//...
/** True when table capability is disabled by config */
disabled?: boolean;
};
/**
* React hook which returns an array of table column actions config objects
*/
export const useColumnActions = (
context: HeaderContext<Row, unknown>,
): ColumnAction[] => {
//...
return useMemo<ColumnAction[]>(
() => [
// Pin left button
{
// ...
disabled: !context.column.getCanPin(),
},
// Pin right button
{
//...
disabled: !context.column.getCanPin(),
},
// Sort ASC button
{
//...
disabled: !context.column.getCanSort(),
},
// Sort DESC button
{
//...
disabled: !context.column.getCanSort(),
},
// Filter button
{
//...
disabled: !context.column.getCanFilter(),
},
],
[/*...*/],
);
};
Now, we will use this logic inside src/DataTable/cells/HeaderCell.tsx
. We connect the disabled
property from the hook to the corresponding Button
component prop. Also, we disable pointer events and set 30% opacity for the disabled state using the corresponding Tailwind CSS helper.
In src/DataTable/cells/HeaderCell.tsx
, we will integrate the disabled
property obtained from the useColumnActions
hook into the corresponding Button
component.
If the disabled flag is set to true:
Button component will be rendered in a disabled state.
Pointer events will be disabled for the button using Tailwind CSS
pointer-events-none
class.Text and icon opacity will be reduced to 30% to visually indicate button state.
{items.map(({ label, icon: Icon, onClick, disabled }) => (
<MenuItem key={label} as={Fragment}>
{() => (
<Button
disabled={disabled}
onClick={onClick}
className={classNames(
// ...
// disabled styles
'disabled:text-textDark/30 disabled:pointer-events-none'
)}
>
{Icon}
<div>{label}</div>
</Button>
)}
</MenuItem>
))}
Finally, we have to set enableColumnFilter
, enablePinning
and enableSorting
properties for table columns inside src/DataTable/columnsConfig.tsx
.
const columnHelper = createColumnHelper<Row>();
export const columns = [
//...
columnHelper.accessor('randomDecimal', {
enableColumnFilter: false,
enablePinning: false,
enableSorting: false,
//...
}
//...
]
Debug mode
The last change is a quality of life improvement. Tanstack has a convenient debugging tools available. So we are going to implement debugAll mode.
Inside src/DataTable/DataTable.tsx
we will extend component props with debug
property. And set this property at useReactTable
hook parameters.
type Props = {
//...
/**
* Enable TanStack table debug mode
* @see https://tanstack.com/table/latest/docs/api/core/table#debugall
*/
debug?: boolean;
};
export const DataTable: FC<Props> = ({ tableData, locale = 'en-US', debug }) => {
//...
const table = useReactTable({
//...
debugAll: debug
});
//...
};
Chapter summary
In these 7 articles we created the Data table component from scratch, using TanStack table, Tailwind CSS and Headless UI. We implemented the following features:
Multidimensional Scroll: Enables smooth and synchronized scrolling in both horizontal and vertical directions, with sticky header maintaining alignment across the viewport
Responsive Design: Adapts seamlessly to various screen sizes and supports a wide range of data types, including text, numbers, dates, and limited enumerable lists.
Customizable Subcomponents: Dialogs, Dropdown menus, and custom input fields for enhanced user interaction.
High-Performance Virtualization: Enables virtualization techniques to efficiently render around 50,000 rows without significant performance degradation, even on less powerful machines.
Column Pinning: Allows users to pin columns to the left or right of the viewport, improving data focus and readability.
Advanced Sorting and Filtering: Implements intelligent sorting and filtering mechanisms that account for the specific data formats of each column.
Dark Mode Support: Provides a user-friendly dark mode option.
Here is the complete demo of the exercise.
To be continued.
Top comments (0)