wave
wave copied to clipboard
Provide sorting logic to the TeableHeaderCell (Proposal)
Provide sorting logic to the TeableHeaderCell (Proposal)
We have several implementations of the same sorting feature in multiple projects so we are aiming to unify them into a single solution and export it from the library. In DMT we built a custom hook to handle the logic of the sorting, able to define in a three-step switch ( 'DESC' | 'ASC' | '') which field is sorted by and by which order.
// useSortBy.ts
export type Order = 'DESC' | 'ASC' | ''
export type SortBy = {
field: string,
order: Order,
}
type OrderNode = { value: Val, next?: OrderNode }
export function useSortBy(): [SortBy, Dispatch<SetStateAction<SortBy>>] {
// Optional custom hook to create circular lists and iterate in a cycle with the next property
const [head] = useCircularList(['ASC', 'DESC', ''])
const [field, setField] = useState('')
const [order, setOrder] = useState<OrderNode>(head)
const setSortBy = useCallback(
(nextField: string) => {
// if the field you're trying to sort with changes, reset the sort
if (field !== nextField) {
setField(nextField)
setOrder(head)
} else {
// We iterate over the states in a cycle
const newOrder = order.next
setOrder(newOrder)
if (newOrder === '') setField('')
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[field, head],
)
return useMemo(
() => [
{
field,
order: order?.value,
},
setSortBy,
],
[field, order, setSortBy],
)
}
We can provide another component from the library in the Table context to be rendered as children of the TableHeaderCell component
// TableHeaderSortingIcons.tsx
const TableHeaderSortingIcons = ({ order }: { order: 'DESC' | 'ASC' | null }) => (
<Box ml={1} display='inline-flex' alignItems='center'>
{order ? (
<Box display='flex' flexDirection='column' alignItems='end'>
<DropupSelectIcon
color={order === "ASC" ? get('semanticColors.primary.icon') : get('semanticColors.primary.secondary')}
size={16}
aria-label="ASC"
/>
<DropdownSelectIcon
color={order === "DESC" ? get('semanticColors.primary.icon') : get('semanticColors.primary.secondary')}
size={16}
aria-label="DESC"
/>
</Box>
) : null}
</Box>
)
Usage
import { TableHeaderCell, TableHeaderSortingIcons, useSortBy } from '@freenow/wave'
function Component () {
const [sortBy, setSortBy] = useSortBy()
/**
* With the hook, we can extract the logic and use it on the top level and get the control over the sort state,
* meaning that when `sort` changes we can sort our items in the right order or fetch some sorted results from an API
* passing the right field
*/
{...}
return (
<TableHeaderCell onClick={() => setSortBy('Name')}>
<Text> Name </Text>
<TableHeaderSortingIcons order={sortBy?.field === 'Name' && sortBy?.order}/>
</TableHeaderCell>
)
}
P.S. This could be an alpha solution and later we can further investigate how it improves this.
I like the idea of exposing all the building blocks - behavior with the useSort
hook and the UI. On top of that we can expose a component with meaningful default behavior which, I assume, will be used the most, because the code above is quite repetitive.
So ideal usage would be something like:
function MySortableTable () {
// (string, 'ASC'|'DESC'|'') => void
const requestSortedData = useCallback((field, order) => {
// request api
}, [])
return (<Table rowStyle='lines'>
<TableRow>
<TableSortableHeaderCell field='name' onSortChange={requestSortedData}>Name</TableSortableHeaderCell>
</TableRow>
</Table>)
}
What do you think?
:tada: This issue has been resolved in version 3.0.0-next.1 :tada:
The release is available on:
Your semantic-release bot :package::rocket: