From 6fa34995ce34f30bc4d4e167e8ea2f7f49348d4e Mon Sep 17 00:00:00 2001 From: ponnexcodev Date: Sat, 19 Oct 2024 15:17:45 +0800 Subject: [PATCH] Table Revamp --- package-lock.json | 10 +- package.json | 2 +- packages/design-system/package.json | 2 +- .../toniq-list-table.element.book.ts | 19 +- .../toniq-list-table.element.test.ts | 6 +- .../toniq-list-table.element.ts | 536 +++++++----------- packages/native-elements-test/package.json | 2 +- packages/scripts/package.json | 2 +- 8 files changed, 214 insertions(+), 365 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ba6c7f7..96f628ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@toniq-labs/design-system-root", - "version": "16.22.10", + "version": "16.23.7-17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@toniq-labs/design-system-root", - "version": "16.22.10", + "version": "16.23.7-17", "hasInstallScript": true, "license": "MIT", "workspaces": [ @@ -13519,7 +13519,7 @@ }, "packages/design-system": { "name": "@toniq-labs/design-system", - "version": "16.22.10", + "version": "16.23.7-17", "license": "MIT", "dependencies": { "@augment-vir/common": "^23.3.4", @@ -13588,7 +13588,7 @@ }, "packages/native-elements-test": { "name": "@toniq-labs/design-system-native-elements-test", - "version": "16.22.10", + "version": "16.23.7-17", "dependencies": { "@toniq-labs/design-system": "*", "element-vir": "*", @@ -13610,7 +13610,7 @@ }, "packages/scripts": { "name": "@toniq-labs/design-system-scripts", - "version": "16.22.10", + "version": "16.23.7-17", "dependencies": { "@augment-vir/common": "^23.3.4", "@augment-vir/node-js": "^23.3.4", diff --git a/package.json b/package.json index 464ec68a..7e050ca1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@toniq-labs/design-system-root", - "version": "16.22.10", + "version": "16.23.7-17", "private": true, "description": "Root design system mono-repo package.", "homepage": "https://github.com/Toniq-Labs/toniq-labs-design-system", diff --git a/packages/design-system/package.json b/packages/design-system/package.json index dab80948..a99066ff 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "@toniq-labs/design-system", - "version": "16.22.10", + "version": "16.23.7-17", "private": false, "description": "Design system elements for Toniq Labs", "keywords": [ diff --git a/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.book.ts b/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.book.ts index 8dd7c8d2..d9a1dc5e 100644 --- a/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.book.ts +++ b/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.book.ts @@ -3,7 +3,7 @@ import {defineBookPage} from 'element-book'; import {CSSResult, HTMLTemplateResult, css, html, listen, unsafeCSS} from 'element-vir'; import {noNativeFormStyles} from 'vira'; import {elementsBookPage} from '../../element-book/book-pages/elements.book'; -import {CryptoBtc24Icon, Target24Icon} from '../../icons'; +import {CryptoBtc24Icon} from '../../icons'; import {toniqFontStyles} from '../../styles'; import {toniqColors} from '../../styles/colors'; import {defineToniqElementNoInputs} from '../define-toniq-element'; @@ -1010,22 +1010,6 @@ const examples: ReadonlyArray< }, }, }, - { - title: 'with pagination and custom action', - inputs: { - pagination: { - currentPage: 1, - pageCount: 10, - }, - paginationAction: html` - <${ToniqIcon.assign({icon: Target24Icon})} - ${listen('click', () => { - alert('pagination action'); - })} - > - `, - }, - }, { title: 'squished horizontally', styles: css` @@ -1265,7 +1249,6 @@ export const toniqListTableElementBookPage = defineBookPage({ currentPage: 1, pageCount: 5, }, - nonBlocking: true, })}> `; }, diff --git a/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.test.ts b/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.test.ts index a18739d1..4ddf2462 100644 --- a/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.test.ts +++ b/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.test.ts @@ -44,7 +44,9 @@ describe(ToniqListTable.tagName, () => { <${ToniqListTable.assign(testCase.inputs)}> `); assertInstanceOf(renderedToniqListTable, ToniqListTable); - const renderedRows = renderedToniqListTable.shadowRoot.querySelectorAll('.row-wrapper'); + const renderedRows = renderedToniqListTable.shadowRoot + .querySelector('.column-wrapper') + ?.querySelectorAll('.column-content'); if (testCase.inputs.pagination) { assertInstanceOf(renderedToniqListTable, ToniqListTable); @@ -56,7 +58,7 @@ describe(ToniqListTable.tagName, () => { return { // Minus the header row - row: renderedRows.length - 1, + row: renderedRows ? renderedRows.length - 1 : 0, }; } diff --git a/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.ts b/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.ts index c0669136..77249250 100644 --- a/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.ts +++ b/packages/design-system/src/elements/toniq-list-table/toniq-list-table.element.ts @@ -13,16 +13,15 @@ import { classMap, css, defineElementEvent, + guard, html, ifDefined, - keyed, listen, nothing, - onResize, + onDomCreated, perInstance, renderIf, repeat, - unsafeCSS, } from 'element-vir'; import {ChevronsRight16Icon} from '../../icons/svgs/core-16/chevrons-right-16.icon'; import {toniqColors, toniqDurations, toniqFontStyles} from '../../styles'; @@ -31,18 +30,18 @@ import {ToniqIcon} from '../toniq-icon/toniq-icon.element'; import {ToniqLoading, ToniqLoadingSizeEnum} from '../toniq-loading/toniq-loading.element'; import {ToniqPagination} from '../toniq-pagination/toniq-pagination.element'; -export type ColumnsBase = ReadonlyArray< - Readonly<{ - key: PropertyKey; - title: string; - disabled?: boolean; - option?: { - sticky?: boolean | undefined; - spaceEvenly?: boolean | undefined; - }; - style?: CSSResult; - }> ->; +export type ListTableColumn = Readonly<{ + key: PropertyKey; + title: string; + disabled?: boolean; + option?: { + sticky?: boolean | undefined; + spaceEvenly?: boolean | undefined; + }; + style?: CSSResult; +}>; + +export type ColumnsBase = ReadonlyArray; export type ListTableRow = { cells: Readonly< @@ -77,11 +76,6 @@ export type ListTableInputs = { }> | undefined; showLoading?: boolean | undefined; - /** - * Used to show the table even if all items have not been painted yet, ideally to be used only - * in fixed/consistent column sizes in all rows - */ - nonBlocking?: boolean | undefined; paginationAction?: HTMLTemplateResult | undefined; }; @@ -144,14 +138,14 @@ export const ToniqListTable = defineToniqElement()({ /* Firefox */ :host { - scrollbar-width: auto; + scrollbar-width: thin; scrollbar-color: ${scrollbarColorCssVar} ${scrollbarTrackColorCssVar}; } /* Chrome, Edge, and Safari */ :host::-webkit-scrollbar { - width: 2px; - height: 2px; + width: 8px; + height: 8px; } :host::-webkit-scrollbar-track { @@ -166,16 +160,12 @@ export const ToniqListTable = defineToniqElement()({ .table-wrapper { position: relative; - overflow-y: hidden; overflow-x: auto; - display: flex; flex-grow: 1; - flex-direction: column; align-items: center; gap: 32px; - background-color: ${toniqColors.pageInteraction.backgroundColor}; border-radius: ${cssVars['toniq-list-table-header-radius'].value}; border: 24px solid ${toniqColors.pageInteraction.backgroundColor}; @@ -191,10 +181,10 @@ export const ToniqListTable = defineToniqElement()({ min-height: 40px; width: 100%; display: flex; - flex-direction: column; - align-self: flex-start; + flex-direction: row; overflow-x: auto; - scrollbar-width: auto; + overflow-y: hidden; + scrollbar-width: thin; scrollbar-color: ${scrollbarColorCssVar} ${scrollbarTrackColorCssVar}; } @@ -230,111 +220,69 @@ export const ToniqListTable = defineToniqElement()({ display: none; } - .row-wrapper { + .column-wrapper { display: flex; + flex-direction: column; + align-items: flex-start; position: relative; background: ${toniqColors.pageInteraction.backgroundColor}; cursor: pointer; } - .row-wrapper:not(:first-of-type) { - min-height: 48px; - } - - .row-wrapper:first-of-type, - .row-wrapper:first-of-type .row-item { - min-height: 32px; - align-items: start; - } - - .row-wrapper:not(:first-of-type):hover:before { - content: ''; - position: absolute; - top: 0; - height: 2px; - width: 100%; - background-color: ${toniqColors.dropShadow.backgroundColor}; - } - - .row-wrapper:not(:first-of-type):hover:after { - content: ''; - position: absolute; - bottom: 0; - height: 2px; - width: 100%; - background-color: ${toniqColors.dropShadow.backgroundColor}; - } - - .row-wrapper:not(:first-of-type):hover .row-item, - .row-wrapper:not(:first-of-type):hover + .row-wrapper .row-item { - border-top-color: transparent; + .column-wrapper.sticky { + position: sticky; + z-index: 2; + filter: drop-shadow(rgba(0, 0, 0, 0.12) 4px 1px 3px); + will-change: filter; } - .row-wrapper:not(:first-of-type) .row-item { - border-top-color: ${toniqColors.dividerFaint.foregroundColor}; + .column-wrapper.fill { + flex: 1; } - .row-item, - .row-content { - display: flex; + .column-content { + height: 48px; + max-height: 48px; + width: -webkit-fill-available; + width: -moz-available; align-items: center; - background: ${toniqColors.pageInteraction.backgroundColor}; + display: flex; + position: relative; } - .row-item.lazy { - display: none; + .column-content * { + text-wrap: nowrap; } - .row-content.hidden { - visibility: hidden; + .column-wrapper:not(:first-of-type) .column-content { + padding-left: calc(${cssVars['toniq-list-table-row-gap'].value} / 2); } - .row-content, - .row-content * { - text-wrap: nowrap; + .column-wrapper:not(:last-of-type) .column-content { + padding-right: calc(${cssVars['toniq-list-table-row-gap'].value} / 2); } - .row-item { + .column-wrapper .column-content:not(:first-of-type) { border: ${cssVars['toniq-list-table-border-width'].value} solid transparent; + border-top-color: ${toniqColors.dividerFaint.foregroundColor}; } - .row-item.sticky { - position: sticky; - filter: drop-shadow(rgba(0, 0, 0, 0.12) 4px 1px 3px); - will-change: filter; - z-index: 2; - } - - .row-wrapper:last-child .row-item { + .column-wrapper .column-content:last-of-type { border-bottom-color: ${toniqColors.dividerFaint.foregroundColor}; } - .row-item:not(:first-child) .row-content { - padding-left: calc(${cssVars['toniq-list-table-row-gap'].value} / 2); - } - - .row-item:not(:last-child) .row-content { - padding-right: calc(${cssVars['toniq-list-table-row-gap'].value} / 2); - } - - .row-item:last-of-type, - .row-item:last-of-type .row-content { - flex-grow: 1; + .column-wrapper .column-content:not(:first-of-type):hover { + border-top-color: ${toniqColors.dropShadow.backgroundColor}; } - .row-item.fill { - flex: 1; - } - - .row-item.fill .row-content { - flex: 1; + .column-wrapper .column-content:not(:first-of-type):hover + .column-content { + border-top-color: ${toniqColors.dropShadow.backgroundColor}; } - .loading-wrapper.hidden { - visibility: hidden; - pointer-events: none; - opacity: 0; - transition: ${toniqDurations.interaction}; + .content-wrapper { + width: 100%; + display: flex; + align-items: center; } .loading-wrapper { @@ -359,52 +307,126 @@ export const ToniqListTable = defineToniqElement()({ } `, stateInitStatic: { - canScroll: false, debouncedResize: perInstance(() => createDebounce(DebounceStyle.FirstThenWait, {milliseconds: 30}), ), - rowStyles: {} as { - [key: string]: { - width: number | undefined; - left: number | undefined; - }; - }, - isLoading: false, + canScroll: false, tableListLeft: 0, }, - initCallback({inputs, state, updateState}) { - const enabledColumns = inputs.columns.filter((column) => !column.disabled); - updateState({ - rowStyles: enabledColumns.reduce((accum, item) => { - accum[item.key as string] = { - width: undefined, - left: undefined, - }; - return accum; - }, state.rowStyles), - }); - }, renderCallback({inputs, state, updateState, events, dispatch, host}) { const enabledColumns = inputs.columns.filter((column) => !column.disabled); // Duplicate first entry for the header column - const rows = [ + const rows: ReadonlyArray>> = [ inputs.rows[0], ...inputs.rows, ].filter(isTruthy); - function tableUpdate(container: HTMLElement | EventTarget | null) { + function tableUpdate() { + const container = host.shadowRoot.querySelector('.table-list') as HTMLElement; if (container instanceof HTMLElement) { - setTimeout(() => { - state.debouncedResize(() => { - updateState({ - canScroll: container.scrollWidth > container.clientWidth, - tableListLeft: container.getBoundingClientRect().left, - }); + state.debouncedResize(() => { + requestAnimationFrame(() => { + const newCanScroll = container.scrollWidth > container.clientWidth; + const newTableListLeft = container.getBoundingClientRect().left; + + if ( + newCanScroll !== state.canScroll || + newTableListLeft !== state.tableListLeft + ) { + updateState({ + canScroll: newCanScroll, + tableListLeft: newTableListLeft, + }); + } }); - }, 0); + }); } } + function listItem(columnItem: ListTableColumn) { + function calculateLeft(columnKey: string) { + const wrapperEl = host.shadowRoot.querySelector( + `.column-wrapper[data-column="${columnKey}"]`, + ); + + if (!(wrapperEl instanceof HTMLElement)) { + return css` + left: 0px; + `; + } + + const wrapperLeft = wrapperEl.getBoundingClientRect().left; + const left = state.tableListLeft ? wrapperLeft - state.tableListLeft : wrapperLeft; + return css` + left: ${left > 0 ? left : 0}px; + `; + } + + const isSticky = !!columnItem.option?.sticky && state.canScroll; + + return html` +
+ ${repeat( + rows, + (row, rowIndex) => rowIndex, + (row, rowIndex) => { + const rowItem = { + contents: row.cells[columnItem.key as keyof typeof row], + rowActions: row.rowActions, + } as { + contents: HtmlInterpolation; + rowActions: typeof row.rowActions; + }; + + return html` +
0 + ? listen('click', (clickEvent) => { + rowItem?.rowActions?.click?.({ + clickEvent, + dispatch, + }); + }) + : nothing} + > + ${renderIf( + rowIndex === 0, + html` + + ${columnItem.title} + + `, + html` +
{ + event.preventDefault(); + event.stopPropagation(); + })} + > + ${guard([rowIndex], () => rowItem.contents)} +
+ `, + )} +
+ `; + }, + )} +
+ `; + } + const paginationTemplate = inputs.pagination && inputs.pagination?.pageCount > 1 ? html` @@ -423,81 +445,8 @@ export const ToniqListTable = defineToniqElement()({ ` : nothing; - function listItem(row: ListTableRow, rowIndex: number) { - return html` -
0 - ? listen('click', (clickEvent) => { - row.rowActions?.click?.({clickEvent, dispatch}); - }) - : nothing} - > - ${repeat( - enabledColumns, - (item, index) => index, - (item, index) => { - const itemKey = item.key as keyof typeof row; - const contents = row.cells[itemKey]; - - const rowItemLeftStyle = css` - left: ${unsafeCSS(`${state.rowStyles[itemKey]?.left}px`)}; - `; - - const rowItemMinWidthStyle = css` - min-width: ${index >= enabledColumns.length - 1 - ? unsafeCSS('unset') - : unsafeCSS(`${state.rowStyles[itemKey]?.width}px`)}; - `; + const isLoading = !!inputs.showLoading || !!!rows.length || !!!enabledColumns.length; - const rowItemMaxWidthStyle = css` - max-width: ${index >= enabledColumns.length - 1 - ? unsafeCSS('unset') - : unsafeCSS(`${state.rowStyles[itemKey]?.width}px`)}; - `; - - return html` -
-
- ${renderIf( - rowIndex === 0, - html` - - ${item.title} - - `, - html` - ${contents} - `, - )} -
-
- `; - }, - )} -
- `; - } - - const isLoading = (inputs.nonBlocking ? false : state.isLoading) || !!inputs.showLoading; return html`
()({ 'can-scroll': state.canScroll, })} > - ${keyed( - inputs.pagination?.currentPage, - html` -
{ - tableUpdate(event.target); - - updateState({ - rowStyles: enabledColumns.reduce((accum, item) => { - accum[item.key as string] = { - width: undefined, - left: undefined, - }; - return accum; - }, state.rowStyles), - }); - - setTimeout(() => { - enabledColumns.forEach((column) => { - const columnKey = column.key as string; - - const rowItems = host.shadowRoot - .querySelector('.table-list') - ?.querySelectorAll( - `.row-item[data-column="${columnKey}"]`, - ); - - if (rowItems) { - rowItems.forEach((rowItem) => { - const left = rowItem.getBoundingClientRect().left; - const currentWidth = - getElementWidthWithMarginPadding( - rowItem.querySelector( - '.row-content', - ) as HTMLElement, - ).width; - if ( - !state.rowStyles[columnKey]?.width || - currentWidth > - (state.rowStyles[columnKey] - ?.width as number) - ) { - updateState({ - rowStyles: { - ...state.rowStyles, - [columnKey]: { - width: currentWidth, - left: state.tableListLeft - ? left - state.tableListLeft - : left, - }, - }, - }); - } - }); - } - }); - - updateState({ - isLoading: false, - }); - }, 0); - })} - ${listen('scroll', (event) => { - tableUpdate(event.target); - })} - ${listen('keydown', (event) => { - if (inputs.showLoading) { - event.preventDefault(); - event.stopImmediatePropagation(); - } - })} - > - ${repeat( - rows, - (item, index) => index, - (item: ListTableRow, index: number) => { - return listItem(item, index); - }, - )} - ${renderIf( - state.canScroll, - html` -
- <${ToniqIcon.assign({ - icon: ChevronsRight16Icon, - })}> -
- `, - )} -
- `, - )} -
{ + tableUpdate(); + window.addEventListener('resize', () => { + tableUpdate(); + }); + })} + ${listen('scroll', () => { + tableUpdate(); + })} + ${listen('keydown', (event) => { + if (inputs.showLoading) { + event.preventDefault(); + event.stopImmediatePropagation(); + } })} > - <${ToniqLoading.assign({ - size: ToniqLoadingSizeEnum.Large, - })}> + ${repeat( + enabledColumns, + (columnItem, columnIndex) => columnIndex, + (columnItem) => listItem(columnItem), + )} + ${renderIf( + state.canScroll, + html` +
+ <${ToniqIcon.assign({ + icon: ChevronsRight16Icon, + })}> +
+ `, + )}
+ ${renderIf( + isLoading, + html` +
+ <${ToniqLoading.assign({ + size: ToniqLoadingSizeEnum.Large, + })}> +
+ `, + )} ${paginationTemplate}
`; }, }); - -function getElementWidthWithMarginPadding(element: HTMLElement) { - const style = getComputedStyle(element); - - const width = element.offsetWidth; - const marginLeft = parseFloat(style.marginLeft); - const marginRight = parseFloat(style.marginRight); - const paddingLeft = parseFloat(style.paddingLeft); - const paddingRight = parseFloat(style.paddingRight); - - const gap = parseFloat(style.gap) || 0; - - return { - width: width + marginLeft + marginRight + gap, - margin: { - left: marginLeft, - right: marginRight, - }, - padding: { - left: paddingLeft, - right: paddingRight, - }, - }; -} diff --git a/packages/native-elements-test/package.json b/packages/native-elements-test/package.json index bd727f8b..34176ead 100644 --- a/packages/native-elements-test/package.json +++ b/packages/native-elements-test/package.json @@ -1,6 +1,6 @@ { "name": "@toniq-labs/design-system-native-elements-test", - "version": "16.22.10", + "version": "16.23.7-17", "private": true, "scripts": { "compile": "virmator compile", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 1c371edd..159bc874 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@toniq-labs/design-system-scripts", - "version": "16.22.10", + "version": "16.23.7-17", "private": true, "scripts": { "compile": "virmator compile",