Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: [dev-6966] tables styling #1508

Merged
merged 2 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions packages/core/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { TableConfig } from './types/TableConfig'
import { Column } from '../../types/Column'
import { pivotData } from '../../helpers/pivotData'
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
import './data-table.css'

export type DataTableProps = {
applyLegendToRow?: Function
Expand Down Expand Up @@ -131,26 +132,27 @@ const DataTable = (props: DataTableProps) => {
}

const rawRows = Object.keys(runtimeData).filter(column => column != 'columns')
const rows = isVertical
? rawRows.sort((a, b) => {
let dataA
let dataB
if (config.type === 'map' && config.columns) {
const sortByColName = config.columns[sortBy.column].name
dataA = runtimeData[a][sortByColName]
dataB = runtimeData[b][sortByColName]
}
if (config.type === 'chart' || config.type === 'dashboard') {
dataA = runtimeData[a][sortBy.column]
dataB = runtimeData[b][sortBy.column]
}
if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
}
return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
})
: rawRows
const rows =
isVertical && sortBy.column
? rawRows.sort((a, b) => {
let dataA
let dataB
if (config.type === 'map' && config.columns) {
const sortByColName = config.columns[sortBy.column].name
dataA = runtimeData[a][sortByColName]
dataB = runtimeData[b][sortByColName]
}
if (['chart', 'dashboard', 'table'].includes(config.type)) {
dataA = runtimeData[a][sortBy.column]
dataB = runtimeData[b][sortBy.column]
}
if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
}
return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
})
: rawRows

