diff --git a/packages/ui/src/assets/icons.svg b/packages/ui/src/assets/icons.svg index 5f92710201..55426d37bf 100644 --- a/packages/ui/src/assets/icons.svg +++ b/packages/ui/src/assets/icons.svg @@ -1,20 +1,26 @@ - + - + - + + + + + + + - + - + @@ -22,16 +28,16 @@ - + - + - + @@ -39,7 +45,7 @@ - + @@ -51,7 +57,7 @@ - + diff --git a/packages/ui/src/assets/icons.svg.jsx b/packages/ui/src/assets/icons.svg.jsx index f4e4c96cce..8e48fc0003 100644 --- a/packages/ui/src/assets/icons.svg.jsx +++ b/packages/ui/src/assets/icons.svg.jsx @@ -9,7 +9,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-arrow-down" id="arrow" xmlns="http://www.w3.org/2000/svg" > @@ -21,7 +20,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-arrow-right-circle" viewBox="0 0 24 24" id="arrow-right-circle" xmlns="http://www.w3.org/2000/svg" @@ -29,6 +27,30 @@ export const SvgIcons = (props) => ( + + + + + + ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-clock" id="clock" xmlns="http://www.w3.org/2000/svg" > @@ -49,7 +70,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-x-circle" viewBox="0 0 24 24" id="close" xmlns="http://www.w3.org/2000/svg" @@ -64,7 +84,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-git-commit" id="commit" xmlns="http://www.w3.org/2000/svg" > @@ -90,7 +109,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-external-link" viewBox="0 0 24 24" id="external-link" xmlns="http://www.w3.org/2000/svg" @@ -104,7 +122,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-bar-chart" id="filter" xmlns="http://www.w3.org/2000/svg" > @@ -125,7 +142,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-help-circle" id="help" xmlns="http://www.w3.org/2000/svg" > @@ -152,7 +168,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-menu" id="menu" xmlns="http://www.w3.org/2000/svg" > @@ -183,7 +198,6 @@ export const SvgIcons = (props) => ( stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - class="feather feather-alert-triangle" id="warning" xmlns="http://www.w3.org/2000/svg" > diff --git a/packages/ui/src/assets/icons/chevron-down.svg b/packages/ui/src/assets/icons/chevron-down.svg new file mode 100644 index 0000000000..b26052518d --- /dev/null +++ b/packages/ui/src/assets/icons/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/chevron-up.svg b/packages/ui/src/assets/icons/chevron-up.svg new file mode 100644 index 0000000000..2cc3795040 --- /dev/null +++ b/packages/ui/src/assets/icons/chevron-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.constants.js b/packages/ui/src/components/bundle-assets/bundle-assets.constants.js deleted file mode 100644 index 2e6ea89fb2..0000000000 --- a/packages/ui/src/components/bundle-assets/bundle-assets.constants.js +++ /dev/null @@ -1,18 +0,0 @@ -export const SORT_BY_NAME = 'name'; -export const SORT_BY_SIZE = 'size'; -export const SORT_BY_DELTA = 'delta'; - -export const SORT_BY = { - [SORT_BY_NAME]: { - label: 'Name', - defaultDirection: 'asc', - }, - [SORT_BY_DELTA]: { - label: 'Delta', - defaultDirection: 'desc', - }, - [SORT_BY_SIZE]: { - label: 'Size', - defaultDirection: 'desc', - }, -}; diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.jsx b/packages/ui/src/components/bundle-assets/bundle-assets.jsx index 82e4b3ec17..2f71356711 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.jsx +++ b/packages/ui/src/components/bundle-assets/bundle-assets.jsx @@ -19,7 +19,6 @@ import { FileName } from '../../ui/file-name'; import { HoverCard } from '../../ui/hover-card'; import { Tag } from '../../ui/tag'; import { Filters } from '../../ui/filters'; -import { SortDropdown } from '../../ui/sort-dropdown'; import { EmptySet } from '../../ui/empty-set'; import { Toolbar } from '../../ui/toolbar'; import { AssetInfo } from '../asset-info'; @@ -170,7 +169,6 @@ export const BundleAssets = (props) => { filters, entryId, hasActiveFilters, - sortFields, sort, updateSort, search, @@ -221,7 +219,6 @@ export const BundleAssets = (props) => { className={css.toolbar} renderActions={({ actionClassName }) => ( - { emptyMessage={emptyMessage} showHeaderSum title={metricsTableTitle} + sort={sort} + updateSort={updateSort} /> @@ -313,12 +312,6 @@ BundleAssets.propTypes = { }).isRequired, entryId: PropTypes.string, hasActiveFilters: PropTypes.bool, - sortFields: PropTypes.shape({ - [PropTypes.string]: PropTypes.shape({ - label: PropTypes.string, - defaultDirection: PropTypes.bool, - }), - }).isRequired, search: PropTypes.string.isRequired, updateSearch: PropTypes.func.isRequired, sort: PropTypes.shape({ diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js index 1ce14f8d15..41d1fd4690 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js +++ b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js @@ -1,7 +1,5 @@ import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, getFileType } from '@bundle-stats/utils'; -import { SORT_BY_NAME, SORT_BY_DELTA, SORT_BY_SIZE } from './bundle-assets.constants'; - export const addRowAssetFlags = (row) => { const { runs } = row; @@ -72,25 +70,11 @@ export const getRowFilter = (filters) => (item) => { return true; }; -export const getCustomSort = (sortId) => (item) => { - if (sortId === SORT_BY_NAME) { - return item.key; - } - - if (sortId === SORT_BY_DELTA) { - return item?.runs?.[0]?.delta ? Math.abs(item.runs[0].delta) : 0; - } - - if (sortId === SORT_BY_SIZE) { - return item?.runs?.[0]?.value || 0; - } - - return [ - !item.isNotPredictive, - !item.changed, - !item.isInitial, - !item.isEntry, - !item.isChunk, - item.key, - ]; -}; +export const getCustomSort = (item) => [ + !item.isNotPredictive, + !item.changed, + !item.isInitial, + !item.isEntry, + !item.isChunk, + item.key, +]; diff --git a/packages/ui/src/components/bundle-assets/index.jsx b/packages/ui/src/components/bundle-assets/index.jsx index b1cab83971..a1d097b594 100644 --- a/packages/ui/src/components/bundle-assets/index.jsx +++ b/packages/ui/src/components/bundle-assets/index.jsx @@ -12,7 +12,6 @@ import { useRowsSort } from '../../hooks/rows-sort'; import { useSearchParams } from '../../hooks/search-params'; import { BundleAssets as BundleAssetsComponent } from './bundle-assets'; import { addRowAssetFlags, addRowIsNotPredictive, getRowFilter, getCustomSort } from './bundle-assets.utils'; -import { SORT_BY } from './bundle-assets.constants'; export const BundleAssets = (props) => { const { jobs, filters, search, setState, sortBy, direction, ...restProps } = props; @@ -55,7 +54,6 @@ export const BundleAssets = (props) => { const sortParams = useRowsSort({ rows: filteredRows, - sortFields: SORT_BY, sortBy, sortDirection: direction, getCustomSort, diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.constants.js b/packages/ui/src/components/bundle-modules/bundle-modules.constants.js deleted file mode 100644 index 2e6ea89fb2..0000000000 --- a/packages/ui/src/components/bundle-modules/bundle-modules.constants.js +++ /dev/null @@ -1,18 +0,0 @@ -export const SORT_BY_NAME = 'name'; -export const SORT_BY_SIZE = 'size'; -export const SORT_BY_DELTA = 'delta'; - -export const SORT_BY = { - [SORT_BY_NAME]: { - label: 'Name', - defaultDirection: 'asc', - }, - [SORT_BY_DELTA]: { - label: 'Delta', - defaultDirection: 'desc', - }, - [SORT_BY_SIZE]: { - label: 'Size', - defaultDirection: 'desc', - }, -}; diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.jsx b/packages/ui/src/components/bundle-modules/bundle-modules.jsx index 6278efd493..2cc2b97f68 100644 --- a/packages/ui/src/components/bundle-modules/bundle-modules.jsx +++ b/packages/ui/src/components/bundle-modules/bundle-modules.jsx @@ -21,7 +21,6 @@ import { FlexStack } from '../../layout/flex-stack'; import { EmptySet } from '../../ui/empty-set'; import { FileName } from '../../ui/file-name'; import { Filters } from '../../ui/filters'; -import { SortDropdown } from '../../ui/sort-dropdown'; import { Tag } from '../../ui/tag'; import { Toolbar } from '../../ui/toolbar'; import { MetricsTable } from '../metrics-table'; @@ -125,7 +124,6 @@ export const BundleModules = ({ resetAllFilters, filters, entryId, - sortFields, sort, updateSort, search, @@ -179,12 +177,6 @@ export const BundleModules = ({ className={css.toolbar} renderActions={({ actionClassName }) => ( - {entryId && ( @@ -277,12 +271,6 @@ BundleModules.propTypes = { hasActiveFilters: PropTypes.bool, - sortFields: PropTypes.shape({ - [PropTypes.string]: PropTypes.shape({ - label: PropTypes.string, - defaultDirection: PropTypes.bool, - }), - }).isRequired, sort: PropTypes.shape({ sortBy: PropTypes.string, direction: PropTypes.string, diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.utils.js b/packages/ui/src/components/bundle-modules/bundle-modules.utils.js index ab648c719a..e398c4cb74 100644 --- a/packages/ui/src/components/bundle-modules/bundle-modules.utils.js +++ b/packages/ui/src/components/bundle-modules/bundle-modules.utils.js @@ -11,8 +11,6 @@ import { getModuleSourceFileType, } from '@bundle-stats/utils'; -import { SORT_BY_NAME, SORT_BY_SIZE, SORT_BY_DELTA } from './bundle-modules.constants'; - export const addRowFlags = (row) => { const { key, runs } = row; @@ -28,21 +26,7 @@ export const addRowFlags = (row) => { return row; }; -export const getCustomSort = (sortBy) => (item) => { - if (sortBy === SORT_BY_NAME) { - return item.key; - } - - if (sortBy === SORT_BY_SIZE) { - return item?.runs?.[0]?.value || 0; - } - - if (sortBy === SORT_BY_DELTA) { - return item?.runs?.[0]?.delta ? Math.abs(item.runs[0].delta) : 0; - } - - return [!item.changed, item.key]; -}; +export const getCustomSort = (item) => [!item.changed, item.key]; export const getRowFilter = (filters) => (row) => { // Skip not changed rows diff --git a/packages/ui/src/components/bundle-modules/index.jsx b/packages/ui/src/components/bundle-modules/index.jsx index 98cb4f9582..233e5c597c 100644 --- a/packages/ui/src/components/bundle-modules/index.jsx +++ b/packages/ui/src/components/bundle-modules/index.jsx @@ -19,7 +19,6 @@ import { getCustomSort, useModuleFilterByChunk, } from './bundle-modules.utils'; -import { SORT_BY } from './bundle-modules.constants'; export const BundleModules = (props) => { const { jobs, filters, search, setState, sortBy, direction, ...restProps } = props; @@ -88,9 +87,8 @@ export const BundleModules = (props) => { const sortParams = useRowsSort({ rows: filteredRows, - sortFields: SORT_BY, - sortBy, - sortDirection: direction, + fieldPath: sortBy, + direction, getCustomSort, }); diff --git a/packages/ui/src/components/bundle-packages/bundle-packages.constants.js b/packages/ui/src/components/bundle-packages/bundle-packages.constants.js deleted file mode 100644 index 2e6ea89fb2..0000000000 --- a/packages/ui/src/components/bundle-packages/bundle-packages.constants.js +++ /dev/null @@ -1,18 +0,0 @@ -export const SORT_BY_NAME = 'name'; -export const SORT_BY_SIZE = 'size'; -export const SORT_BY_DELTA = 'delta'; - -export const SORT_BY = { - [SORT_BY_NAME]: { - label: 'Name', - defaultDirection: 'asc', - }, - [SORT_BY_DELTA]: { - label: 'Delta', - defaultDirection: 'desc', - }, - [SORT_BY_SIZE]: { - label: 'Size', - defaultDirection: 'desc', - }, -}; diff --git a/packages/ui/src/components/bundle-packages/bundle-packages.jsx b/packages/ui/src/components/bundle-packages/bundle-packages.jsx index ffe2837701..250cb46f25 100644 --- a/packages/ui/src/components/bundle-packages/bundle-packages.jsx +++ b/packages/ui/src/components/bundle-packages/bundle-packages.jsx @@ -8,7 +8,6 @@ import I18N from '../../i18n'; import { FlexStack } from '../../layout/flex-stack'; import { EmptySet } from '../../ui/empty-set'; import { Filters } from '../../ui/filters'; -import { SortDropdown } from '../../ui/sort-dropdown'; import { Tag } from '../../ui/tag'; import { Toolbar } from '../../ui/toolbar'; import { ComponentLink } from '../component-link'; @@ -117,7 +116,6 @@ export const BundlePackages = (props) => { resetAllFilters, totalRowCount, filters, - sortFields, sort, updateSort, search, @@ -178,7 +176,6 @@ export const BundlePackages = (props) => { className={css.toolbar} renderActions={({ actionClassName }) => ( - { renderRowHeader={renderRowHeader} showHeaderSum title={metricsTableTitle} + sort={sort} + updateSort={updateSort} /> @@ -259,12 +258,6 @@ BundlePackages.propTypes = { changed: PropTypes.bool, }).isRequired, hasActiveFilters: PropTypes.bool, - sortFields: PropTypes.shape({ - [PropTypes.string]: PropTypes.shape({ - label: PropTypes.string, - defaultDirection: PropTypes.bool, - }), - }).isRequired, sort: PropTypes.shape({ sortBy: PropTypes.string, direction: PropTypes.string, diff --git a/packages/ui/src/components/bundle-packages/bundle-packages.utils.js b/packages/ui/src/components/bundle-packages/bundle-packages.utils.js index b3d57fb0ba..bdeccb6b63 100644 --- a/packages/ui/src/components/bundle-packages/bundle-packages.utils.js +++ b/packages/ui/src/components/bundle-packages/bundle-packages.utils.js @@ -1,8 +1,6 @@ import uniq from 'lodash/uniq'; import { PACKAGE_FILTERS } from '@bundle-stats/utils'; -import { SORT_BY_NAME, SORT_BY_DELTA, SORT_BY_SIZE } from './bundle-packages.constants'; - // Get a list of duplicate packages across jobs export const getDuplicatePackages = (jobs) => { const jobsDuplicatePackages = jobs.map((job) => { @@ -25,21 +23,7 @@ export const getRowFilter = (filters) => (item) => { return true; }; -export const getCustomSort = (sortId) => (item) => { - if (sortId === SORT_BY_NAME) { - return item.key; - } - - if (sortId === SORT_BY_DELTA) { - return item?.runs?.[0]?.delta ? Math.abs(item.runs[0].delta) : 0; - } - - if (sortId === SORT_BY_SIZE) { - return item?.runs?.[0]?.value || 0; - } - - return [!item.changed, item.key]; -}; +export const getCustomSort = (item) => [!item.changed, item.key]; export const getAddRowDuplicateFlag = (duplicateJobs) => (row) => ({ ...row, diff --git a/packages/ui/src/components/bundle-packages/index.jsx b/packages/ui/src/components/bundle-packages/index.jsx index 6e37a1f56b..96e9c1a035 100644 --- a/packages/ui/src/components/bundle-packages/index.jsx +++ b/packages/ui/src/components/bundle-packages/index.jsx @@ -6,7 +6,6 @@ import * as webpack from '@bundle-stats/utils/lib-esm/webpack/compare'; import { useRowsFilter } from '../../hooks/rows-filter'; import { useRowsSort } from '../../hooks/rows-sort'; import { useSearchParams } from '../../hooks/search-params'; -import { SORT_BY } from './bundle-packages.constants'; import { BundlePackages as BundlePackagesComponent } from './bundle-packages'; import { getDuplicatePackages, getAddRowDuplicateFlag, getRowFilter, getCustomSort } from './bundle-packages.utils'; @@ -50,9 +49,8 @@ export const BundlePackages = (props) => { const sortParams = useRowsSort({ rows: filteredRows, - sortFields: SORT_BY, - sortBy, - sortDirection: direction, + fieldPath: sortBy, + direction, getCustomSort, }); diff --git a/packages/ui/src/components/metrics-table/metrics-table.jsx b/packages/ui/src/components/metrics-table/metrics-table.jsx index f65f9755cc..1b711a7aa5 100644 --- a/packages/ui/src/components/metrics-table/metrics-table.jsx +++ b/packages/ui/src/components/metrics-table/metrics-table.jsx @@ -11,6 +11,7 @@ import { Stack } from '../../layout/stack'; import { Metric } from '../metric'; import { Delta } from '../delta'; import { JobName } from '../job-name'; +import { SortButton } from '../sort-button'; import styles from './metrics-table.module.css'; const METRIC_TYPE_DATA = getGlobalMetricType(null, METRIC_TYPE_FILE_SIZE); @@ -20,15 +21,14 @@ const CURRENT_COLUMN_SPAN = 3; const BASELINE_TITLE = 'Baseline'; const CURRENT_TITLE = 'Current'; -const getRowsRunTotal = (rows, runIndex) => sum(rows.map((row) => row?.runs?.[runIndex]?.value || 0)); +const getRowsRunTotal = (rows, runIndex) => + sum(rows.map((row) => row?.runs?.[runIndex]?.value || 0)); const ColumnJob = ({ run, isBaseline }) => { const colSpan = isBaseline ? BASELINE_COLUMN_SPAN : CURRENT_COLUMN_SPAN; if (!run) { - return ( - - - ); + return -; } const { label, internalBuildNumber } = run; @@ -36,7 +36,7 @@ const ColumnJob = ({ run, isBaseline }) => { return ( @@ -46,29 +46,66 @@ const ColumnJob = ({ run, isBaseline }) => { ); }; -const ColumnSum = ({ rows, isBaseline, runIndex }) => { +const ColumnSum = ({ rows, isBaseline, runIndex, updateSort, sort }) => { const currentRunTotal = getRowsRunTotal(rows, runIndex); const baselineRunTotal = !isBaseline && getRowsRunTotal(rows, runIndex + 1); const infoTotal = getMetricRunInfo(METRIC_TYPE_DATA, currentRunTotal, baselineRunTotal); + const fieldPath = `runs[${runIndex}]`; return ( <> - + {(updateSort && sort) ? ( + + + + ) : ( + + )} {!isBaseline && ( <> - + {(updateSort && sort) ? ( + + + + ) : ( + + )} - + {(updateSort && sort) ? ( + + + + ) : ( + + )} )} @@ -95,7 +132,8 @@ const Row = ({ item, renderRowHeader }) => ( ); } - const { displayValue, deltaPercentage, displayDelta, displayDeltaPercentage, deltaType } = run; + const { displayValue, deltaPercentage, displayDelta, displayDeltaPercentage, deltaType } = + run; return ( <> @@ -144,6 +182,8 @@ export const MetricsTable = ({ title, showAllItems, setShowAllItems, + sort, + updateSort, ...restProps }) => { const columnCount = (runs.length - 1) * CURRENT_COLUMN_SPAN + BASELINE_COLUMN_SPAN + 1; @@ -167,11 +207,21 @@ export const MetricsTable = ({ {title || ' '} - {runs.map((run, runIndex) => )} + {runs.map((run, runIndex) => ( + + ))} {showHeaderSum && ( - {runs.map((run, runIndex) => )} + {runs.map((run, runIndex) => ( + + ))} )} @@ -199,23 +249,22 @@ export const MetricsTable = ({ {showAllItems ? ( - - ) : ( - - ) - } + + ) : ( + + )} )} diff --git a/packages/ui/src/components/metrics-table/metrics-table.module.css b/packages/ui/src/components/metrics-table/metrics-table.module.css index 2781b0202c..5261fb416f 100644 --- a/packages/ui/src/components/metrics-table/metrics-table.module.css +++ b/packages/ui/src/components/metrics-table/metrics-table.module.css @@ -15,11 +15,8 @@ width: 100%; } -.tableHeaderRunMetric { - text-transform: none; -} - .root .job { + padding-right: calc(var(--space-xxxsmall) + var(--space-small)); text-align: right; } @@ -30,7 +27,6 @@ } .root .delta { - padding-left: 0; text-align: right; font-size: var(--size-xsmall); vertical-align: baseline; @@ -52,8 +48,9 @@ .showHeaderSum .headerRow .sum { padding-top: var(--space-xxxsmall); - font-size: var(--size-xsmall); + font-size: var(--size-small); vertical-align: middle; + text-transform: none; } /** rows */ diff --git a/packages/ui/src/components/sort-button/index.ts b/packages/ui/src/components/sort-button/index.ts new file mode 100644 index 0000000000..4d25b4ace8 --- /dev/null +++ b/packages/ui/src/components/sort-button/index.ts @@ -0,0 +1 @@ +export * from './sort-button'; diff --git a/packages/ui/src/components/sort-button/sort-button.module.css b/packages/ui/src/components/sort-button/sort-button.module.css new file mode 100644 index 0000000000..5119073d73 --- /dev/null +++ b/packages/ui/src/components/sort-button/sort-button.module.css @@ -0,0 +1,89 @@ +.root { + --icon-dimension: 15px; + display: inline-block; + margin: calc(0px - var(--space-xxxsmall)); + margin-right: calc(0px - 1px - var(--icon-dimension)); + border-radius: var(--radius-small); + position: relative; +} + +.button { + appearance: none; + cursor: pointer; + border: 0; + padding: 0; + margin: 0; + background: transparent; + transition: var(--transition-out); +} + +.toggle { + padding: var(--space-xxxsmall); + padding-right: calc(1px + var(--icon-dimension)); +} + +.direction { + position: absolute; + padding: 2px; + display: block; + overflow: hidden; + right: -2px; /* compensate for the button padding */ + color: var(--color-text-ultra-light); + transition: var(--transition-out); +} + +.directionIcon { + display: block; + width: var(--icon-dimension); + height: var(--icon-dimension); +} + +.directionActive { + color: var(--color-text); + transition: var(--transition-in); +} + +.direction:hover, +.direction:active, +.direction:focus { + color: var(--color-text-dark); + transition: var(--transition-in); +} + +.directionAsc { + padding-bottom: 0; + bottom: 50%; +} + +.directionAsc .directionIcon { + transform: translateY(3px); +} + +.directionDesc { + padding-top: 0; + top: 50%; +} + +.directionDesc .directionIcon { + transform: translateY(-5px); +} + +.root:hover, +.root:active, +.root:focus { + background: var(--color-outline); +} + +.direction { + pointer-events: none; + opacity: 0; +} + +.active .direction, +.root:hover .direction, +.root:active .direction, +.root:focus .direction { + pointer-events: auto; + opacity: 1; + transition: var(--transition-in); +} diff --git a/packages/ui/src/components/sort-button/sort-button.tsx b/packages/ui/src/components/sort-button/sort-button.tsx new file mode 100644 index 0000000000..c1b9ee8d4c --- /dev/null +++ b/packages/ui/src/components/sort-button/sort-button.tsx @@ -0,0 +1,108 @@ +import React, { useCallback } from 'react'; +import cx from 'classnames'; + +import { SORT } from '../../constants'; +import type { SortAction } from '../../types'; +import { Icon } from '../../ui/icon'; +import { Tooltip } from '../../ui/tooltip'; +import css from './sort-button.module.css'; + +interface SortInfo { + direction: string; + title: string; +} + +const getToggleAction = (sort: SortAction, field: string, label: string): SortInfo => { + // Sort the column desc if not currently sorted + if (sort?.field !== field) { + return { direction: SORT.DESC, title: `Order ${label} descending` }; + } + + // Resort asc if the column is already sorted descending + if (sort?.direction === SORT.DESC) { + return { direction: SORT.ASC, title: `Order ${label} ascending` }; + } + + // Reset + return { direction: '', title: 'Reset order' }; +}; + +const getAscAction = (label: string): SortInfo => { + return { direction: SORT.ASC, title: `Order ${label} ascending` }; +}; + +const getDescAction = (label: string): SortInfo => { + return { direction: SORT.DESC, title: `Order ${label} descending` }; +}; + +interface SortButtonProps { + fieldPath: string; + fieldName: string; + label: string; + sort: SortAction; + updateSort: (params: SortAction) => void; +} + +export const SortButton = (props: SortButtonProps & React.ComponentProps<'div'>) => { + const { className = '', children, fieldPath, fieldName, label, sort, updateSort } = props; + + const field = `${fieldPath}.${fieldName}`; + const toggleAction = getToggleAction(sort, field, label); + const ascAction = getAscAction(label); + const descAction = getDescAction(label); + const isSorted = sort.field === field; + + const getOrderOnClick = useCallback( + (action: SortInfo) => () => { + if (action.direction) { + updateSort({ field, direction: action.direction as SortAction['direction'] }); + return; + } + + updateSort({ field: '', direction: '' }); + }, + [field, updateSort, toggleAction], + ); + + return ( +
+ + {children} + + + + + + + +
+ ); +}; diff --git a/packages/ui/src/constants.js b/packages/ui/src/constants.js index b1a6e408fa..b3c48068b0 100644 --- a/packages/ui/src/constants.js +++ b/packages/ui/src/constants.js @@ -35,3 +35,8 @@ export const METRICS_WEBPACK_MODULES = [ 'webpack.duplicateCode', ]; export const METRICS_WEBPACK_PACKAGES = ['webpack.packageCount', 'webpack.duplicatePackagesCount']; + +export const SORT = { + ASC: 'asc', + DESC: 'desc', +}; diff --git a/packages/ui/src/hooks/rows-sort.js b/packages/ui/src/hooks/rows-sort.js deleted file mode 100644 index dece6f7c3d..0000000000 --- a/packages/ui/src/hooks/rows-sort.js +++ /dev/null @@ -1,24 +0,0 @@ -import { useState, useMemo } from 'react'; -import orderBy from 'lodash/orderBy'; - -export const useRowsSort = ({ - rows, - sortFields, - sortBy = 'size', - sortDirection = 'desc', - getCustomSort, -}) => { - const [sort, updateSort] = useState({ field: sortBy, direction: sortDirection }); - - const orderedRows = useMemo( - () => orderBy(rows, getCustomSort(sort.field), sort.direction), - [rows, sort], - ); - - return { - sortFields, - sort, - updateSort, - items: orderedRows, - }; -}; diff --git a/packages/ui/src/hooks/rows-sort.ts b/packages/ui/src/hooks/rows-sort.ts new file mode 100644 index 0000000000..185975f5fd --- /dev/null +++ b/packages/ui/src/hooks/rows-sort.ts @@ -0,0 +1,48 @@ +import { useState, useMemo } from 'react'; +import get from 'lodash/get'; +import orderBy from 'lodash/orderBy'; + +import type { SortAction } from '../types'; +import { SORT } from '../constants'; + +interface UseRowsSortParams { + rows: Array; + fieldPath: string; + direction: SortAction['direction']; + getCustomSort: (item: unknown) => Array; +} + +export const getSortFn = + (fieldPath: string, getCustomSort: UseRowsSortParams['getCustomSort']) => (item: unknown) => { + if (!fieldPath) { + return getCustomSort(item); + } + + return Math.abs(get(item, fieldPath) || 0); + }; + +export const useRowsSort = ({ + rows, + fieldPath = 'runs[0].delta', + direction = 'desc', + getCustomSort, +}: UseRowsSortParams) => { + const [sort, updateSort] = useState({ field: fieldPath, direction }); + + const orderedRows = useMemo( + () => + orderBy( + rows, + getSortFn(sort.field, getCustomSort), + // if direction is empty (reset), sort asc + direction !== '' ? direction : (SORT.ASC as any), + ), + [rows, sort], + ); + + return { + sort, + updateSort, + items: orderedRows, + }; +}; diff --git a/packages/ui/src/types.d.ts b/packages/ui/src/types.d.ts new file mode 100644 index 0000000000..6b82a8cf48 --- /dev/null +++ b/packages/ui/src/types.d.ts @@ -0,0 +1,4 @@ +export interface SortAction { + field: string; + direction: 'asc' | 'desc' | ''; +} diff --git a/packages/ui/src/ui/icon/icon.tsx b/packages/ui/src/ui/icon/icon.tsx index 916d0019b7..c9930dde30 100644 --- a/packages/ui/src/ui/icon/icon.tsx +++ b/packages/ui/src/ui/icon/icon.tsx @@ -7,6 +7,8 @@ const ICONS = { ARROW: 'arrow', ARROW_RIGHT_CIRLCE: 'arrow-right-circle', CANCEL: 'close', + CHEVRON_DOWN: 'chevron-down', + CHEVRON_UP: 'chevron-up', CLOSE: 'close', CLOCK: 'clock', COMMIT: 'commit', diff --git a/packages/ui/src/ui/index.js b/packages/ui/src/ui/index.js index a4913a52c5..94074f287d 100644 --- a/packages/ui/src/ui/index.js +++ b/packages/ui/src/ui/index.js @@ -10,7 +10,6 @@ export { Icon } from './icon'; export { Input } from './input'; export { Loader } from './loader'; export { Skeleton } from './skeleton'; -export { SortDropdown } from './sort-dropdown'; export { Table } from './table'; export { Tabs } from './tabs'; export { Tag } from './tag'; diff --git a/packages/ui/src/ui/sort-dropdown/index.js b/packages/ui/src/ui/sort-dropdown/index.js deleted file mode 100644 index cff8b6ec67..0000000000 --- a/packages/ui/src/ui/sort-dropdown/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './sort-dropdown'; diff --git a/packages/ui/src/ui/sort-dropdown/sort-dropdown.jsx b/packages/ui/src/ui/sort-dropdown/sort-dropdown.jsx deleted file mode 100644 index 8f81b7c76d..0000000000 --- a/packages/ui/src/ui/sort-dropdown/sort-dropdown.jsx +++ /dev/null @@ -1,121 +0,0 @@ -import React, { useMemo } from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; - -import { Dropdown } from '../dropdown'; -import { Icon } from '../icon'; -import css from './sort-dropdown.module.css'; - -const Item = ({ - as: Component, - id, - label, - isActive, - direction, - defaultDirection, - getButtonOnClick, - ...restProps -}) => { - - const buttonProps = useMemo(() => { - let resolveNextDirection = defaultDirection; - - if (isActive) { - resolveNextDirection = direction === 'asc' ? 'desc' : 'asc'; - } - - if (resolveNextDirection === 'desc') { - return { - className: css.itemAsc, - onClick: getButtonOnClick(id, 'desc'), - title: `Order data by ${label} descending`, - }; - } - - return { - onClick: getButtonOnClick(id, 'asc'), - title: `Order data by ${label} ascending`, - }; - }, [isActive, direction, defaultDirection, id, label]); - - return ( - - - {label} - - ); -}; - -Item.propTypes = { - className: PropTypes.string, - as: PropTypes.element.isRequired, - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - isActive: PropTypes.bool.isRequired, - direction: PropTypes.string.isRequired, - defaultDirection: PropTypes.string.isRequired, - getButtonOnClick: PropTypes.func.isRequired, -}; - -Item.defaultProps = { - className: '', -}; - -export const SortDropdown = (props) => { - const { className, label, fields, field, direction, onChange } = props; - const rootClassName = cx(css.root, className); - - const customLabel = fields[field] ? `Ordered by ${fields[field].label}` : label; - - return ( - - {({ MenuItem, menu, menuItemClassName, menuItemActiveClassName }) => { - const getButtonOnClick = (newField, newDirection) => () => { - onChange({ field: newField, direction: newDirection }); - menu.toggle(); - }; - - return ( -
- {Object.entries(fields).map(([key, item]) => ( - - ))} -
- ); - }} -
- ); -}; - -SortDropdown.defaultProps = { - className: '', - label: 'Order by', - onChange: () => {}, - field: '', - direction: 'asc', -}; - -SortDropdown.propTypes = { - className: PropTypes.string, - label: PropTypes.string, - fields: PropTypes.shape({ - [PropTypes.string]: PropTypes.string, - }).isRequired, - onChange: PropTypes.func, - field: PropTypes.string, - direction: PropTypes.string, -}; diff --git a/packages/ui/src/ui/sort-dropdown/sort-dropdown.module.css b/packages/ui/src/ui/sort-dropdown/sort-dropdown.module.css deleted file mode 100644 index fcc8605dad..0000000000 --- a/packages/ui/src/ui/sort-dropdown/sort-dropdown.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.item { - display: flex; - align-items: center; -} - -.itemIcon { - flex: 0 0 auto; - visibility: hidden; - margin-right: var(--space-xxxsmall); -} - -.itemLabel { - flex: 1 1 100%; -} - -.itemAsc .itemIcon { - transform: rotate(180deg); -} - -.itemActive .itemIcon { - visibility: visible; -} diff --git a/packages/ui/src/ui/sort-dropdown/sort-dropdown.stories.jsx b/packages/ui/src/ui/sort-dropdown/sort-dropdown.stories.jsx deleted file mode 100644 index b510f89ea6..0000000000 --- a/packages/ui/src/ui/sort-dropdown/sort-dropdown.stories.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -import { getWrapperDecorator } from '../../stories'; -import { SortDropdown } from '.'; - -export default { - title: 'UI/SortDropdown', - component: SortDropdown, - decorators: [getWrapperDecorator({ paddingLeft: '200px' })], -}; - -export const Default = () => ( - { - console.log(state); // eslint-disable-line no-console - }} - fields={{ - filename: { - label: 'Filename', - }, - size: { - label: 'Size', - }, - }} - field="size" - /> -);