wave icon indicating copy to clipboard operation
wave copied to clipboard

Provide sorting logic to the TeableHeaderCell (Proposal)

Open div-Leo opened this issue 2 years ago • 1 comments

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.

div-Leo avatar Apr 13 '22 15:04 div-Leo

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?

nlopin avatar May 24 '22 15:05 nlopin

: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:

github-actions[bot] avatar Sep 29 '23 13:09 github-actions[bot]