diff --git a/packages/core/components/DataTable/DataTable.tsx b/packages/core/components/DataTable/DataTable.tsx index 245f8f055..ceac0f5d2 100644 --- a/packages/core/components/DataTable/DataTable.tsx +++ b/packages/core/components/DataTable/DataTable.tsx @@ -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 @@ -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`, @@ -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', @@ -311,7 +313,7 @@ const DataTable = (props: DataTableProps) => { End Date } - tableOptions={{ className: 'region-table data-table' }} + tableOptions={{ className: 'table table-striped region-table data-table' }} fontSize={config.fontSize} /> )} @@ -329,7 +331,7 @@ const DataTable = (props: DataTableProps) => {
@@ -344,7 +346,7 @@ const DataTable = (props: DataTableProps) => { stickyHeader headContent={} 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 diff --git a/packages/core/components/DataTable/components/ChartHeader.tsx b/packages/core/components/DataTable/components/ChartHeader.tsx index 2b1b8833d..190a389af 100644 --- a/packages/core/components/DataTable/components/ChartHeader.tsx +++ b/packages/core/components/DataTable/components/ChartHeader.tsx @@ -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? } @@ -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} ` @@ -47,13 +37,18 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType } if (columnHeaderText === notApplicableText) return - return {`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'} order`} + return ( + {`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${ + sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending' + } order`} + ) } - 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 {notApplicableText} + if (text === '__series__' && !config.table.indexLabel) + return {notApplicableText} return text } @@ -71,7 +66,8 @@ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType } {dataSeriesColumns.map((column, index) => { const text = getSeriesName(column, config) - + const newSortBy = getNewSortBy(sortBy, column, index) + const sortByAsc = sortBy.column === column ? sortBy.asc : undefined return ( { 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)} > - - {column === sortBy.column && {!sortBy.asc ? : }} + + {!hasRowType && } ) @@ -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 ( { - 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)} > - - {index === sortBy.colIndex && {!sortBy.asc ? : }} + + {!hasRowType && } + ) diff --git a/packages/core/components/DataTable/components/Icons.tsx b/packages/core/components/DataTable/components/Icons.tsx deleted file mode 100644 index bf14636ec..000000000 --- a/packages/core/components/DataTable/components/Icons.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export const UpIcon = () => ( - - - -) -export const DownIcon = () => ( - - - -) diff --git a/packages/core/components/DataTable/components/MapHeader.tsx b/packages/core/components/DataTable/components/MapHeader.tsx index 6ee9f0b56..723018d1e 100644 --- a/packages/core/components/DataTable/components/MapHeader.tsx +++ b/packages/core/components/DataTable/components/MapHeader.tsx @@ -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 {notApplicableText} + if (text === '__series__' && !config.table.indexLabel) + return {notApplicableText} return text } @@ -21,7 +23,7 @@ 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 @@ -29,7 +31,8 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeader if (config.type === 'map' && (text === undefined || text === '')) { text = 'Location' } - + const newSortBy = getNewSortBy(sortBy, column, index) + const sortByAsc = sortBy.column === column ? sortBy.asc : undefined return ( { - 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)} > - {sortBy.column === column && {!sortBy.asc ? : }} - {`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} order`} + + {`Sort by ${text} in ${ + sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending' + } order`} ) })} diff --git a/packages/core/components/DataTable/components/SortIcon/index.tsx b/packages/core/components/DataTable/components/SortIcon/index.tsx new file mode 100644 index 000000000..780bb3370 --- /dev/null +++ b/packages/core/components/DataTable/components/SortIcon/index.tsx @@ -0,0 +1,25 @@ +import './sort-icon.css' + +const UpIcon = ({ active }) => ( + + + +) +const DownIcon = ({ active }) => ( + + + +) + +type SortIconProps = { + ascending?: boolean +} + +export const SortIcon: React.FC = ({ ascending }) => { + return ( + + + + + ) +} diff --git a/packages/core/components/DataTable/components/SortIcon/sort-icon.css b/packages/core/components/DataTable/components/SortIcon/sort-icon.css new file mode 100644 index 000000000..dcc7a978a --- /dev/null +++ b/packages/core/components/DataTable/components/SortIcon/sort-icon.css @@ -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; + } +} diff --git a/packages/core/styles/_data-table.scss b/packages/core/components/DataTable/data-table.css similarity index 74% rename from packages/core/styles/_data-table.scss rename to packages/core/components/DataTable/data-table.css index ed8268eb7..7f3acc998 100644 --- a/packages/core/styles/_data-table.scss +++ b/packages/core/components/DataTable/data-table.css @@ -1,298 +1,286 @@ -.collapsed + .table-container { - border-bottom: none; -} -.table-container { - overflow-x: auto; - border-right: 1px solid $lightGray; - border-left: 1px solid $lightGray; - border-bottom: 1px solid $lightGray; -} - -div.data-table-heading { - position: relative; - background: rgba(0, 0, 0, 0.05); - padding: 0.5em 0.7em; - border: $lightGray 1px solid; - cursor: pointer; - - svg { - position: absolute; - height: 100%; - width: 15px; - top: 0; - right: 1em; - } - - &:focus { - z-index: 2; - position: relative; - } - @include breakpoint(xs) { - font-size: $font-small + 0.2em; - } -} - -table.horizontal { - th, - td { - min-width: 200px; - } -} - -table.data-table { - min-width: 100%; - background: #fff; - position: relative; - border: none; - overflow-x: auto; - border-collapse: collapse; - overflow: auto; - appearance: none; - table-layout: fixed; - * { - box-sizing: border-box; - } - - thead { - user-select: none; - -moz-user-select: none; - user-select: none; - - button { - background: none; - font-size: initial; - color: #fff; - border: 0; - } - - tr { - background: none; - } - } - thead { - color: #fff; - background-color: $mediumGray; - .resizer { - cursor: e-resize; - width: 10px; - position: absolute; - top: 0; - bottom: 0; - right: 0; - touch-action: none; - } - tr { - text-align: left; - } - th, - td { - padding: 0.5em 1.3em 0.5em 0.7em; - line-height: normal; - position: relative; - text-align: left; - cursor: pointer; - border-right: 1px solid $lightGray !important; - svg { - margin-left: 1rem; - } - } - - th.sort { - background-color: darken($mediumGray, 10%); - background-repeat: no-repeat; - background-position: right 0.5em center; - background-size: 10px 5px; - } - - // format the white triangle sort icon in data table headers - .sort-icon { - fill: white; - width: 30px; - float: right; - align-self: center; - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - } - - th:last-child, - td:last-child { - border-right: 0; - } - } - - tbody { - tr { - width: 100%; - &:hover { - background: rgba(0, 0, 0, 0.05); - } - } - } - - tr { - border-bottom: solid 1px #e5e5e5; - min-width: 100%; // Needed to fill content up - &:last-child { - border-bottom: 0; - } - } - - td { - padding: 0.3em 0.7em; - - border-right: 1px solid rgba(0, 0, 0, 0.1); - } - - th, - td { - width: 1% !important; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - &:last-child { - border-right: 0 !important; - } - - @include breakpoint(xs) { - font-size: $font-small; - } - } - tr { - & > * { - width: 100%; - } - } - th { - flex-grow: 1; - } - - td { - position: relative; - flex-grow: 1; - } - - td a { - padding: 0.3em 0.7em; - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - display: block; - color: inherit; - text-decoration: none; - } - - td span.table-link { - text-decoration: underline; - cursor: pointer; - color: #075290; - svg { - max-width: 13px; - vertical-align: baseline; - margin-left: 5px; - } - } - - .boxplot-td { - //display: inline-block; - //box-sizing: border-box; - table-layout: fixed; - width: 200; - //min-width: 150px; - //max-width: 400px; - } -} - -.no-data { - position: relative; - .no-data-message { - @include emptyState; - h3 { - font-size: 1.3rem; - font-weight: 600; - margin-bottom: 0.3rem; - } - } - tr:hover { - background: #fff; - } - th, - td { - width: 50%; - &::before { - content: '\00a0'; - } - } -} - -.data-table-pagination { - margin: 1rem 0; - display: flex; - align-items: center; - ul { - list-style: none; - margin: 0 1rem 0 0; - display: flex; - li + li { - margin-left: 0.3rem; - } - button { - background: $mediumGray; - padding: 0.6rem 0.8rem; - &:hover { - background: lighten($mediumGray, 5%); - } - } - button.btn-next { - &::before { - content: ' '; - background-image: url(../assets/icon-caret-filled-up.svg); - background-size: 10px 5px; - width: 10px; - height: 5px; - display: block; - transform: rotate(90deg); - } - } - button.btn-prev { - &::before { - content: ' '; - background-image: url(../assets/icon-caret-filled-up.svg); - background-size: 10px 5px; - width: 10px; - height: 5px; - display: block; - transform: rotate(-90deg); - } - } - button[disabled] { - background: $mediumGray; - opacity: 0.3; - cursor: default; - &:hover { - background: $mediumGray; - } - } - } -} - -.btn-download { - color: #fff; - float: right; - text-decoration: none; - transition: 0.3s all; - margin: 1em 0; - &:hover { - transition: 0.3s all; - } -} -.cove, -.cdc-open-viz-module { - .download-links a:not(:last-child) { - margin-right: 10px; - display: inline-block; - } -} +.collapsed + .table-container { + border-bottom: none; +} +.table-container { + overflow-x: auto; + border-right: 1px solid var(--lightGray); + border-left: 1px solid var(--lightGray); + border-bottom: 1px solid var(--lightGray); +} + +div.data-table-heading { + position: relative; + background: rgba(0, 0, 0, 0.05); + padding: 0.5em 0.7em; + border: var(--lightGray) 1px solid; + + svg { + position: absolute; + height: 100%; + width: 15px; + top: 0; + right: 1em; + } + + &:focus { + z-index: 2; + position: relative; + } + @media (max-width: var(--breakpoint-sm)) { + font-size: var(--font-small) + 0.2em; + } +} + +table.horizontal { + th, + td { + min-width: 200px; + } +} + +table.data-table { + margin-bottom: 0; + min-width: 100%; + background: #fff; + position: relative; + border: none; + overflow-x: auto; + border-collapse: collapse; + overflow: auto; + appearance: none; + table-layout: fixed; + * { + box-sizing: border-box; + } + + thead { + user-select: none; + -moz-user-select: none; + user-select: none; + + button { + background: none; + font-size: initial; + color: #fff; + border: 0; + } + + tr { + background: none; + } + } + thead { + color: #fff; + .resizer { + cursor: e-resize; + width: 10px; + position: absolute; + top: 0; + bottom: 0; + right: 0; + touch-action: none; + } + tr { + text-align: left; + } + th, + td { + padding: 0.5em 1.3em 0.5em 0.7em; + line-height: normal; + position: relative; + text-align: left; + border-right: 1px solid var(--lightGray) !important; + } + + th { + background-color: var(--primary); + background-repeat: no-repeat; + background-position: right 0.5em center; + background-size: 10px 5px; + } + + th:last-child, + td:last-child { + border-right: 0; + } + } + + tbody { + tr { + width: 100%; + } + } + + tr { + &.row-group { + background-color: var(--tertiary) !important; + font-weight: bold; + } + border-bottom: solid 1px #e5e5e5; + min-width: 100%; /* Needed to fill content up*/ + &:last-child { + border-bottom: 0; + } + } + + th, + td { + padding: 0.3em 0.7em; + border-right: 1px solid rgba(0, 0, 0, 0.1); + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + &:last-child { + border-right: 0 !important; + } + + @media (max-width: var(--breakpoint-sm)) { + font-size: var(--font-small) + 0.2em; + } + } + tr { + & > * { + width: 100%; + } + } + th { + flex-grow: 1; + } + + td { + position: relative; + flex-grow: 1; + svg { + margin-left: 1rem; + } + } + + td a { + padding: 0.3em 0.7em; + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + display: block; + color: inherit; + text-decoration: none; + } + + td span.table-link { + text-decoration: underline; + cursor: pointer; + color: #075290; + svg { + max-width: 13px; + vertical-align: baseline; + margin-left: 5px; + } + } + + .boxplot-td { + table-layout: fixed; + width: 200; + } +} + +.no-data { + position: relative; + .no-data-message { + background: rgba(255, 255, 255, 0.5); + top: 0; + left: 0; + right: 0; + bottom: 0; + position: absolute; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + z-index: 7; + :is(h3) { + font-size: 1.3rem; + font-weight: 600; + margin-bottom: 0.3rem; + } + } + tr:hover { + background: #fff; + } + th, + td { + width: 50%; + &::before { + content: '\00a0'; + } + } +} + +.data-table-pagination { + margin: 1rem 0; + display: flex; + align-items: center; + ul { + list-style: none; + margin: 0 1rem 0 0; + display: flex; + li + li { + margin-left: 0.3rem; + } + button { + background: var(--mediumGray); + padding: 0.6rem 0.8rem; + &:hover { + background: lighten(var(--mediumGray), 5%); + } + } + button.btn-next { + &::before { + content: ' '; + background-image: url(../assets/icon-caret-filled-up.svg); + background-size: 10px 5px; + width: 10px; + height: 5px; + display: block; + transform: rotate(90deg); + } + } + button.btn-prev { + &::before { + content: ' '; + background-image: url(../assets/icon-caret-filled-up.svg); + background-size: 10px 5px; + width: 10px; + height: 5px; + display: block; + transform: rotate(-90deg); + } + } + button[disabled] { + background: var(--mediumGray); + opacity: 0.3; + cursor: default; + &:hover { + background: var(--mediumGray); + } + } + } +} + +.btn-download { + color: #fff; + float: right; + text-decoration: none; + transition: 0.3s all; + margin: 1em 0; + &:hover { + transition: 0.3s all; + } +} +.cove, +.cdc-open-viz-module { + .download-links a:not(:last-child) { + margin-right: 10px; + display: inline-block; + } +} diff --git a/packages/core/components/DataTable/helpers/customSort.ts b/packages/core/components/DataTable/helpers/customSort.ts index 6cd503eea..35bf4e2b2 100644 --- a/packages/core/components/DataTable/helpers/customSort.ts +++ b/packages/core/components/DataTable/helpers/customSort.ts @@ -14,7 +14,7 @@ export const customSort = (a, b, sortBy, config) => { const numberB = parseInt(b.match(/\d+$/)[0], 10) // Compare the numeric parts - return !sortBy.asc ? Number(numberA) - Number(numberB) : Number(numberB) - Number(numberA) + return sortBy.asc ? Number(numberA) - Number(numberB) : Number(numberB) - Number(numberA) } } @@ -26,39 +26,35 @@ export const customSort = (a, b, sortBy, config) => { const trimmedB = String(valueB).trim() if (config.xAxis?.dataKey === sortBy.column && config.xAxis.type === 'date') { - let dateA = parseDate(config.xAxis.dateParseFormat, trimmedA) + const dateA = parseDate(config.xAxis.dateParseFormat, trimmedA)?.getTime() - let dateB = parseDate(config.xAxis.dateParseFormat, trimmedB) - - if (dateA && dateA.getTime) dateA = dateA.getTime() - - if (dateB && dateB.getTime) dateB = dateB.getTime() - - return !sortBy.asc ? dateA - dateB : dateB - dateA + const dateB = parseDate(config.xAxis.dateParseFormat, trimmedB)?.getTime() + console.log(dateA, dateB) + return sortBy.asc ? dateA - dateB : dateB - dateA } // Check if values are numbers const isNumA = !isNaN(Number(valueA)) && valueA !== undefined && valueA !== null && trimmedA !== '' const isNumB = !isNaN(Number(valueB)) && valueB !== undefined && valueB !== null && trimmedB !== '' // Handle empty strings or spaces - if (trimmedA === '' && trimmedB !== '') return !sortBy.asc ? -1 : 1 - if (trimmedA !== '' && trimmedB === '') return !sortBy.asc ? 1 : -1 + if (trimmedA === '' && trimmedB !== '') return sortBy.asc ? -1 : 1 + if (trimmedA !== '' && trimmedB === '') return sortBy.asc ? 1 : -1 // Both are numbers: Compare numerically if (isNumA && isNumB) { - return !sortBy.asc ? Number(valueA) - Number(valueB) : Number(valueB) - Number(valueA) + return sortBy.asc ? Number(valueA) - Number(valueB) : Number(valueB) - Number(valueA) } // Only A is a number if (isNumA) { - return !sortBy.asc ? -1 : 1 + return sortBy.asc ? -1 : 1 } // Only B is a number if (isNumB) { - return !sortBy.asc ? 1 : -1 + return sortBy.asc ? 1 : -1 } // Neither are numbers: Compare as strings - return !sortBy.asc ? trimmedA.localeCompare(trimmedB) : trimmedB.localeCompare(trimmedA) + return sortBy.asc ? trimmedA.localeCompare(trimmedB) : trimmedB.localeCompare(trimmedA) } diff --git a/packages/core/components/DataTable/helpers/getNewSortBy.ts b/packages/core/components/DataTable/helpers/getNewSortBy.ts new file mode 100644 index 000000000..b42a1af91 --- /dev/null +++ b/packages/core/components/DataTable/helpers/getNewSortBy.ts @@ -0,0 +1,26 @@ +export const getNewSortBy = (sortBy, column, index) => { + let asc + let sortByCol = column + const ascending = sortBy.asc === true + const descending = sortBy.asc === false + const isInactive = sortBy.asc === undefined + const noColumnSelected = sortBy.column === undefined + if (noColumnSelected || sortBy.column !== column) { + // this is the first time a column is clicked + asc = true + } else { + // clicking the same column + if (descending) { + // reset + sortByCol = undefined + asc = undefined + } + if (isInactive) { + asc = true + } + if (ascending) { + asc = false + } + } + return { column: sortByCol, asc, colIndex: index } +} diff --git a/packages/core/components/DataTable/helpers/tests/customSort.test.ts b/packages/core/components/DataTable/helpers/tests/customSort.test.ts new file mode 100644 index 000000000..d44e7cfa0 --- /dev/null +++ b/packages/core/components/DataTable/helpers/tests/customSort.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest' +import { customSort } from '../customSort' + +describe('customSort()', () => { + it('should return positive number when a > b', () => { + const a = 3 + const b = 1 + const sortBy = { column: 'someColumn', asc: true, colIndex: 0 } + const config = { type: 'map' } + expect(customSort(a, b, sortBy, config)).greaterThan(0) + expect(customSort(a, b, sortBy, { type: 'notMap' })).greaterThan(0) + }) + it('should return negative number when a < b', () => { + const a = 1 + const b = 3 + const sortBy = { column: 'someColumn', asc: true, colIndex: 0 } + const config = { type: 'map' } + expect(customSort(a, b, sortBy, config)).lessThan(0) + expect(customSort(a, b, sortBy, { type: 'notMap' })).lessThan(0) + }) + it('works for dates', () => { + const a = 2000 + const b = 1999 + const sortBy = { column: 'someColumn', asc: true, colIndex: 0 } + expect( + customSort(a, b, sortBy, { xAxis: { dataKey: sortBy.column, dateParseFormat: '%Y', type: 'date' } }) + ).greaterThan(0) + expect( + customSort(b, a, sortBy, { xAxis: { dataKey: sortBy.column, dateParseFormat: '%Y', type: 'date' } }) + ).lessThan(0) + }) + it('works for strings', () => { + const a = 'banana' + const b = 'apple' + const sortBy = { column: 'someColumn', asc: true, colIndex: 0 } + const config = { type: 'map' } + expect(customSort(a, b, sortBy, config)).greaterThan(0) + expect(customSort(a, b, sortBy, { type: 'notMap' })).greaterThan(0) + expect(customSort(b, a, sortBy, config)).lessThan(0) + expect(customSort(b, a, sortBy, { type: 'notMap' })).lessThan(0) + }) + it('works for strings after number', () => { + const a = 'banana' + const b = '1' + const sortBy = { column: 'someColumn', asc: true, colIndex: 0 } + const config = { type: 'map' } + expect(customSort(a, b, sortBy, config)).greaterThan(0) + expect(customSort(a, b, sortBy, { type: 'notMap' })).greaterThan(0) + expect(customSort(b, a, sortBy, config)).lessThan(0) + expect(customSort(b, a, sortBy, { type: 'notMap' })).lessThan(0) + }) +}) diff --git a/packages/core/components/DataTable/helpers/tests/getNewSortBy.test.ts b/packages/core/components/DataTable/helpers/tests/getNewSortBy.test.ts new file mode 100644 index 000000000..c06c64862 --- /dev/null +++ b/packages/core/components/DataTable/helpers/tests/getNewSortBy.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest' +import { getNewSortBy } from '../getNewSortBy' + +describe('getNewSortBy()', () => { + it('should return ascending when currently undefined', () => { + const sortBy = { column: undefined, asc: undefined, colIndex: 0 } + const column = 'someColumn' + const index = 1 + const result = getNewSortBy(sortBy, column, index) + expect(result).toEqual({ column, asc: true, colIndex: index }) + }) + it('should return ascending false when currently true', () => { + const sortBy = { column: 'someColumn', asc: true, colIndex: 0 } + const column = 'someColumn' + const index = 1 + const result = getNewSortBy(sortBy, column, index) + expect(result).toEqual({ column, asc: false, colIndex: index }) + }) + it('should return ascending undefined when currently false', () => { + const sortBy = { column: 'someColumn', asc: false, colIndex: 0 } + const column = 'someColumn' + const index = 1 + const result = getNewSortBy(sortBy, column, index) + expect(result).toEqual({ column: undefined, asc: undefined, colIndex: index }) + }) +}) diff --git a/packages/core/components/Table/components/GroupRow.tsx b/packages/core/components/Table/components/GroupRow.tsx index eea9004b9..aa59ad898 100644 --- a/packages/core/components/Table/components/GroupRow.tsx +++ b/packages/core/components/Table/components/GroupRow.tsx @@ -8,7 +8,7 @@ type GroupRowProps = { const GroupRow = ({ label, colSpan, data }: GroupRowProps) => { return ( - + {label} diff --git a/packages/core/components/_stories/styles.scss b/packages/core/components/_stories/styles.scss index 5a75c69b4..54e7d4af6 100644 --- a/packages/core/components/_stories/styles.scss +++ b/packages/core/components/_stories/styles.scss @@ -1,7 +1,6 @@ @import '../../styles/base.scss'; @import '../../styles/_variables'; @import '../../styles/_mixins'; -@import '../../styles/_data-table'; @import '../../styles/_global.scss'; .visually-hidden { diff --git a/packages/core/styles/_global-variables.scss b/packages/core/styles/_global-variables.scss index bcfed7754..dd6dd3166 100644 --- a/packages/core/styles/_global-variables.scss +++ b/packages/core/styles/_global-variables.scss @@ -70,6 +70,15 @@ $colors: ( --#{$key}: #{$value}; } --editorWidth: 350px; + + --font-small: 0.7em; + + --breakpoint-xs: 480px; + --breakpoint-sm: 768px; + --breakpoint-md: 960px; + --breakpoint-lg: 1170px; + --breakpoint-xl: 1280px; + } } diff --git a/packages/core/styles/_reset.scss b/packages/core/styles/_reset.scss index d3280a9af..049c6ba74 100644 --- a/packages/core/styles/_reset.scss +++ b/packages/core/styles/_reset.scss @@ -18,7 +18,6 @@ } } .cdc-open-viz-module { - div, span, applet, object, @@ -69,14 +68,7 @@ form, label, legend, - table, caption, - tbody, - tfoot, - thead, - tr, - th, - td, article, aside, canvas, diff --git a/packages/core/styles/base.scss b/packages/core/styles/base.scss index bd3c59d15..44cdefdbe 100644 --- a/packages/core/styles/base.scss +++ b/packages/core/styles/base.scss @@ -71,7 +71,6 @@ body.post-type-cdc_visualization .cdc-editor .configure .editor-panel { @include breakpointClass(md) { font-size: 16px !important; } - @import 'data-table'; @import 'global'; @import 'button-section'; @import 'series-list'; diff --git a/packages/core/styles/v2/layout/index.scss b/packages/core/styles/v2/layout/index.scss index b84347f3b..6775a4afb 100644 --- a/packages/core/styles/v2/layout/index.scss +++ b/packages/core/styles/v2/layout/index.scss @@ -1,5 +1,4 @@ @import 'alert'; @import 'component'; -@import 'data-table'; @import 'progression'; @import 'tooltip'; diff --git a/packages/editor/src/components/PreviewDataTable.tsx b/packages/editor/src/components/PreviewDataTable.tsx index d10c10a45..cf1ec762b 100644 --- a/packages/editor/src/components/PreviewDataTable.tsx +++ b/packages/editor/src/components/PreviewDataTable.tsx @@ -24,7 +24,16 @@ const TableFilter = memo(({ globalFilter, setGlobalFilter, disabled = false }: a setFilterValue(e.target.value) } - return + return ( + + ) }) const Header = memo(({ globalFilter, data, setGlobalFilter }: any) => ( @@ -38,7 +47,12 @@ const Footer = memo(({ previousPage, nextPage, canPreviousPage, canNextPage, pag