const limitHeight = {
maxHeight: config.table.limitHeight && `${config.table.height}px`,
Expand Down Expand Up @@ -283,7 +285,7 @@ const DataTable = (props: DataTableProps) => {
)
}
tableOptions={{
className: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${
className: `table table-striped ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${
isVertical ? '' : ' horizontal'
}`,
'aria-live': 'assertive',
Expand Down Expand Up @@ -311,7 +313,7 @@ const DataTable = (props: DataTableProps) => {
<th>End Date</th>
</tr>
}
tableOptions={{ className: 'region-table data-table' }}
tableOptions={{ className: 'table table-striped region-table data-table' }}
fontSize={config.fontSize}
/>
)}
Expand All @@ -329,7 +331,7 @@ const DataTable = (props: DataTableProps) => {
<ErrorBoundary component='DataTable'>
<section
id={tabbingId.replace('#', '')}
className={`data-table-container ${viewport} w-100`}
className={`data-table-container ${viewport}`}
aria-label={accessibilityLabel}
>
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
Expand All @@ -344,7 +346,7 @@ const DataTable = (props: DataTableProps) => {
stickyHeader
headContent={<BoxplotHeader categories={config.boxplot.categories} />}
tableOptions={{
className: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}`,
className: `table table-striped ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}`,
'aria-live': 'assertive',
'aria-rowcount': 11,
hidden: !expanded
Expand Down
62 changes: 33 additions & 29 deletions packages/core/components/DataTable/components/ChartHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { getChartCellValue } from '../helpers/getChartCellValue'
import { getSeriesName } from '../helpers/getSeriesName'
import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
import { DownIcon, UpIcon } from './Icons'
import ScreenReaderText from '@cdc/core/components/elements/ScreenReaderText'
import { SortIcon } from './SortIcon'
import { getNewSortBy } from '../helpers/getNewSortBy'

type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; hasRowType? }

Expand All @@ -19,17 +20,6 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
}
}

const handleHeaderClasses = (sortBy, text) => {
let classes = ['sort']
if (sortBy.column === text && sortBy.asc) {
classes.push('sort-asc')
}
if (sortBy.column === text && sortBy.desc) {
classes.push('sort-desc')
}
return classes.join(' ')
}

const ScreenReaderSortByText = ({ text, config, sortBy }) => {
const notApplicableText = 'Not Applicable'
let columnHeaderText = `${text} `
Expand All @@ -47,13 +37,18 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }

if (columnHeaderText === notApplicableText) return

return <span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'} order`}</span>
return (
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
} order`}</span>
)
}

const ColumnHeadingText = ({ column, text, config }) => {
const ColumnHeadingText = ({ text, config }) => {
let notApplicableText = 'Not Applicable'
if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
if (text === '__series__' && !config.table.indexLabel) return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
if (text === '__series__' && !config.table.indexLabel)
return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
return text
}

Expand All @@ -71,7 +66,8 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
<tr>
{dataSeriesColumns.map((column, index) => {
const text = getSeriesName(column, config)

const newSortBy = getNewSortBy(sortBy, column, index)
const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
return (
<th
style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
Expand All @@ -81,19 +77,22 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
scope='col'
onClick={() => {
if (hasRowType) return
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
setSortBy(newSortBy)
}}
onKeyDown={e => {
if (hasRowType) return
if (e.keyCode === 13) {
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
setSortBy(newSortBy)
}
}}
className={handleHeaderClasses(sortBy, text)}
{...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
{...(sortBy.column === column
? sortBy.asc
? { 'aria-sort': 'ascending' }
: { 'aria-sort': 'descending' }
: null)}
>
<ColumnHeadingText text={text} column={column} config={config} />
{column === sortBy.column && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
<ColumnHeadingText text={text} config={config} />
{!hasRowType && <SortIcon ascending={sortByAsc} />}
<ScreenReaderSortByText sortBy={sortBy} config={config} text={text} />
</th>
)
Expand All @@ -107,7 +106,8 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
{['__series__', ...Object.keys(data)].slice(sliceVal).map((row, index) => {
let column = config.xAxis?.dataKey
let text = row !== '__series__' ? getChartCellValue(row, column, config, data) : '__series__'

const newSortBy = getNewSortBy(text, index)
const sortByAsc = sortBy.colIndex === index ? sortBy.asc : undefined
return (
<th
style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
Expand All @@ -116,18 +116,22 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }
role='columnheader'
scope='col'
onClick={() => {
setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
setSortBy(newSortBy)
}}
onKeyDown={e => {
if (e.keyCode === 13) {
setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
setSortBy(newSortBy)
}
}}
className={handleHeaderClasses(sortBy, text)}
{...(sortBy.column === text ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
{...(sortBy.column === text
? sortBy.asc
? { 'aria-sort': 'ascending' }
: { 'aria-sort': 'descending' }
: null)}
>
<ColumnHeadingText text={text} column={column} config={config} />
{index === sortBy.colIndex && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
<ColumnHeadingText text={text} config={config} />
{!hasRowType && <SortIcon ascending={sortByAsc} />}

<ScreenReaderSortByText text={text} config={config} sortBy={sortBy} />
</th>
)
Expand Down
10 changes: 0 additions & 10 deletions packages/core/components/DataTable/components/Icons.tsx

This file was deleted.

29 changes: 19 additions & 10 deletions packages/core/components/DataTable/components/MapHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { DataTableProps } from '../DataTable'
import { DownIcon, UpIcon } from './Icons'
import ScreenReaderText from '../../elements/ScreenReaderText'
import { SortIcon } from './SortIcon'
import { getNewSortBy } from '../helpers/getNewSortBy'

type MapHeaderProps = DataTableProps & {
sortBy: { column; asc }
setSortBy: Function
}

const ColumnHeadingText = ({ column, text, config }) => {
const ColumnHeadingText = ({ text, config }) => {
let notApplicableText = 'Not Applicable'
if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
if (text === '__series__' && !config.table.indexLabel) return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
if (text === '__series__' && !config.table.indexLabel)
return <ScreenReaderText as='span'>{notApplicableText}</ScreenReaderText>
return text
}

Expand All @@ -21,15 +23,16 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader
.filter(column => columns[column].dataTable === true && columns[column].name)
.map((column, index) => {
let text
if (column !== 'geo') {
if (column && column !== 'geo') {
text = columns[column].label ? columns[column].label : columns[column].name
} else {
text = config.type === 'map' ? indexTitle : config.xAxis?.dataKey
}
if (config.type === 'map' && (text === undefined || text === '')) {
text = 'Location'
}

const newSortBy = getNewSortBy(sortBy, column, index)
const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
return (
<th
key={`col-header-${column}__${index}`}
Expand All @@ -38,19 +41,25 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader
role='columnheader'
scope='col'
onClick={() => {
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
setSortBy(newSortBy)
}}
onKeyDown={e => {
if (e.keyCode === 13) {
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
setSortBy(newSortBy)
}
}}
className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
{...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
{...(sortBy.column === column
? sortBy.asc
? { 'aria-sort': 'ascending' }
: { 'aria-sort': 'descending' }
: null)}
>
<ColumnHeadingText text={text} config={config} column={column} />
{sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} order`}</span>
<SortIcon ascending={sortByAsc} />
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
} order`}</span>
</th>
)
})}
Expand Down
25 changes: 25 additions & 0 deletions packages/core/components/DataTable/components/SortIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import './sort-icon.css'

const UpIcon = ({ active }) => (
<svg className={'up' + (active ? ' active' : '')} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
<path d='M0 5l5-5 5 5z' />
</svg>
)
const DownIcon = ({ active }) => (
<svg className={'down' + (active ? ' active' : '')} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
<path d='M0 0l5 5 5-5z' />
</svg>
)

type SortIconProps = {
ascending?: boolean
}

export const SortIcon: React.FC<SortIconProps> = ({ ascending }) => {
return (
<span role='button' className={'sort-icon'}>
<UpIcon active={ascending === true} />
<DownIcon active={ascending === false} />
</span>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* format the white triangle sort icon in data table headers */
.sort-icon {
fill: white;
width: 15px;
height: 20px;
float: right;
position: relative;
:is(svg) {
position: absolute;
fill: rgba(255, 255, 255, 0.5);
&.active {
fill: white;
}
}
.up {
top: 0;
}
.down {
bottom: 0;
}
}
Loading
Loading