From f69f396843708a5c00dea7059a3f45ac5f1985c9 Mon Sep 17 00:00:00 2001 From: Byron Wall <87667330+ByronDWall@users.noreply.github.com> Date: Mon, 30 Oct 2023 08:43:51 -0400 Subject: [PATCH] Pangolin 3072: Header cell updates (#2629) * fix(rebase): update design tokens and header-cell.styles.tsx to resolve merge conflicts * fix(rebase): update header-cell.styles.tsx to resolve merge conflict * feat(header cell updates): remove unused font-size-for-table-header decision token * feat(header cell updates): remove unused value from sortableHeaderProps in header-cell.tsx * feat(header cell): fix merge conflicts in rebase * fix(design tokens): remove unused avatar design tokens left over from rebase --- .changeset/rich-avocados-live.md | 6 + design-system/materials/custom-properties.css | 8 +- .../materials/custom-properties.json | 8 +- .../materials/internals/definition.yaml | 8 +- design-system/src/design-tokens.ts | 17 +-- .../data-table/src/header-cell.styles.tsx | 106 +++++++++++++++--- .../components/data-table/src/header-cell.tsx | 12 +- 7 files changed, 126 insertions(+), 39 deletions(-) create mode 100644 .changeset/rich-avocados-live.md diff --git a/.changeset/rich-avocados-live.md b/.changeset/rich-avocados-live.md new file mode 100644 index 0000000000..40544b27e9 --- /dev/null +++ b/.changeset/rich-avocados-live.md @@ -0,0 +1,6 @@ +--- +'@commercetools-uikit/data-table': minor +'@commercetools-uikit/design-system': minor +--- + +Update data table header styles, clamp header cell label to not extend past 2 lines in height, update and animate header cell sorting icons diff --git a/design-system/materials/custom-properties.css b/design-system/materials/custom-properties.css index 1cf2e85d94..454fadefe0 100644 --- a/design-system/materials/custom-properties.css +++ b/design-system/materials/custom-properties.css @@ -211,7 +211,7 @@ 95% ); --background-color-for-table-cell-when-hovered: hsl(232, 18%, 98%); - --background-color-for-table-header: hsl(232, 18%, 95%); + --background-color-for-table-header: hsl(232, 18%, 98%); --background-color-for-tag: hsl(232, 18%, 95%); --background-color-for-tag-warning: hsl( 25.110132158590307, @@ -334,7 +334,7 @@ --border-color-for-button-as-icon-as-primary: hsl(232, 18%, 80%); --border-color-for-button-as-icon-when-disabled: #fff; --border-color-for-table-header: #fff; - --border-color-for-table-header-as-bottom: hsl(232, 18%, 90%); + --border-color-for-table-header-as-bottom: hsl(232, 18%, 95%); --border-color-for-table-manager-droppable-list: hsl(232, 18%, 80%); --border-color-for-collapsible-panel-header: hsl(232, 18%, 90%); --border-color-for-stamp-when-error: hsl(339.1304347826087, 100%, 85%); @@ -440,7 +440,7 @@ --font-color-for-link-as-secondary: #1a1a1a; --font-color-for-link-as-primary-when-active: hsl(175, 55%, 45%); --font-color-for-link-as-secondary-when-active: hsl(175, 55%, 45%); - --font-color-for-table-header: #1a1a1a; + --font-color-for-table-header: hsl(232, 18%, 40%); --font-color-for-localized-input-label: hsl(232, 18%, 60%); --font-color-for-view-switcher: hsl(232, 18%, 40%); --font-color-for-view-switcher-when-disabled: hsl(232, 18%, 60%); @@ -539,7 +539,7 @@ --font-weight-for-text-as-caption: 400; --font-weight-for-text-as-detail: 400; --font-weight-for-button: 500; - --font-weight-for-table-header: 600; + --font-weight-for-table-header: 500; --font-weight-for-text-as-bold: 600; --margin-for-table-header: 8px; --margin-for-table-cell-as-condensed: 8px; diff --git a/design-system/materials/custom-properties.json b/design-system/materials/custom-properties.json index a250b77295..0fc1a48866 100644 --- a/design-system/materials/custom-properties.json +++ b/design-system/materials/custom-properties.json @@ -191,7 +191,7 @@ "--background-color-for-input-when-readonly": "hsl(232, 18%, 95%)", "--background-color-for-input-when-active": "hsl(203.05555555555554, 93.9130434783%, 95%)", "--background-color-for-table-cell-when-hovered": "hsl(232, 18%, 98%)", - "--background-color-for-table-header": "hsl(232, 18%, 95%)", + "--background-color-for-table-header": "hsl(232, 18%, 98%)", "--background-color-for-tag": "hsl(232, 18%, 95%)", "--background-color-for-tag-warning": "hsl(25.110132158590307, 89.0196078431%, 95%)", "--background-color-for-tag-when-hovered": "hsl(232, 18%, 90%)", @@ -258,7 +258,7 @@ "--border-color-for-button-as-icon-as-primary": "hsl(232, 18%, 80%)", "--border-color-for-button-as-icon-when-disabled": "#fff", "--border-color-for-table-header": "#fff", - "--border-color-for-table-header-as-bottom": "hsl(232, 18%, 90%)", + "--border-color-for-table-header-as-bottom": "hsl(232, 18%, 95%)", "--border-color-for-table-manager-droppable-list": "hsl(232, 18%, 80%)", "--border-color-for-collapsible-panel-header": "hsl(232, 18%, 90%)", "--border-color-for-stamp-when-error": "hsl(339.1304347826087, 100%, 85%)", @@ -338,7 +338,7 @@ "--font-color-for-link-as-secondary": "#1a1a1a", "--font-color-for-link-as-primary-when-active": "hsl(175, 55%, 45%)", "--font-color-for-link-as-secondary-when-active": "hsl(175, 55%, 45%)", - "--font-color-for-table-header": "#1a1a1a", + "--font-color-for-table-header": "hsl(232, 18%, 40%)", "--font-color-for-localized-input-label": "hsl(232, 18%, 60%)", "--font-color-for-view-switcher": "hsl(232, 18%, 40%)", "--font-color-for-view-switcher-when-disabled": "hsl(232, 18%, 60%)", @@ -429,7 +429,7 @@ "--font-weight-for-text-as-caption": "400", "--font-weight-for-text-as-detail": "400", "--font-weight-for-button": "500", - "--font-weight-for-table-header": "600", + "--font-weight-for-table-header": "500", "--font-weight-for-text-as-bold": "600", "--margin-for-table-header": "8px", "--margin-for-table-cell-as-condensed": "8px", diff --git a/design-system/materials/internals/definition.yaml b/design-system/materials/internals/definition.yaml index ad32c41d5a..3cd6f4511c 100644 --- a/design-system/materials/internals/definition.yaml +++ b/design-system/materials/internals/definition.yaml @@ -552,7 +552,7 @@ decisionGroupsByTheme: background-color-for-table-cell-when-hovered: choice: color-neutral-98 background-color-for-table-header: - choice: color-neutral-95 + choice: color-neutral-98 background-color-for-tag: choice: color-neutral-95 background-color-for-tag-warning: @@ -696,7 +696,7 @@ decisionGroupsByTheme: border-color-for-table-header: choice: color-surface border-color-for-table-header-as-bottom: - choice: color-neutral-90 + choice: color-neutral-95 border-color-for-table-manager-droppable-list: choice: color-neutral border-color-for-collapsible-panel-header: @@ -876,7 +876,7 @@ decisionGroupsByTheme: font-color-for-link-as-secondary-when-active: choice: color-primary font-color-for-table-header: - choice: color-solid + choice: color-neutral-40 font-color-for-localized-input-label: choice: color-neutral-60 font-color-for-view-switcher: @@ -1099,7 +1099,7 @@ decisionGroupsByTheme: font-weight-for-button: choice: font-weight-500 font-weight-for-table-header: - choice: font-weight-600 + choice: font-weight-500 font-weight-for-text-as-bold: choice: font-weight-600 diff --git a/design-system/src/design-tokens.ts b/design-system/src/design-tokens.ts index ba24915fb2..93a13dbf3e 100644 --- a/design-system/src/design-tokens.ts +++ b/design-system/src/design-tokens.ts @@ -203,7 +203,7 @@ export const themes = { backgroundColorForInputWhenActive: 'hsl(203.05555555555554, 93.9130434783%, 95%)', backgroundColorForTableCellWhenHovered: 'hsl(232, 18%, 98%)', - backgroundColorForTableHeader: 'hsl(232, 18%, 95%)', + backgroundColorForTableHeader: 'hsl(232, 18%, 98%)', backgroundColorForTag: 'hsl(232, 18%, 95%)', backgroundColorForTagWarning: 'hsl(25.110132158590307, 89.0196078431%, 95%)', @@ -280,7 +280,7 @@ export const themes = { borderColorForButtonAsIconAsPrimary: 'hsl(232, 18%, 80%)', borderColorForButtonAsIconWhenDisabled: '#fff', borderColorForTableHeader: '#fff', - borderColorForTableHeaderAsBottom: 'hsl(232, 18%, 90%)', + borderColorForTableHeaderAsBottom: 'hsl(232, 18%, 95%)', borderColorForTableManagerDroppableList: 'hsl(232, 18%, 80%)', borderColorForCollapsiblePanelHeader: 'hsl(232, 18%, 90%)', borderColorForStampWhenError: 'hsl(339.1304347826087, 100%, 85%)', @@ -368,7 +368,7 @@ export const themes = { fontColorForLinkAsSecondary: '#1a1a1a', fontColorForLinkAsPrimaryWhenActive: 'hsl(175, 55%, 45%)', fontColorForLinkAsSecondaryWhenActive: 'hsl(175, 55%, 45%)', - fontColorForTableHeader: '#1a1a1a', + fontColorForTableHeader: 'hsl(232, 18%, 40%)', fontColorForLocalizedInputLabel: 'hsl(232, 18%, 60%)', fontColorForViewSwitcher: 'hsl(232, 18%, 40%)', fontColorForViewSwitcherWhenDisabled: 'hsl(232, 18%, 60%)', @@ -460,7 +460,7 @@ export const themes = { fontWeightForTextAsCaption: '400', fontWeightForTextAsDetail: '400', fontWeightForButton: '500', - fontWeightForTableHeader: '600', + fontWeightForTableHeader: '500', fontWeightForTextAsBold: '600', marginForTableHeader: '8px', marginForTableCellAsCondensed: '8px', @@ -796,7 +796,7 @@ const designTokens = { backgroundColorForTableCellWhenHovered: 'var(--background-color-for-table-cell-when-hovered, hsl(232, 18%, 98%))', backgroundColorForTableHeader: - 'var(--background-color-for-table-header, hsl(232, 18%, 95%))', + 'var(--background-color-for-table-header, hsl(232, 18%, 98%))', backgroundColorForTag: 'var(--background-color-for-tag, hsl(232, 18%, 95%))', backgroundColorForTagWarning: 'var(--background-color-for-tag-warning, hsl(25.110132158590307, 89.0196078431%, 95%))', @@ -924,7 +924,7 @@ const designTokens = { 'var(--border-color-for-button-as-icon-when-disabled, #fff)', borderColorForTableHeader: 'var(--border-color-for-table-header, #fff)', borderColorForTableHeaderAsBottom: - 'var(--border-color-for-table-header-as-bottom, hsl(232, 18%, 90%))', + 'var(--border-color-for-table-header-as-bottom, hsl(232, 18%, 95%))', borderColorForTableManagerDroppableList: 'var(--border-color-for-table-manager-droppable-list, hsl(232, 18%, 80%))', borderColorForCollapsiblePanelHeader: @@ -1067,7 +1067,8 @@ const designTokens = { 'var(--font-color-for-link-as-primary-when-active, hsl(175, 55%, 45%))', fontColorForLinkAsSecondaryWhenActive: 'var(--font-color-for-link-as-secondary-when-active, hsl(175, 55%, 45%))', - fontColorForTableHeader: 'var(--font-color-for-table-header, #1a1a1a)', + fontColorForTableHeader: + 'var(--font-color-for-table-header, hsl(232, 18%, 40%))', fontColorForLocalizedInputLabel: 'var(--font-color-for-localized-input-label, hsl(232, 18%, 60%))', fontColorForViewSwitcher: @@ -1208,7 +1209,7 @@ const designTokens = { fontWeightForTextAsCaption: 'var(--font-weight-for-text-as-caption, 400)', fontWeightForTextAsDetail: 'var(--font-weight-for-text-as-detail, 400)', fontWeightForButton: 'var(--font-weight-for-button, 500)', - fontWeightForTableHeader: 'var(--font-weight-for-table-header, 600)', + fontWeightForTableHeader: 'var(--font-weight-for-table-header, 500)', fontWeightForTextAsBold: 'var(--font-weight-for-text-as-bold, 600)', marginForTableHeader: 'var(--margin-for-table-header, 8px)', marginForTableCellAsCondensed: diff --git a/packages/components/data-table/src/header-cell.styles.tsx b/packages/components/data-table/src/header-cell.styles.tsx index 920e93e23a..931faf2d94 100644 --- a/packages/components/data-table/src/header-cell.styles.tsx +++ b/packages/components/data-table/src/header-cell.styles.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/react'; +import { css, keyframes } from '@emotion/react'; import styled from '@emotion/styled'; import { getCellInnerStyles } from './cell.styles'; import { designTokens } from '@commercetools-uikit/design-system'; @@ -6,28 +6,69 @@ import type { THeaderCell } from './header-cell'; const getButtonStyle = () => css` cursor: pointer; - /* remove user-agent button styles */ border: none; background: none; text-decoration: none; color: inherit; font: inherit; - font-size: ${designTokens.fontSizeForTable}; + font-size: ${designTokens.fontSize10}; font-family: inherit; `; +/* When a sortable header is deselected, + * (i.e. when another sortable header is selected) + * the AngleUpDown is animated with fadeIn + * + * When the user hovers over a sortable header that + * is not the active sorted column, the icon for + * the current sort direction is animated with fadeIn + */ +const fadeIn = keyframes` + from { + opacity: 0; + } + to { + opacity: 1; + } +`; + +/* When a sortable header is activated/selected, + * and the sort direction is 'desc', the ArrowDown + * icon is animated with rotateClockwise + */ +const rotateClockwise = keyframes` + from { + transform: rotate(-180deg); + } to { + transform: rotate(0deg); + } +`; + +/* When a sortable header is activated/selected, + * and the sort direction is 'asc', the ArrowUp + * icon is animated with rotateCounterClockwise + */ +const rotateCounterClockwise = keyframes` + from { + transform: rotate(180deg); + } to { + transform: rotate(0deg); + } +`; + /* A sortable header has the two arrow svg icons * GIVEN column is sortable and is not focused * THEN AngleUpDown icon is shown (default behaviour) - * AND AngleUp or AngleDown icon is not shown + * AND ArrowUp or ArrowDown icon is not shown * - * GIVEN column is sortable and foucsed + * GIVEN column is sortable and foucsed or hovered * THEN AngleUpDown icon is hidden - * AND AngleUp or AngleDown icon is shown + * AND ArrowUp or ArrowDown icon is shown */ type TGetSortableHeaderStyles = { isActive?: boolean; + label?: 'asc' | 'desc'; }; const getSortableHeaderStyles = (props: TGetSortableHeaderStyles) => css` @@ -37,18 +78,25 @@ const getSortableHeaderStyles = (props: TGetSortableHeaderStyles) => css` svg[data-icon-state='inactive'], svg[data-icon-state='active'] { - margin-left: ${designTokens.spacing20}; + margin-left: ${designTokens.spacing10}; flex-shrink: 0; } svg[data-icon-state='inactive'] { display: ${props.isActive ? 'none' : 'inline-block'}; + animation: ${fadeIn} 150ms ease-in-out; } svg[data-icon-state='active'] { display: ${props.isActive ? 'inline-block' : 'none'}; + animation: ${props.isActive && + css` + ${props.label === 'asc' + ? rotateCounterClockwise + : rotateClockwise} 150ms ease-in-out + `}; } /* for cases where svgs have a predefined fill */ > svg * { - fill: ${designTokens.fontColorForTableHeader} !important; + fill: ${designTokens.colorNeutral60} !important; } :hover, @@ -58,6 +106,10 @@ const getSortableHeaderStyles = (props: TGetSortableHeaderStyles) => css` } svg[data-icon-state='active'] { display: inline-block; + animation: ${!props.isActive && + css` + ${fadeIn} 150ms ease-in-out + `}; } } `; @@ -71,7 +123,7 @@ type THeaderCellInner = Pick< const HeaderCellInner = styled.div` box-sizing: border-box; display: flex; - justify-content: space-between; + justify-content: flex-start; padding: 0 ${(props) => props.isCondensed @@ -89,8 +141,8 @@ type TBaseHeaderCell = { shouldClipContent?: boolean; }; const BaseHeaderCell = styled.th` - color: ${designTokens.fontColorForTableHeader}; - background-color: ${designTokens.backgroundColorForTableHeader}; + color: ${designTokens.colorNeutral40}; + background-color: ${designTokens.colorNeutral98}; position: ${(props) => props.disableHeaderStickiness ? 'relative' : 'sticky'}; @@ -100,8 +152,22 @@ const BaseHeaderCell = styled.th` /* remove user-agent styles */ padding: 0; - font-weight: ${designTokens.fontWeightForTableHeader}; - font-size: ${designTokens.fontSizeForTable}; + font-weight: ${designTokens.fontWeight500}; + font-size: ${designTokens.fontSize10}; + + /** + * bottom border that doesn't overshadow the resize indicator + */ + :after { + content: ''; + position: absolute; + z-index: -1; + width: 100%; + height: ${designTokens.borderWidth1}; + bottom: 0; + left: 0; + background-color: ${designTokens.colorNeutral95}; + } /* this ensures that, when dragging this header's column resizer it remains above the rest of the headers, preventing accidental hovers/flickering */ @@ -116,11 +182,22 @@ const BaseHeaderCell = styled.th` `; const HeaderLabelWrapper = styled.div` + display: inline-flex; /* ensure height stays the same even if label is empty 1.4em = default line-height */ min-height: 1.4em; margin: ${designTokens.marginForTableHeader} 0; - flex: 1; + flex: 0 0 fit-content; +`; + +const HeaderLabelTextWrapper = styled.span` + /* ensure that the header text truncates on the second line + https://css-tricks.com/line-clampin/#aa-the-standardized-way */ + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + overflow-wrap: anywhere; `; const HeaderIconWrapper = styled.div` @@ -135,5 +212,6 @@ export { HeaderCellInner, BaseHeaderCell, HeaderLabelWrapper, + HeaderLabelTextWrapper, HeaderIconWrapper, }; diff --git a/packages/components/data-table/src/header-cell.tsx b/packages/components/data-table/src/header-cell.tsx index 2ba791f561..f03131dba5 100644 --- a/packages/components/data-table/src/header-cell.tsx +++ b/packages/components/data-table/src/header-cell.tsx @@ -6,14 +6,15 @@ import { type RefObject, } from 'react'; import { - AngleUpIcon, - AngleDownIcon, AngleUpDownIcon, + ArrowDownIcon, + ArrowUpIcon, } from '@commercetools-uikit/icons'; import { BaseHeaderCell, HeaderCellInner, HeaderIconWrapper, + HeaderLabelTextWrapper, HeaderLabelWrapper, } from './header-cell.styles'; import Resizer from './column-resizer'; @@ -129,13 +130,13 @@ const defaultProps: Pick< const HeaderCell = (props: THeaderCell) => { let sortableHeaderProps = {}; - let SortingIcon!: typeof AngleDownIcon; + let SortingIcon!: typeof ArrowDownIcon; if (props.isSortable) { const isActive = props.sortedBy === props.columnKey; const nextSortDirection = !isActive || props.sortDirection === 'desc' ? 'asc' : 'desc'; - SortingIcon = props.sortDirection === 'desc' ? AngleDownIcon : AngleUpIcon; + SortingIcon = props.sortDirection === 'desc' ? ArrowDownIcon : ArrowUpIcon; sortableHeaderProps = { as: 'button', @@ -167,7 +168,8 @@ const HeaderCell = (props: THeaderCell) => { {...sortableHeaderProps} > - {props.children} + {props.children} + {props.iconComponent && ( {props.iconComponent} )}