Skip to content

Commit

Permalink
Move useSort (now useTableSort) into its own module
Browse files Browse the repository at this point in the history
  • Loading branch information
phildarnowsky-broad committed Feb 12, 2025
1 parent 06f8ebe commit 7b6e066
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 104 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { ReactNode, useCallback, useState } from 'react'
import React from 'react'
import styled from 'styled-components'

import { BaseTable, TooltipAnchor, TooltipHint } from '@gnomad/ui'
import { BaseTable } from '@gnomad/ui'

import { GNOMAD_POPULATION_NAMES } from '@gnomad/dataset-metadata/gnomadPopulations'

import { MitochondrialVariant, MitochondrialVariantPopulation } from './MitochondrialVariantPage'

import useTableSort, { ColumnSpecifier, numericCompareFunction } from '../useTableSort'

const CountCell = styled.span`
display: inline-block;
width: 7ch;
Expand All @@ -31,98 +33,6 @@ const Table = styled(BaseTable)`
}
`

type RowCompareFunction<RowData> = (a: RowData, b: RowData) => number

type ColumnSpecifier<RowData> = {
key: keyof RowData
label: string
tooltip: string | null
compareValueFunction: RowCompareFunction<RowData>
}

const renderColumnHeader = <RowData,>(
key: keyof RowData,
sortBy: keyof RowData,
setSortBy: (key: keyof RowData) => void,
sortAscending: boolean,
label: string,
tooltip: string | null,
compareValueFunction: RowCompareFunction<RowData>
) => {
let ariaSortAttr: React.AriaAttributes['aria-sort'] = 'none'
if (sortBy === key) {
ariaSortAttr = sortAscending ? 'ascending' : 'descending'
}

return tooltip ? (
<th aria-sort={ariaSortAttr} scope="col">
{/* @ts-expect-error TS(2322) FIXME: Type '{ children: Element; tooltip: any; }' is not... Remove this comment to see the full error message */}
<TooltipAnchor tooltip={tooltip}>
<button type="button" onClick={() => setSortBy(key)}>
{/* @ts-expect-error TS(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}
<TooltipHint>{label}</TooltipHint>
</button>
</TooltipAnchor>
</th>
) : (
<th aria-sort={ariaSortAttr} scope="col">
<button type="button" onClick={() => setSortBy(key)}>
{label}
</button>
</th>
)
}

// .sort((a, b) => {
// const [population1, population2] = sortAscending ? [a, b] : [b, a]
//
// return sortBy === 'id'
// ? GNOMAD_POPULATION_NAMES[population1.id].localeCompare(
// GNOMAD_POPULATION_NAMES[population2.id]
// )
// : population1[sortBy] - population2[sortBy]
// }) */

const useSort = <RowData,>(
columnSpecifiers: ColumnSpecifier<RowData>[],
defaultSortKey: keyof RowData,
rowData: RowData[]
): { headers: ReactNode; sortedRowData: RowData[] } => {
const [key, setKey] = useState<keyof RowData>(defaultSortKey)
const [ascending, setAscending] = useState<boolean>(false)

const setSortKey = useCallback(
(newKey: keyof RowData) => {
setKey(newKey)
setAscending(newKey === key ? !ascending : false)
},
[key, ascending]
)

const { compareValueFunction } = columnSpecifiers.find((column) => column.key === key)!
const sortedRowData = [...rowData].sort((a, b) => {
const ascendingCompare = compareValueFunction(a, b)
return ascending ? ascendingCompare : -ascendingCompare
})

const headers = (
<>
{columnSpecifiers.map((columnSpecifier) =>
renderColumnHeader(
columnSpecifier.key,
key,
setSortKey,
ascending,
columnSpecifier.label,
columnSpecifier.tooltip,
columnSpecifier.compareValueFunction
)
)}
</>
)
return { headers, sortedRowData }
}

type MitochondrialVariantPopulationFrequenciesTableProps = {
variant: MitochondrialVariant
}
Expand All @@ -132,15 +42,6 @@ type MitochondrialVariantPopulationWithFrequency = MitochondrialVariantPopulatio
af_het: number
}

type NumberHolder<Key extends string> = {
[K in Key]: number
}

export const numericCompareFunction =
<Key extends string>(key: Key) =>
<RowData extends NumberHolder<Key>>(a: RowData, b: RowData) =>
a[key] - b[key]

const comparePopulationNames = (
a: MitochondrialVariantPopulationWithFrequency,
b: MitochondrialVariantPopulationWithFrequency
Expand Down Expand Up @@ -199,7 +100,7 @@ const MitochondrialVariantPopulationFrequenciesTable = ({
af_het: population.an !== 0 ? population.ac_het / population.an : 0,
}))

const { headers, sortedRowData } = useSort<MitochondrialVariantPopulationWithFrequency>(
const { headers, sortedRowData } = useTableSort<MitochondrialVariantPopulationWithFrequency>(
columnSpecifiers,
'af_hom',
populationsWithFrequencies
Expand Down
93 changes: 93 additions & 0 deletions browser/src/useTableSort.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useCallback, useState, ReactNode } from 'react'
import { TooltipAnchor, TooltipHint } from '@gnomad/ui'

export type RowCompareFunction<RowData> = (a: RowData, b: RowData) => number

export type ColumnSpecifier<RowData> = {
key: keyof RowData
label: string
tooltip: string | null
compareValueFunction: RowCompareFunction<RowData>
}

const renderColumnHeader = <RowData,>(
key: keyof RowData,
sortBy: keyof RowData,
setSortBy: (key: keyof RowData) => void,
sortAscending: boolean,
label: string,
tooltip: string | null
) => {
let ariaSortAttr: React.AriaAttributes['aria-sort'] = 'none'
if (sortBy === key) {
ariaSortAttr = sortAscending ? 'ascending' : 'descending'
}

return tooltip ? (
<th aria-sort={ariaSortAttr} scope="col">
{/* @ts-expect-error TS(2322) FIXME: Type '{ children: Element; tooltip: any; }' is not... Remove this comment to see the full error message */}
<TooltipAnchor tooltip={tooltip}>
<button type="button" onClick={() => setSortBy(key)}>
{/* @ts-expect-error TS(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}
<TooltipHint>{label}</TooltipHint>
</button>
</TooltipAnchor>
</th>
) : (
<th aria-sort={ariaSortAttr} scope="col">
<button type="button" onClick={() => setSortBy(key)}>
{label}
</button>
</th>
)
}

const useTableSort = <RowData,>(
columnSpecifiers: ColumnSpecifier<RowData>[],
defaultSortKey: keyof RowData,
rowData: RowData[]
): { headers: ReactNode; sortedRowData: RowData[] } => {
const [key, setKey] = useState<keyof RowData>(defaultSortKey)
const [ascending, setAscending] = useState<boolean>(false)

const setSortKey = useCallback(
(newKey: keyof RowData) => {
setKey(newKey)
setAscending(newKey === key ? !ascending : false)
},
[key, ascending]
)

const { compareValueFunction } = columnSpecifiers.find((column) => column.key === key)!
const sortedRowData = [...rowData].sort((a, b) => {
const ascendingCompare = compareValueFunction(a, b)
return ascending ? ascendingCompare : -ascendingCompare
})

const headers = (
<>
{columnSpecifiers.map((columnSpecifier) =>
renderColumnHeader(
columnSpecifier.key,
key,
setSortKey,
ascending,
columnSpecifier.label,
columnSpecifier.tooltip
)
)}
</>
)
return { headers, sortedRowData }
}

type NumberHolder<Key extends string> = {
[K in Key]: number
}

export const numericCompareFunction =
<Key extends string>(key: Key) =>
<RowData extends NumberHolder<Key>>(a: RowData, b: RowData) =>
a[key] - b[key]

export default useTableSort

0 comments on commit 7b6e066

Please sign in to comment.