diff --git a/packages/components/src/components/@shared/_kol-popover-button-mixin.scss b/packages/components/src/components/@shared/_kol-popover-button-mixin.scss new file mode 100644 index 00000000000..decb4dfad67 --- /dev/null +++ b/packages/components/src/components/@shared/_kol-popover-button-mixin.scss @@ -0,0 +1,17 @@ +@use 'mixins' as *; + +@mixin kol-popover-button-styles { + @layer kol-component { + .kol-popover-button { + &__button { + display: inline-block; // critical for floating UI to work correctly + } + + &__popover { + border: 1px solid #000; + margin: 0; + padding: 0; + } + } + } +} diff --git a/packages/components/src/components/@shared/_kol-table-settings-mixin.scss b/packages/components/src/components/@shared/_kol-table-settings-mixin.scss new file mode 100644 index 00000000000..b756b9dd881 --- /dev/null +++ b/packages/components/src/components/@shared/_kol-table-settings-mixin.scss @@ -0,0 +1,32 @@ +@use './mixins' as *; + +@mixin kol-table-settings-styles { + @layer kol-component { + .kol-table-settings { + background: #fff; + position: absolute; + right: 0; + top: 0; + z-index: 1; + + &__columns-container { + margin-top: rem(16); + max-height: rem(200); + overflow: auto; + } + + &__columns { + align-items: center; + display: grid; + gap: rem(8); + grid-auto-rows: min-content; + grid-template-columns: min-content 1fr rem(100) auto auto; + overflow: hidden; + } + + &__column { + display: contents; + } + } + } +} diff --git a/packages/components/src/components/@shared/_kol-table-stateless-mixin.scss b/packages/components/src/components/@shared/_kol-table-stateless-mixin.scss index 5b9b52939f7..4c46bb9cb05 100644 --- a/packages/components/src/components/@shared/_kol-table-stateless-mixin.scss +++ b/packages/components/src/components/@shared/_kol-table-stateless-mixin.scss @@ -1,16 +1,22 @@ -@use '../@shared/mixins' as *; @use '../host-display-block' as *; -@use '../tooltip/style.scss' as *; +@use '../tooltip/style' as *; +@use './kol-table-settings-mixin' as *; +@use './mixins' as *; @mixin kol-table-stateless-styles { + @include kol-table-settings-styles; + @layer kol-component { .kol-table { display: block; font-size: rem(16); - max-width: 100%; - overflow-x: auto; - overflow-y: hidden; + position: relative; + + &__scroll-container { + overflow-x: auto; + overflow-y: hidden; + } &__table { width: 100%; @@ -18,6 +24,8 @@ &__caption { text-align: start; + min-height: var(--a11y-min-size); // align with configuration button + padding-right: var(--a11y-min-size); } &__focus-element { diff --git a/packages/components/src/components/popover-button/component.tsx b/packages/components/src/components/popover-button/component.tsx new file mode 100644 index 00000000000..f237f79d4d0 --- /dev/null +++ b/packages/components/src/components/popover-button/component.tsx @@ -0,0 +1,257 @@ +import type { JSX } from '@stencil/core'; +import { Component, h, Method, Prop, State, Watch } from '@stencil/core'; +import { KolButtonWcTag } from '../../core/component-names'; +import { alignFloatingElements } from '../../utils/align-floating-elements'; +import type { PopoverButtonProps, PopoverButtonStates } from '../../schema/components/popover-button'; +import type { + AccessKeyPropType, + AlternativeButtonLinkRolePropType, + AriaDescriptionPropType, + ButtonCallbacksPropType, + ButtonTypePropType, + ButtonVariantPropType, + CustomClassPropType, + IconsPropType, + LabelWithExpertSlotPropType, + PopoverAlignPropType, + ShortKeyPropType, + StencilUnknown, + Stringified, + SyncValueBySelectorPropType, + TooltipAlignPropType, +} from '../../schema'; +import { validatePopoverAlign } from '../../schema'; + +/** + * @slot - The popover content. + */ +@Component({ + tag: 'kol-popover-button-wc', + shadow: false, +}) +// class implementing PopoverButtonProps and not API because we don't want to repeat the entire state and validation for button props +export class KolPopoverButton implements PopoverButtonProps { + private refButton?: HTMLKolButtonWcElement; + private refPopover?: HTMLDivElement; + + @State() public state: PopoverButtonStates = { + _label: '', + _popoverAlign: 'bottom', + }; + @State() private justClosed = false; + + /** + * Hides the popover programmatically by calling the native hidePopover method. + */ + @Method() + // eslint-disable-next-line @typescript-eslint/require-await + public async hidePopover() { + void this.refPopover?.hidePopover(); + } + + /* Regarding type issue see https://github.com/microsoft/TypeScript/issues/54864 */ + private handleBeforeToggle(event: Event) { + if ((event as ToggleEvent).newState === 'closed') { + this.justClosed = true; + + setTimeout(() => { + // Reset the flag after the event loop tick. + this.justClosed = false; + }, 10); // timeout of 0 should be sufficient but doesn't work in Safari Mobile (needs further investigation). + } else if (this.refPopover) { + /** + * Avoid "flicker" by hiding the element until the position is set in the `toggle` event handler. `alignFloatingElements` is responsible for setting the visibility back to 'visible'. + */ + this.refPopover.style.visibility = 'hidden'; + } + } + + private handleToggle(event: Event) { + if ((event as ToggleEvent).newState === 'open' && this.refPopover && this.refButton) { + void alignFloatingElements({ + align: this.state._popoverAlign, + floatingElement: this.refPopover, + referenceElement: this.refButton, + }); + } + } + + private handleButtonClick() { + // If the popover was just closed by native behavior, do nothing (and let it stay closed). + if (!this.justClosed) { + this.refPopover?.togglePopover(); + } + } + + public componentDidRender() { + this.refPopover?.addEventListener('toggle', this.handleToggle.bind(this)); + this.refPopover?.addEventListener('beforetoggle', this.handleBeforeToggle.bind(this)); + } + + public disconnectedCallback() { + this.refPopover?.removeEventListener('toggle', this.handleToggle.bind(this)); + this.refPopover?.removeEventListener('beforetoggle', this.handleBeforeToggle.bind(this)); + } + + public render(): JSX.Element { + return ( +
+ (this.refButton = element)} + onClick={this.handleButtonClick.bind(this)} + > + + + +
(this.refPopover = element)} data-testid="popover-content" popover="auto" id="popover" class="kol-popover-button__popover"> + +
+
+ ); + } + + /** + * Defines which key combination can be used to trigger or focus the interactive element of the component. + */ + @Prop() public _accessKey?: AccessKeyPropType; + + /** + * Defines which elements are controlled by this component. (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls) + */ + @Prop() public _ariaControls?: string; + + /** + * Defines the value for the aria-description attribute. + */ + @Prop() public _ariaDescription?: AriaDescriptionPropType; + + /** + * Defines whether the interactive element of the component expanded something. (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-expanded) + */ + @Prop() public _ariaExpanded?: boolean; + + /** + * Defines whether the interactive element of the component is selected (e.g. role=tab). (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-selected) + */ + @Prop() public _ariaSelected?: boolean; + + /** + * Defines the custom class attribute if _variant="custom" is set. + */ + @Prop() public _customClass?: CustomClassPropType; + + /** + * Makes the element not focusable and ignore all events. + */ + @Prop() public _disabled?: boolean = false; + + /** + * Hides the caption by default and displays the caption text with a tooltip when the + * interactive element is focused or the mouse is over it. + * @TODO: Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. + */ + @Prop() public _hideLabel?: boolean = false; + + /** + * Defines the icon classnames (e.g. `_icons="fa-solid fa-user"`). + */ + @Prop() public _icons?: IconsPropType; + + /** + * Defines the internal ID of the primary component element. + */ + @Prop() public _id?: string; + + /** + * Defines the visible or semantic label of the component (e.g. aria-label, label, headline, caption, summary, etc.). Set to `false` to enable the expert slot. + */ + @Prop() public _label!: LabelWithExpertSlotPropType; + + /** + * Defines the technical name of an input field. + */ + @Prop() public _name?: string; + + /** + * Defines the callback functions for button events. + */ + @Prop() public _on?: ButtonCallbacksPropType; + + /** + * Defines where to show the Popover preferably: top, right, bottom or left. + */ + @Prop() public _popoverAlign?: PopoverAlignPropType = 'bottom'; + + /** + * Defines the role of the components primary element. + */ + @Prop() public _role?: AlternativeButtonLinkRolePropType; + + /** + * Adds a visual short key hint to the component. + */ + @Prop() public _shortKey?: ShortKeyPropType; + + /** + * Selector for synchronizing the value with another input element. + * @internal + */ + @Prop() public _syncValueBySelector?: SyncValueBySelectorPropType; + + /** + * Defines which tab-index the primary element of the component has. (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) + */ + @Prop() public _tabIndex?: number; + + /** + * Defines where to show the Tooltip preferably: top, right, bottom or left. + */ + @Prop() public _tooltipAlign?: TooltipAlignPropType = 'top'; + + /** + * Defines either the type of the component or of the components interactive element. + */ + @Prop() public _type?: ButtonTypePropType = 'button'; + + /** + * Defines the value that the button emits on click. + */ + @Prop() public _value?: Stringified; + + /** + * Defines which variant should be used for presentation. + */ + @Prop() public _variant?: ButtonVariantPropType = 'normal'; + + @Watch('_popoverAlign') + public validatePopoverAlign(value?: PopoverAlignPropType): void { + validatePopoverAlign(this, value); + } + + public componentWillLoad() { + this.validatePopoverAlign(this._popoverAlign); + } +} diff --git a/packages/components/src/components/popover-button/shadow.tsx b/packages/components/src/components/popover-button/shadow.tsx index 145a9f490fb..dda6de13482 100644 --- a/packages/components/src/components/popover-button/shadow.tsx +++ b/packages/components/src/components/popover-button/shadow.tsx @@ -1,8 +1,7 @@ import type { JSX } from '@stencil/core'; -import { Component, h, Prop, State, Watch } from '@stencil/core'; -import { KolButtonWcTag } from '../../core/component-names'; -import { alignFloatingElements } from '../../utils/align-floating-elements'; -import type { PopoverButtonProps, PopoverButtonStates } from '../../schema/components/popover-button'; +import { Component, h, Method, Prop } from '@stencil/core'; +import { KolPopoverButtonWcTag } from '../../core/component-names'; +import type { PopoverButtonProps } from '../../schema/components/popover-button'; import type { AccessKeyPropType, AlternativeButtonLinkRolePropType, @@ -20,7 +19,6 @@ import type { SyncValueBySelectorPropType, TooltipAlignPropType, } from '../../schema'; -import { validatePopoverAlign } from '../../schema'; /** * @slot - The popover content. @@ -32,98 +30,48 @@ import { validatePopoverAlign } from '../../schema'; }, shadow: true, }) -// class implementing PopoverButtonProps and not API because we don't want to repeat the entire state and validation for button props export class KolPopoverButton implements PopoverButtonProps { - private refButton?: HTMLKolButtonWcElement; - private refPopover?: HTMLDivElement; - - @State() public state: PopoverButtonStates = { - _label: '', - _popoverAlign: 'bottom', - }; - @State() private justClosed = false; - - /* Regarding type issue see https://github.com/microsoft/TypeScript/issues/54864 */ - private handleBeforeToggle(event: Event) { - if ((event as ToggleEvent).newState === 'closed') { - this.justClosed = true; - - setTimeout(() => { - // Reset the flag after the event loop tick. - this.justClosed = false; - }, 10); // timeout of 0 should be sufficient but doesn't work in Safari Mobile (needs further investigation). - } else if (this.refPopover) { - /** - * Avoid "flicker" by hiding the element until the position is set in the `toggle` event handler. `alignFloatingElements` is responsible for setting the visibility back to 'visible'. - */ - this.refPopover.style.visibility = 'hidden'; - } - } - - private handleToggle(event: Event) { - if ((event as ToggleEvent).newState === 'open' && this.refPopover && this.refButton) { - void alignFloatingElements({ - align: this.state._popoverAlign, - floatingElement: this.refPopover, - referenceElement: this.refButton, - }); - } - } - - private handleButtonClick() { - // If the popover was just closed by native behavior, do nothing (and let it stay closed). - if (!this.justClosed) { - this.refPopover?.togglePopover(); - } - } - - public componentDidRender() { - this.refPopover?.addEventListener('toggle', this.handleToggle.bind(this)); - this.refPopover?.addEventListener('beforetoggle', this.handleBeforeToggle.bind(this)); - } + private ref?: HTMLKolPopoverButtonWcElement; - public disconnectedCallback() { - this.refPopover?.removeEventListener('toggle', this.handleToggle.bind(this)); - this.refPopover?.removeEventListener('beforetoggle', this.handleBeforeToggle.bind(this)); + /** + * Hides the popover programmatically by forwarding the call to the web component. + */ + @Method() + // eslint-disable-next-line @typescript-eslint/require-await + public async hidePopover() { + void this.ref?.hidePopover(); } public render(): JSX.Element { return ( -
- (this.refButton = element)} - onClick={this.handleButtonClick.bind(this)} - > - - - -
(this.refPopover = element)} data-testid="popover-content" popover="auto" id="popover" class="kol-popover-button__popover"> - -
-
+ (this.ref = element)} + _accessKey={this._accessKey} + _ariaControls={this._ariaControls} + _ariaDescription={this._ariaDescription} + _ariaExpanded={this._ariaExpanded} + _ariaSelected={this._ariaSelected} + _customClass={this._customClass} + _disabled={this._disabled} + _hideLabel={this._hideLabel} + _icons={this._icons} + _id={this._id} + _label={this._label} + _name={this._name} + _on={this._on} + _popoverAlign={this._popoverAlign} + _role={this._role} + _shortKey={this._shortKey} + _syncValueBySelector={this._syncValueBySelector} + _tabIndex={this._tabIndex} + _tooltipAlign={this._tooltipAlign} + _type={this._type} + _value={this._value} + _variant={this._variant} + > + + + ); } @@ -239,13 +187,4 @@ export class KolPopoverButton implements PopoverButtonProps { * Defines which variant should be used for presentation. */ @Prop() public _variant?: ButtonVariantPropType = 'normal'; - - @Watch('_popoverAlign') - public validatePopoverAlign(value?: PopoverAlignPropType): void { - validatePopoverAlign(this, value); - } - - public componentWillLoad() { - this.validatePopoverAlign(this._popoverAlign); - } } diff --git a/packages/components/src/components/popover-button/style.scss b/packages/components/src/components/popover-button/style.scss index ee2f19db104..eca7a02ed0d 100644 --- a/packages/components/src/components/popover-button/style.scss +++ b/packages/components/src/components/popover-button/style.scss @@ -1,19 +1,7 @@ -@use '../@shared/mixins' as *; @use '../../styles/global' as *; @use '../@shared/kol-button-mixin' as *; +@use '../@shared/kol-popover-button-mixin' as *; +@use '../tooltip/style' as *; @include kol-button-styles('kol-button'); - -@layer kol-component { - .kol-popover-button { - &__button { - display: inline-block; // critical for floating UI to work correctly - } - - &__popover { - border: 1px solid #000; - margin: 0; - padding: 0; - } - } -} +@include kol-popover-button-styles; diff --git a/packages/components/src/components/popover-button/test/__snapshots__/snapshot.spec.tsx.snap b/packages/components/src/components/popover-button/test/__snapshots__/snapshot.spec.tsx.snap index 02a6e77a153..716083e3bf0 100644 --- a/packages/components/src/components/popover-button/test/__snapshots__/snapshot.spec.tsx.snap +++ b/packages/components/src/components/popover-button/test/__snapshots__/snapshot.spec.tsx.snap @@ -3,14 +3,10 @@ exports[`kol-popover-button should render with _label="Click to toggle" 1`] = ` `; diff --git a/packages/components/src/components/table-stateful/shadow.tsx b/packages/components/src/components/table-stateful/shadow.tsx index 661c94bedaf..9528e8db9fc 100644 --- a/packages/components/src/components/table-stateful/shadow.tsx +++ b/packages/components/src/components/table-stateful/shadow.tsx @@ -41,6 +41,8 @@ import { import { Callback } from '../../schema/enums'; import type { MinWidthPropType } from '../../schema/props/min-width'; import { dispatchDomEvent, KolEvent } from '../../utils/events'; +import type { TableSettingsPropType } from '../../schema/props/table-settings'; +import { validateTableSettings } from '../../schema/props/table-settings'; const PAGINATION_OPTIONS = [10, 20, 50, 100]; @@ -121,6 +123,11 @@ export class KolTableStateful implements TableAPI { */ @Prop() public _on?: TableStatefulCallbacksPropType; + /** + * Defines the table settings including column visibility, order and width. + */ + @Prop() public _tableSettings?: TableSettingsPropType; + @State() public state: TableStates = { _allowMultiSort: false, _data: [], @@ -295,6 +302,11 @@ export class KolTableStateful implements TableAPI { validateTableStatefulCallbacks(this, value); } + @Watch('_tableSettings') + public validateTableSettings(value?: TableSettingsPropType): void { + validateTableSettings(this, value); + } + private readonly handlePagination: KoliBriPaginationButtonCallbacks = { onClick: (event: Event, page: number) => { if (typeof this.state._pagination._on?.onClick === 'function') { @@ -373,10 +385,11 @@ export class KolTableStateful implements TableAPI { this.validateHeaders(this._headers); this.validateLabel(this._label); this.validateMinWidth(this._minWidth); + this.validateOn(this._on); this.validatePagination(this._pagination); this.validatePaginationPosition(this._paginationPosition); this.validateSelection(this._selection); - this.validateOn(this._on); + this.validateTableSettings(this._tableSettings); } private selectDisplayedData(data: KoliBriTableDataType[], pageSize: number, page: number): KoliBriTableDataType[] { @@ -551,6 +564,7 @@ export class KolTableStateful implements TableAPI { }, }} _selection={this.state._selection} + _tableSettings={this.state._tableSettings} /> {this.pageEndSlice > 0 && this.showPagination && paginationBottom} diff --git a/packages/components/src/components/table-stateful/style.scss b/packages/components/src/components/table-stateful/style.scss index 47bdec17572..83eab3a3027 100644 --- a/packages/components/src/components/table-stateful/style.scss +++ b/packages/components/src/components/table-stateful/style.scss @@ -1,7 +1,13 @@ -@use '../@shared/mixins' as *; @use '../../styles/global' as *; +@use '../../styles/kol-alert-mixin' as *; +@use '../@shared/kol-button-mixin' as *; +@use '../@shared/kol-popover-button-mixin' as *; @use '../@shared/kol-table-stateless-mixin' as *; +@use '../@shared/mixins' as *; +@include kol-alert; +@include kol-button-styles('kol-button'); +@include kol-popover-button-styles; @include kol-table-stateless-styles; @layer kol-component { diff --git a/packages/components/src/components/table-stateless/component.tsx b/packages/components/src/components/table-stateless/component.tsx index 4917baa0afa..04955577805 100644 --- a/packages/components/src/components/table-stateless/component.tsx +++ b/packages/components/src/components/table-stateless/component.tsx @@ -2,7 +2,7 @@ import type { JSX } from '@stencil/core'; import { Component, Element, Fragment, h, Listen, Prop, State, Watch } from '@stencil/core'; import clsx from 'clsx'; -import { KolButtonWcTag, KolIconTag, KolTooltipWcTag } from '../../core/component-names'; +import { KolButtonWcTag, KolIconTag, KolTableSettingsWcTag, KolTooltipWcTag } from '../../core/component-names'; import type { TranslationKey } from '../../i18n'; import { translate } from '../../i18n'; import type { @@ -19,9 +19,11 @@ import type { TableDataPropType, TableHeaderCellsPropType, TableSelectionPropType, + TableSettings, TableStatelessAPI, TableStatelessStates, } from '../../schema'; +import { setState } from '../../schema'; import { validateLabel, validateTableCallbacks, @@ -30,11 +32,14 @@ import { validateTableHeaderCells, validateTableSelection, } from '../../schema'; +import type { ColumnSettings } from '../../schema/types'; import { Callback } from '../../schema/enums'; import type { MinWidthPropType } from '../../schema/props/min-width'; import { validateMinWidth } from '../../schema/props/min-width'; import { nonce } from '../../utils/dev.utils'; import { dispatchDomEvent, KolEvent } from '../../utils/events'; +import type { TableSettingsPropType } from '../../schema/props/table-settings'; +import { validateTableSettings } from '../../schema/props/table-settings'; /** * @internal @@ -102,6 +107,11 @@ export class KolTableStateless implements TableStatelessAPI { */ @Prop() public _selection?: TableSelectionPropType; + /** + * Defines the table settings including column visibility, order and width. + */ + @Prop() public _tableSettings?: TableSettingsPropType; + @Watch('_data') public validateData(value?: TableDataPropType) { validateTableData(this, value, { @@ -119,6 +129,7 @@ export class KolTableStateless implements TableStatelessAPI { @Watch('_headerCells') public validateHeaderCells(value?: TableHeaderCellsPropType) { validateTableHeaderCells(this, value); + this.initializeTableSettings(); } @Watch('_label') @@ -143,6 +154,11 @@ export class KolTableStateless implements TableStatelessAPI { validateTableSelection(this, value); } + @Watch('_tableSettings') + public validateTableSettings(value?: TableSettingsPropType) { + validateTableSettings(this, value); + } + @Listen('keydown') public handleKeyDown(event: KeyboardEvent) { if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { @@ -175,6 +191,11 @@ export class KolTableStateless implements TableStatelessAPI { } } + @Listen('settingsChange') + public handleSettingsChange(event: CustomEvent) { + setState(this, '_tableSettings', event.detail); + } + public disconnectedCallback() { this.tableDivElementResizeObserver?.disconnect(); } @@ -291,6 +312,23 @@ export class KolTableStateless implements TableStatelessAPI { return primaryHeadersWithKeys; } + private getColumnPositionMap(): Map { + const keyToPosition = new Map(); + this.state._tableSettings?.columns.forEach((setting) => { + keyToPosition.set(setting.key, setting.position); + }); + return keyToPosition; + } + + private sortByColumnPosition(columns: T[]): T[] { + const keyToPosition = this.getColumnPositionMap(); + return [...columns].sort((a, b) => { + const posA = keyToPosition.get(a.key ?? '') ?? Number.MAX_SAFE_INTEGER; + const posB = keyToPosition.get(b.key ?? '') ?? Number.MAX_SAFE_INTEGER; + return posA - posB; + }); + } + private createDataField(data: KoliBriTableDataType[], headers: KoliBriTableHeaders, isFoot?: boolean): (KoliBriTableCell & KoliBriTableDataType)[][] { headers.horizontal = Array.isArray(headers?.horizontal) ? headers.horizontal : []; headers.vertical = Array.isArray(headers?.vertical) ? headers.vertical : []; @@ -311,6 +349,8 @@ export class KolTableStateless implements TableStatelessAPI { rowSpans[index] = []; }); + const sortedPrimaryHeader = this.sortByColumnPosition(primaryHeader); + for (let i = startRow; i < maxRows; i++) { const dataRow: KoliBriTableHeaderCellWithLogic[] = []; headers.vertical.forEach((headerCells, index) => { @@ -342,33 +382,33 @@ export class KolTableStateless implements TableStatelessAPI { if (this.horizontal === true) { const row = isFoot && this.state._dataFoot ? this.state._dataFoot[i - startRow] : data[i]; if ( - typeof primaryHeader[j] === 'object' && - primaryHeader[j] !== null && - typeof primaryHeader[j].key === 'string' && + typeof sortedPrimaryHeader[j] === 'object' && + sortedPrimaryHeader[j] !== null && + typeof sortedPrimaryHeader[j].key === 'string' && typeof row === 'object' && row !== null ) { dataRow.push({ - ...primaryHeader[j], + ...sortedPrimaryHeader[j], colSpan: undefined, data: row, - label: row[primaryHeader[j].key as unknown as string] as string, + label: row[sortedPrimaryHeader[j].key as unknown as string] as string, rowSpan: undefined, }); } } else { if ( - typeof primaryHeader[i] === 'object' && - primaryHeader[i] !== null && - typeof primaryHeader[i].key === 'string' && + typeof sortedPrimaryHeader[i] === 'object' && + sortedPrimaryHeader[i] !== null && + typeof sortedPrimaryHeader[i].key === 'string' && typeof data[j] === 'object' && data[j] !== null ) { dataRow.push({ - ...primaryHeader[i], + ...sortedPrimaryHeader[i], colSpan: undefined, data: data[j], - label: data[j][primaryHeader[i].key as unknown as number] as string, + label: data[j][sortedPrimaryHeader[i].key as unknown as number] as string, rowSpan: undefined, }); } @@ -415,6 +455,24 @@ export class KolTableStateless implements TableStatelessAPI { } } + private initializeTableSettings() { + if (this._tableSettings) { + return; // when tableSettings are defined via props, don't override them. + } + const primaryHeaders = this.getPrimaryHeaders(this.state._headerCells as KoliBriTableHeaders); + if (!this.state._tableSettings) { + this.state._tableSettings = { columns: [] }; + } + this.state._tableSettings.columns = primaryHeaders + .filter((header) => header.key) // only headers with a key are supported + .map((header, index) => ({ + key: header.key ?? nonce(), + label: header.label, + position: index, + visible: true, + })); + } + public componentWillLoad(): void { this.validateData(this._data); this.validateDataFoot(this._dataFoot); @@ -423,6 +481,7 @@ export class KolTableStateless implements TableStatelessAPI { this.validateMinWidth(this._minWidth); this.validateOn(this._on); this.validateSelection(this._selection); + this.validateTableSettings(this._tableSettings); } /** @@ -535,6 +594,10 @@ export class KolTableStateless implements TableStatelessAPI { ); }; + private getColumnSettings(cell: KoliBriTableCell | KoliBriTableHeaderCell): ColumnSettings | undefined { + return this.state._tableSettings?.columns.find((setting) => setting.key === (cell as KoliBriTableHeaderCellWithLogic).key); + } + /** * Renders a table cell, either as a data cell (``) or a header cell (``). * If a custom `render` function is provided in the cell, it will be used to display content. @@ -545,6 +608,12 @@ export class KolTableStateless implements TableStatelessAPI { * @returns {JSX.Element} The rendered table cell (either `` or ``). */ private readonly renderTableCell = (cell: KoliBriTableCell, rowIndex: number, colIndex: number, isVertical: boolean): JSX.Element => { + // Skip rendering if the column is not visible + const columnSetting = this.getColumnSettings(cell); + if (columnSetting && !columnSetting.visible) { + return ''; + } + let key = `${rowIndex}-${colIndex}-${cell.label}`; if (cell.data) { const dataKey = this.getDataKey(cell.data); @@ -564,7 +633,7 @@ export class KolTableStateless implements TableStatelessAPI { rowSpan={cell.rowSpan} style={{ textAlign: cell.textAlign, - width: cell.width, + width: columnSetting?.width ? `${columnSetting.width}ch` : cell.width, }} ref={ typeof cell.render === 'function' @@ -608,6 +677,18 @@ export class KolTableStateless implements TableStatelessAPI { return selection; } + /** + * Calculates and returns the minimum width for a table based on its settings and columns' visibility and widths. + * + * @return {string} The minimum width of the table as a string. If `_minWidth` is set to 'auto', the width is + * calculated based on the total visible column widths in characters. Otherwise, it returns the greater value + * between `_minWidth` and the calculated total visible column widths. + */ + private getTableMinWidth(): string { + const totalColumnWidth = this.state._tableSettings?.columns.filter((col) => col.visible).reduce((total, col) => total + (col.width ?? 0), 0) ?? 0; + return this.state._minWidth === 'auto' ? `${totalColumnWidth}ch` : `max(${this.state._minWidth}, ${totalColumnWidth}ch)`; + } + /** * Renders the header cell for row selection. This cell contains a checkbox for selecting * all rows when selection is enabled. If multiple selection is allowed, the checkbox allows @@ -697,6 +778,12 @@ export class KolTableStateless implements TableStatelessAPI { * @returns {JSX.Element} The rendered header cell with possible sorting controls. */ private renderHeadingCell(cell: KoliBriTableHeaderCell, rowIndex: number, colIndex: number, isVertical: boolean): JSX.Element { + // Skip rendering if the column is not visible + const columnSettings = this.getColumnSettings(cell); + if (columnSettings && !columnSettings.visible) { + return ''; + } + let ariaSort = undefined; let sortButtonIcon = 'codicon codicon-fold'; @@ -726,7 +813,7 @@ export class KolTableStateless implements TableStatelessAPI { colSpan={cell.colSpan} rowSpan={cell.rowSpan} style={{ - width: cell.width, + width: columnSettings?.width ? `${columnSettings.width}ch` : cell.width, }} aria-sort={ariaSort} data-sort={`sort-${cell.sortDirection}`} @@ -793,51 +880,61 @@ export class KolTableStateless implements TableStatelessAPI { const dataField = this.createDataField(this.state._data, this.state._headerCells); this.checkboxRefs = []; + const sortedHorizontalHeaders = this.state._headerCells.horizontal?.map((row) => this.sortByColumnPosition(row)); + return ( - /* Firefox automatically makes the following div focusable when it has a scrollbar. We implement a similar behavior cross-browser by allowing the - *
to receive focus. Hence, we disable focus for the div to avoid having two focusable elements by setting `tabindex="-1"` - */ - /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ -
(this.tableDivElement = element)} class="kol-table" tabindex={this.tableDivElementHasScrollbar ? '-1' : undefined}> - + + + {/* Firefox automatically makes the following div focusable when it has a scrollbar. We implement a similar behavior cross-browser by allowing the + *
to receive focus. Hence, we disable focus for the div to avoid having two focusable elements by setting `tabindex="-1"` + */} + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} +
(this.tableDivElement = element)} + class="kol-table__scroll-container" + tabindex={this.tableDivElementHasScrollbar ? '-1' : undefined} > - {/* - * The following element allows the table to receive focus without providing redundant content to screen readers. - * The `div` is technically not allowed here. But any allowed element would mutate the table semantics. Additionally, the ` ` is necessary to - * prevent screen readers from just reading "blank". - */} - {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} -
-   -
- -
- - {Array.isArray(this.state._headerCells.horizontal) && ( - - {[ - this.state._headerCells.horizontal.map((cols, rowIndex) => ( - - {this.state._selection && this.renderHeadingSelectionCell()} - {rowIndex === 0 && this.renderHeaderTdCell()} - {Array.isArray(cols) && cols.map((cell, colIndex) => this.renderHeadingCell(cell, rowIndex, colIndex, false))} - - )), - this.renderSpacer('head', this.state._headerCells.horizontal), - ]} - - )} - - {dataField.map((row: (KoliBriTableCell & KoliBriTableDataType)[], rowIndex: number) => this.renderTableRow(row, rowIndex, true))} - - {this.renderFoot()} -
- {this.state._label} -
+ + {/* + * The following element allows the table to receive focus without providing redundant content to screen readers. + * The `div` is technically not allowed here. But any allowed element would mutate the table semantics. Additionally, the ` ` is necessary to + * prevent screen readers from just reading "blank". + */} + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} +
+   +
+ + + + {Array.isArray(sortedHorizontalHeaders) && ( + + {[ + sortedHorizontalHeaders.map((cols, rowIndex) => ( + + {this.state._selection && this.renderHeadingSelectionCell()} + {rowIndex === 0 && this.renderHeaderTdCell()} + {Array.isArray(cols) && cols.map((cell, colIndex) => this.renderHeadingCell(cell, rowIndex, colIndex, false))} + + )), + this.renderSpacer('head', sortedHorizontalHeaders), + ]} + + )} + + {dataField.map((row: (KoliBriTableCell & KoliBriTableDataType)[], rowIndex: number) => this.renderTableRow(row, rowIndex, true))} + + {this.renderFoot()} +
+ {this.state._label} +
+
); } diff --git a/packages/components/src/components/table-stateless/shadow.tsx b/packages/components/src/components/table-stateless/shadow.tsx index 8e8f09f7745..343a36e19b0 100644 --- a/packages/components/src/components/table-stateless/shadow.tsx +++ b/packages/components/src/components/table-stateless/shadow.tsx @@ -10,6 +10,7 @@ import type { TableStatelessProps, } from '../../schema'; import type { MinWidthPropType } from '../../schema/props/min-width'; +import type { TableSettingsPropType } from '../../schema/props/table-settings'; @Component({ tag: 'kol-table-stateless', @@ -54,6 +55,11 @@ export class KolTableStateless implements TableStatelessProps { */ @Prop() public _selection?: TableSelectionPropType; + /** + * Defines the table settings including column visibility, order and width. + */ + @Prop() public _tableSettings?: TableSettingsPropType; + public render(): JSX.Element { return ( ); } diff --git a/packages/components/src/components/table-stateless/style.scss b/packages/components/src/components/table-stateless/style.scss index 6a14645b5ec..0f67700f3e1 100644 --- a/packages/components/src/components/table-stateless/style.scss +++ b/packages/components/src/components/table-stateless/style.scss @@ -1,6 +1,13 @@ -@use '../@shared/mixins' as *; @use '../../styles/global' as *; +@use '../../styles/kol-alert-mixin' as *; +@use '../@shared/kol-button-mixin' as *; +@use '../@shared/kol-popover-button-mixin' as *; @use '../@shared/kol-table-stateless-mixin' as *; +@use '../@shared/mixins' as *; @use '../host-display-block' as *; +@use '../tooltip/style' as *; +@include kol-alert; +@include kol-button-styles('kol-button'); +@include kol-popover-button-styles; @include kol-table-stateless-styles; diff --git a/packages/components/src/components/table-stateless/table-settings.e2e.ts b/packages/components/src/components/table-stateless/table-settings.e2e.ts new file mode 100644 index 00000000000..e0f35a46e75 --- /dev/null +++ b/packages/components/src/components/table-stateless/table-settings.e2e.ts @@ -0,0 +1,243 @@ +import { expect } from '@playwright/test'; +import { test } from '@stencil/playwright'; +import type { TableHeaderCellsPropType, TableSettings } from '../../schema'; +import { KolEvent } from '../../utils/events'; + +const DATA = [ + { id: '1001', name: 'John', age: 30 }, + { id: '1002', name: 'Jane', age: 25 }, + { id: '1003', name: 'Bob', age: 35 }, +]; + +const HEADERS: TableHeaderCellsPropType = { + horizontal: [ + [ + { key: 'id', label: 'ID' }, + { key: 'name', label: 'Name' }, + { key: 'age', label: 'Age' }, + ], + ], +}; + +test.describe('kol-table-settings', () => { + test.beforeEach(async ({ page }) => { + await page.setContent(``); + await page.waitForChanges(); + }); + + test.describe('Basic Settings Popover Tests', () => { + test('it opens the settings popover when clicking the settings button', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + const popover = page.getByTestId('popover-content'); + await expect(popover).toBeVisible(); + }); + + test('it closes the popover when clicking the cancel button', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + const cancelButton = page.getByTestId('table-settings-cancel'); + await cancelButton.click(); + const popover = page.getByTestId('popover-content'); + await expect(popover).not.toBeVisible(); + }); + + test('it persists settings after closing and reopening the popover', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + // Hide the name column + const nameCheckbox = page.getByRole('checkbox', { name: 'Name' }); + await nameCheckbox.click(); + + // Apply changes + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + // Reopen settings + await settingsButton.click(); + + // Verify name column is still hidden + await expect(nameCheckbox).not.toBeChecked(); + }); + + test('it emits an DOM event when settings change', async ({ page }) => { + const tableStateless = page.locator('kol-table-stateless'); + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + const eventPromise = tableStateless.evaluate((element: HTMLKolTableStatelessElement, KolEvent) => { + return new Promise((resolve) => { + element.addEventListener(KolEvent.settingsChange, (event: Event) => { + resolve((event as CustomEvent).detail as TableSettings); + }); + }); + }, KolEvent); + + // Apply changes + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + await expect(eventPromise).resolves.toEqual({ + columns: [ + { + key: 'id', + label: 'ID', + position: 0, + visible: true, + }, + { + key: 'name', + label: 'Name', + position: 1, + visible: true, + }, + { + key: 'age', + label: 'Age', + position: 2, + visible: true, + }, + ], + }); + }); + }); + + test.describe('Column Visibility Management', () => { + test('it lists all columns in the settings', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + const columnLabels = page.locator('.kol-table-settings__column > span'); + await expect(columnLabels).toHaveText(['ID', 'Name', 'Age']); + }); + + test('it toggles visibility of individual columns', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + const nameCheckbox = page.getByRole('checkbox', { name: 'Name' }); + await nameCheckbox.click(); + + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + // Verify name column is hidden in the table + const nameColumn = page.locator('kol-table-stateless-wc th').filter({ hasText: 'Name' }); + await expect(nameColumn).not.toBeVisible(); + }); + + test('it shows error message when all columns are hidden', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + await page.waitForChanges(); + + // Hide all columns + const checkboxes = page.getByRole('checkbox'); + for (const checkbox of await checkboxes.all()) { + await checkbox.click(); + } + + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + const errorMessage = page.locator('kol-table-settings-wc kol-alert-wc'); + await expect(errorMessage).toBeVisible(); + }); + + test('it removes error message when at least one column is visible', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + await page.waitForChanges(); + + // Hide all columns + const checkboxes = page.getByRole('checkbox'); + for (const checkbox of await checkboxes.all()) { + await checkbox.click(); + } + + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + // Show one column + await checkboxes.first().click(); + await applyButton.click(); + + const errorMessage = page.locator('kol-table-settings-wc kol-alert-wc'); + await expect(errorMessage).not.toBeVisible(); + }); + }); + + test.describe('Column Width Management', () => { + test('it accepts valid width values', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + const idWidthInput = page.getByRole('spinbutton', { name: 'ID' }); + await idWidthInput.fill('50'); + + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + // Verify width is applied + const idColumn = page.locator('kol-table-stateless-wc th').filter({ hasText: 'ID' }); + await expect(idColumn).toHaveAttribute('style', 'width: 50ch;'); + }); + }); + + test.describe('Column Order Management', () => { + test('it disables up button for first column', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + const firstUpButton = page.getByTestId('table-settings-move-up').first().locator('button'); + await expect(firstUpButton).toBeDisabled(); + }); + + test('it disables down button for last column', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + const lastDownButton = page.getByTestId('table-settings-move-down').last().locator('button'); + await expect(lastDownButton).toBeDisabled(); + }); + + test('it moves a column up', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + // Move name column up + const nameUpButton = page.getByTestId('table-settings-move-up').filter({ hasText: 'Name' }); + await nameUpButton.click(); + + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + // Verify column order in table + const columns = page.locator('kol-table-stateless-wc th'); + await expect(columns.nth(0)).toHaveText('Name'); + await expect(columns.nth(1)).toHaveText('ID'); + }); + + test('it moves a column down', async ({ page }) => { + const settingsButton = page.getByTestId('popover-button').locator('button'); + await settingsButton.click(); + + // Move ID column down + const idDownButton = page.getByTestId('table-settings-move-down').filter({ hasText: 'ID' }); + await idDownButton.click(); + + const applyButton = page.getByTestId('table-settings-apply'); + await applyButton.click(); + + // Verify column order in table + const columns = page.locator('kol-table-stateless-wc th'); + await expect(columns.nth(0)).toHaveText('Name'); + await expect(columns.nth(1)).toHaveText('ID'); + }); + }); +}); diff --git a/packages/components/src/components/table-stateless/table-settings.tsx b/packages/components/src/components/table-stateless/table-settings.tsx new file mode 100644 index 00000000000..eaf4d6d3305 --- /dev/null +++ b/packages/components/src/components/table-stateless/table-settings.tsx @@ -0,0 +1,169 @@ +import type { JSX } from '@stencil/core'; +import { Component, Element, h, Prop, State, Watch } from '@stencil/core'; +import { translate } from '../../i18n'; +import { KolAlertWcTag, KolButtonWcTag, KolHeadingTag, KolInputCheckboxTag, KolInputNumberTag, KolPopoverButtonWcTag } from '../../core/component-names'; +import { dispatchDomEvent, KolEvent } from '../../utils/events'; +import type { TableSettingsPropType } from '../../schema/props/table-settings'; +import type { ColumnSettings } from '../../schema'; + +/** + * @internal + */ +@Component({ + tag: 'kol-table-settings-wc', + shadow: false, +}) +export class KolTableSettings { + @Element() private readonly host?: HTMLKolTableSettingsWcElement; + @State() tableSettings: TableSettingsPropType = { columns: [] }; + @State() errorMessage: string | null = null; + @Prop() _tableSettings: TableSettingsPropType = { columns: [] }; + + @Watch('_tableSettings') + handleTableSettingsChange(newValue: TableSettingsPropType) { + this.tableSettings = { + ...newValue, + columns: this.sortColumnsByPosition(newValue.columns), + }; + } + + public componentWillLoad() { + this.handleTableSettingsChange(this._tableSettings); + } + + private popoverRef: HTMLKolPopoverButtonWcElement | undefined; + + private sortColumnsByPosition(columns: ColumnSettings[]): ColumnSettings[] { + return [...columns].sort((colA, colB) => colA.position - colB.position); + } + + private moveColumn(columnId: string, direction: 'up' | 'down'): void { + const columnSettings = [...this.tableSettings.columns]; + + const sourceIndex = columnSettings.findIndex((col) => col.key === columnId); + const targetIndex = direction === 'up' ? sourceIndex - 1 : sourceIndex + 1; + + const source = columnSettings[sourceIndex]; + const target = columnSettings[targetIndex]; + + const newCols = columnSettings.map((col) => { + if (col.key === source.key) return { ...col, position: target.position }; + if (col.key === target.key) return { ...col, position: source.position }; + return col; + }); + + // re-sort by position and update + this.tableSettings = { + ...this.tableSettings, + columns: this.sortColumnsByPosition(newCols), + }; + } + + private handleVisibilityChange(key: string, visible: unknown): void { + this.tableSettings = { + ...this.tableSettings, + columns: this.tableSettings.columns.map((col) => (col.key === key ? { ...col, visible: Boolean(visible) } : col)), + }; + } + + private handleWidthChange(key: string, width: unknown): void { + this.tableSettings = { + ...this.tableSettings, + columns: this.tableSettings.columns.map((col) => (col.key === key ? { ...col, width: Number(width) } : col)), + }; + } + + private handleCancel() { + void this.popoverRef?.hidePopover(); + } + + private handleSubmit(event: Event): void { + event.preventDefault(); + + const hasVisibleColumn = this.tableSettings.columns.some((column) => column.visible); + + if (!hasVisibleColumn) { + this.errorMessage = translate('kol-table-settings-error-all-invisible'); + return; + } else if (this.host) { + this.errorMessage = null; + dispatchDomEvent(this.host, KolEvent.settingsChange, this.tableSettings); + void this.popoverRef?.hidePopover(); + } + } + + public render(): JSX.Element { + const sortedColumns = [...this.tableSettings.columns].sort((a, b) => a.position - b.position); + + return ( + (this.popoverRef = el)} + class="kol-table-settings" + _icons="codicon codicon-settings-gear" + _label={translate('kol-table-settings')} + _popoverAlign="top" + _hideLabel + > +
+ + + {this.errorMessage && } + +
+
+
+ {sortedColumns.map((column, index) => ( +
+ this.handleVisibilityChange(column.key, value) }} + /> + {column.label} + this.handleWidthChange(column.key, value) }} + /> + this.moveColumn(column.key, 'up') }} + _disabled={index === 0} + data-testid="table-settings-move-up" + /> + this.moveColumn(column.key, 'down') }} + _disabled={index === sortedColumns.length - 1} + data-testid="table-settings-move-down" + /> +
+ ))} +
+
+ +
+ this.handleCancel() }} + data-testid="table-settings-cancel" + /> + +
+
+
+
+ ); + } +} diff --git a/packages/components/src/components/table-stateless/test/__snapshots__/snapshot.spec.tsx.snap b/packages/components/src/components/table-stateless/test/__snapshots__/snapshot.spec.tsx.snap index d375b646702..dcde1ad55a6 100644 --- a/packages/components/src/components/table-stateless/test/__snapshots__/snapshot.spec.tsx.snap +++ b/packages/components/src/components/table-stateless/test/__snapshots__/snapshot.spec.tsx.snap @@ -3,73 +3,76 @@ exports[`kol-table-stateless-wc should render with _label="Table with horizontal and vertical headers" _minWidth="400px" _headerCells={"horizontal":[[{"key":"header1","label":"Header1","textAlign":"left"},{"key":"header2","label":"Header2","textAlign":"center"},{"key":"header3","label":"Header3","textAlign":"right"}]],"vertical":[[{"key":"row1","label":"Row 1","textAlign":"left"},{"key":"row2","label":"Row 2","textAlign":"center"},{"key":"row3","label":"Row 3","textAlign":"right"}]]} _data=[{"header1":"Cell 1.1","header2":"Cell 1.2","header3":"Cell 1.3"},{"header1":"Cell 2.1","header2":"Cell 2.2","header3":"Cell 2.3"},{"header1":"Cell 3.1","header2":"Cell 3.2","header3":"Cell 3.3"}] 1`] = `
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Table with horizontal and vertical headers -
- Header1 - - Header2 - - Header3 -
- Row 1 - - Cell 1.1 - - Cell 1.2 - - Cell 1.3 -
- Row 2 - - Cell 2.1 - - Cell 2.2 - - Cell 2.3 -
- Row 3 - - Cell 3.1 - - Cell 3.2 - - Cell 3.3 -
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Table with horizontal and vertical headers +
+ Header1 + + Header2 + + Header3 +
+ Row 1 + + Cell 1.1 + + Cell 1.2 + + Cell 1.3 +
+ Row 2 + + Cell 2.1 + + Cell 2.2 + + Cell 2.3 +
+ Row 3 + + Cell 3.1 + + Cell 3.2 + + Cell 3.3 +
+
`; @@ -77,43 +80,46 @@ exports[`kol-table-stateless-wc should render with _label="Table with horizontal exports[`kol-table-stateless-wc should render with _label="Table with only horizontal headers" _minWidth="400px" _headerCells={"horizontal":[[{"key":"header1","label":"Header 1","textAlign":"left"},{"key":"header2","label":"Header 2","textAlign":"center"}]],"vertical":[]} _data=[{"header1":"Cell 1.1","header2":"Cell 1.2"},{"header1":"Cell 2.1","header2":"Cell 2.2"}] 1`] = `
- -
- - - - - - - - - - - - - - - - - - - - -
- Table with only horizontal headers -
- Header 1 - - Header 2 -
- Cell 1.1 - - Cell 1.2 -
- Cell 2.1 - - Cell 2.2 -
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ Table with only horizontal headers +
+ Header 1 + + Header 2 +
+ Cell 1.1 + + Cell 1.2 +
+ Cell 2.1 + + Cell 2.2 +
+
`; @@ -121,58 +127,61 @@ exports[`kol-table-stateless-wc should render with _label="Table with only horiz exports[`kol-table-stateless-wc should render with _label="Table with two horizontal header rows" _minWidth="400px" _headerCells={"horizontal":[[{"label":"Header 1","textAlign":"left"},{"label":"Header 2","textAlign":"center"}],[{"key":"header1","label":"Sub Header 1","textAlign":"left"},{"key":"header2","label":"Sub Header 2","textAlign":"center"}]],"vertical":[[{"key":"row-1","label":"Row 1","textAlign":"left"},{"key":"row-2","label":"Row 2","textAlign":"center"}]]} _data=[{"header1":"Cell 1.1","header2":"Cell 1.2"},{"header1":"Cell 2.1","header2":"Cell 2.2"}] 1`] = `
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Table with two horizontal header rows -
- Header 1 - - Header 2 -
- Sub Header 1 - - Sub Header 2 -
- Row 1 - - Cell 1.1 - - Cell 1.2 -
- Row 2 - - Cell 2.1 - - Cell 2.2 -
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Table with two horizontal header rows +
+ Header 1 + + Header 2 +
+ Sub Header 1 + + Sub Header 2 +
+ Row 1 + + Cell 1.1 + + Cell 1.2 +
+ Row 2 + + Cell 2.1 + + Cell 2.2 +
+
`; @@ -180,58 +189,61 @@ exports[`kol-table-stateless-wc should render with _label="Table with two horizo exports[`kol-table-stateless-wc should render with _label="Table with two spanned horizontal and vertical headers" _minWidth="400px" _headerCells={"horizontal":[[{"label":"H-Header","colSpan":2}],[{"key":"header1","label":"Sub H-Header 1"},{"key":"header2","label":"Sub H-Header 2"}]],"vertical":[[{"label":"V-Header","rowSpan":2}],[{"label":"Sub V-Header 1"},{"label":"Sub V-Header 2"}]]} _data=[{"header1":"Cell 1.1","header2":"Cell 1.2"},{"header1":"Cell 2.1","header2":"Cell 2.2"}] 1`] = `
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Table with two spanned horizontal and vertical headers -
- H-Header -
- Sub H-Header 1 - - Sub H-Header 2 -
- V-Header - - Sub V-Header 1 - - Cell 1.1 - - Cell 1.2 -
- Sub V-Header 2 - - Cell 2.1 - - Cell 2.2 -
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Table with two spanned horizontal and vertical headers +
+ H-Header +
+ Sub H-Header 1 + + Sub H-Header 2 +
+ V-Header + + Sub V-Header 1 + + Cell 1.1 + + Cell 1.2 +
+ Sub V-Header 2 + + Cell 2.1 + + Cell 2.2 +
+
`; diff --git a/packages/components/src/core/component-names.ts b/packages/components/src/core/component-names.ts index a3e84b4897d..ff604c52054 100644 --- a/packages/components/src/core/component-names.ts +++ b/packages/components/src/core/component-names.ts @@ -37,6 +37,7 @@ export let KolNavTag = 'kol-nav' as const; export let KolPaginationTag = 'kol-pagination' as const; export let KolPopoverWcTag = 'kol-popover-wc' as const; export let KolPopoverButtonTag = 'kol-popover-button' as const; +export let KolPopoverButtonWcTag = 'kol-popover-button-wc' as const; export let KolProgressTag = 'kol-progress' as const; export let KolQuoteTag = 'kol-quote' as const; export let KolSelectTag = 'kol-select' as const; @@ -44,6 +45,7 @@ export let KolSingleSelectTag = 'kol-single-select' as const; export let KolSkipNavTag = 'kol-skip-nav' as const; export let KolSpinTag = 'kol-spin' as const; export let KolSplitButtonTag = 'kol-split-button' as const; +export let KolTableSettingsWcTag = 'kol-table-settings-wc' as const; export let KolTableStatefulTag = 'kol-table-stateful'; export let KolTableStatelessTag = 'kol-table-stateless' as const; export let KolTableStatelessWcTag = 'kol-table-stateless-wc' as const; @@ -98,6 +100,7 @@ export const setCustomTagNames = (transformTagName: (tagName: string) => string) KolPaginationTag = transformTagName(KolPaginationTag as string) as 'kol-pagination'; KolPopoverWcTag = transformTagName(KolPopoverWcTag as string) as 'kol-popover-wc'; KolPopoverButtonTag = transformTagName(KolPopoverButtonTag as string) as 'kol-popover-button'; + KolPopoverButtonWcTag = transformTagName(KolPopoverButtonWcTag as string) as 'kol-popover-button-wc'; KolProgressTag = transformTagName(KolProgressTag as string) as 'kol-progress'; KolQuoteTag = transformTagName(KolQuoteTag as string) as 'kol-quote'; KolSelectTag = transformTagName(KolSelectTag as string) as 'kol-select'; @@ -105,6 +108,7 @@ export const setCustomTagNames = (transformTagName: (tagName: string) => string) KolSkipNavTag = transformTagName(KolSkipNavTag as string) as 'kol-skip-nav'; KolSpinTag = transformTagName(KolSpinTag as string) as 'kol-spin'; KolSplitButtonTag = transformTagName(KolSplitButtonTag as string) as 'kol-split-button'; + KolTableSettingsWcTag = transformTagName(KolTableSettingsWcTag) as 'kol-table-settings-wc'; KolTableStatefulTag = transformTagName(KolTableStatefulTag) as 'kol-table-stateful'; KolTableStatelessTag = transformTagName(KolTableStatelessTag as string) as 'kol-table-stateless'; KolTableStatelessWcTag = transformTagName(KolTableStatelessWcTag as string) as 'kol-table-stateless-wc'; diff --git a/packages/components/src/locales/de.ts b/packages/components/src/locales/de.ts index 9c2796df78f..3f5fffac42a 100644 --- a/packages/components/src/locales/de.ts +++ b/packages/components/src/locales/de.ts @@ -40,6 +40,14 @@ export default { 'table-selection-all': 'Alle auswählen', 'table-selection-none': 'Alle abwählen', 'table-selection-indeterminate': 'Alle auswählen bei teilweiser Auswahl', + 'table-settings': 'Tabellenkonfiguration', + 'table-settings-cancel': 'Abbrechen', + 'table-settings-apply': 'Übernehmen', + 'table-settings-show-column': 'Spalte {{column}} anzeigen', + 'table-settings-column-width': 'Breite von {{column}}', + 'table-settings-move-up': 'Spalte {{column}} nach oben verschieben', + 'table-settings-move-down': 'Spalte {{column}} nach unten verschieben', + 'table-settings-error-all-invisible': 'Mindestens eine Spalte muss sichtbar sein.', dropdown: 'Auswahlliste', 'nav-label-open': 'Untermenü zu {{label}} öffnen', 'nav-label-close': 'Untermenü zu {{label}} schließen', diff --git a/packages/components/src/locales/en.ts b/packages/components/src/locales/en.ts index 38468a2909d..921d15cc59f 100644 --- a/packages/components/src/locales/en.ts +++ b/packages/components/src/locales/en.ts @@ -40,6 +40,14 @@ export default { 'table-selection-all': 'Select all', 'table-selection-none': 'Deselect all', 'table-selection-indeterminate': 'Select all with partial selection', + 'table-settings': 'Table configuration', + 'table-settings-cancel': 'Cancel', + 'table-settings-apply': 'Apply', + 'table-settings-show-column': 'Show column {{column}}', + 'table-settings-column-width': '{{column}} width', + 'table-settings-move-up': 'Move {{column}} column up', + 'table-settings-move-down': 'Move {{column}} column down', + 'table-settings-error-all-invisible': 'At least one column must be visible.', dropdown: 'Dropdown', 'nav-label-open': 'Submenu for {{label}} open', 'nav-label-close': 'Submenu for {{label}} close', diff --git a/packages/components/src/schema/components/table.ts b/packages/components/src/schema/components/table.ts index 661b65a75e6..380f186b30e 100644 --- a/packages/components/src/schema/components/table.ts +++ b/packages/components/src/schema/components/table.ts @@ -5,6 +5,7 @@ import type { PropMinWidth } from '../props/min-width'; import type { PropPaginationPosition } from '../props/pagination-position'; import type { KoliBriSortDirection, KoliBriTableDataType, KoliBriTableHeaderCell, KoliBriTableSelection, Stringified } from '../types'; import type { KoliBriPaginationProps } from './pagination'; +import type { PropTableSettings } from '../props/table-settings'; export type KoliBriDataCompareFn = (a: KoliBriTableDataType, b: KoliBriTableDataType) => number; @@ -44,7 +45,8 @@ type OptionalProps = { } & PropTableDataFoot & PropPaginationPosition & PropTableSelection & - StatefulPropTableCallbacks; + StatefulPropTableCallbacks & + PropTableSettings; type RequiredStates = { allowMultiSort: boolean; @@ -59,7 +61,8 @@ type RequiredStates = { type OptionalStates = { sortDirection: KoliBriSortDirection; selection: KoliBriTableSelection; -} & StatefulPropTableCallbacks; +} & StatefulPropTableCallbacks & + PropTableSettings; export type TableStates = Generic.Element.Members; export type TableAPI = Generic.Element.ComponentApi; diff --git a/packages/components/src/schema/components/tableStateless.ts b/packages/components/src/schema/components/tableStateless.ts index 56f5ea19f7c..cb02286a0f3 100644 --- a/packages/components/src/schema/components/tableStateless.ts +++ b/packages/components/src/schema/components/tableStateless.ts @@ -3,10 +3,11 @@ import type { PropLabel, PropTableCallbacks, PropTableData, PropTableDataFoot, P import type { PropMinWidth } from '../props/min-width'; import type { PropTableHeaderCells } from '../props/table-header-cells'; import type { KoliBriTableDataType, KoliBriTableSelection } from '../types'; +import type { PropTableSettings } from '../props/table-settings'; type RequiredProps = PropLabel & PropMinWidth & PropTableData & PropTableHeaderCells; -type OptionalProps = PropTableCallbacks & PropTableDataFoot & PropTableSelection; +type OptionalProps = PropTableCallbacks & PropTableDataFoot & PropTableSelection & PropTableSettings; type RequiredStates = { headerCells: TableHeaderCells; @@ -17,7 +18,8 @@ type RequiredStates = { type OptionalStates = { dataFoot: KoliBriTableDataType[]; selection: KoliBriTableSelection; -} & PropTableCallbacks; +} & PropTableCallbacks & + PropTableSettings; export type TableStatelessProps = Generic.Element.Members; export type TableStatelessStates = Generic.Element.Members; diff --git a/packages/components/src/schema/props/table-settings.ts b/packages/components/src/schema/props/table-settings.ts new file mode 100644 index 00000000000..3bc335ccb7a --- /dev/null +++ b/packages/components/src/schema/props/table-settings.ts @@ -0,0 +1,19 @@ +import type { Generic } from 'adopted-style-sheets'; + +import { watchValidator } from '../utils'; +import type { TableSettings } from '../types/table-settings'; + +/* types */ +export type TableSettingsPropType = TableSettings; + +/** + * Defines the table settings including column visibility, order and width. + */ +export type PropTableSettings = { + tableSettings: TableSettingsPropType; +}; + +/* validator */ +export const validateTableSettings = (component: Generic.Element.Component, value?: TableSettingsPropType): void => { + watchValidator(component, `_tableSettings`, (value) => typeof value === 'object' && value !== null, new Set(['TableSettings']), value); +}; diff --git a/packages/components/src/schema/types/index.ts b/packages/components/src/schema/types/index.ts index 474dc2c3001..9d7432cc718 100644 --- a/packages/components/src/schema/types/index.ts +++ b/packages/components/src/schema/types/index.ts @@ -12,3 +12,4 @@ export * from './progress'; export * from './unknown'; export * from './w3c'; export * from './table'; +export * from './table-settings'; diff --git a/packages/components/src/schema/types/table-settings.ts b/packages/components/src/schema/types/table-settings.ts new file mode 100644 index 00000000000..ba7fb46e1b9 --- /dev/null +++ b/packages/components/src/schema/types/table-settings.ts @@ -0,0 +1,11 @@ +export interface ColumnSettings { + key: string; + label: string; + visible: boolean; + position: number; + width?: number; +} + +export interface TableSettings { + columns: ColumnSettings[]; +} diff --git a/packages/components/src/utils/events.ts b/packages/components/src/utils/events.ts index e0b4ce6b390..4d8b3005ec3 100644 --- a/packages/components/src/utils/events.ts +++ b/packages/components/src/utils/events.ts @@ -14,6 +14,7 @@ enum KolEvent { sort = 'kolSort', submit = 'kolSubmit', toggle = 'kolToggle', + settingsChange = 'settingsChange', } function createKoliBriEvent(event: KolEvent, detail?: T): CustomEvent { diff --git a/packages/components/stencil.config.ts b/packages/components/stencil.config.ts index 2ed2641749c..9c9f9f81703 100644 --- a/packages/components/stencil.config.ts +++ b/packages/components/stencil.config.ts @@ -48,6 +48,7 @@ const TAGS = [ 'kol-skip-nav', 'kol-spin', 'kol-split-button', + 'kol-table-settings-wc', 'kol-table-stateful', 'kol-table-stateless', 'kol-tabs', @@ -71,6 +72,7 @@ const EXCLUDE_TAGS = [ 'kol-link-wc', 'kol-popover-wc', 'kol-span-wc', + 'kol-table-settings-wc', 'kol-table-stateless-wc', 'kol-tooltip-wc', ]; diff --git a/packages/samples/react/src/components/table/predefined-settings.tsx b/packages/samples/react/src/components/table/predefined-settings.tsx new file mode 100644 index 00000000000..19bacd39e4b --- /dev/null +++ b/packages/samples/react/src/components/table/predefined-settings.tsx @@ -0,0 +1,41 @@ +import { SampleDescription } from '../SampleDescription'; +import { KolTableStateful } from '@public-ui/react'; +import type { FC } from 'react'; +import React from 'react'; + +const DATA = [{ columnA: 'Column A', columnB: 'Column B', columnC: 'Column C' }]; + +export const PredefinedSettings: FC = () => { + return ( + <> + +

This example shows the table with predefined settings.

+
+ + + + ); +}; diff --git a/packages/samples/react/src/components/table/render-cell.tsx b/packages/samples/react/src/components/table/render-cell.tsx index cc378ebc4f2..64272431e8e 100644 --- a/packages/samples/react/src/components/table/render-cell.tsx +++ b/packages/samples/react/src/components/table/render-cell.tsx @@ -84,7 +84,7 @@ const HEADERS: KoliBriTableHeaders = { }, { label: 'Action (react)', - key: 'order', + key: 'action', width: '20em', /* Example 4: Render function using React */ diff --git a/packages/samples/react/src/components/table/routes.ts b/packages/samples/react/src/components/table/routes.ts index 7f0d80f36bc..4224acdfd9e 100644 --- a/packages/samples/react/src/components/table/routes.ts +++ b/packages/samples/react/src/components/table/routes.ts @@ -14,23 +14,25 @@ import { TableStatelessWithSingleSelection } from './stateless-with-single-selec import { TableWithPagination } from './with-pagination'; import { InteractiveChildElements } from './interactive-child-elements'; import { MultiSortTable } from './multi-sort'; +import { PredefinedSettings } from './predefined-settings'; export const TABLE_ROUTES: Routes = { table: { 'column-alignment': TableColumnAlignment, 'complex-headers': TableComplexHeaders, 'horizontal-scrollbar': TableHorizontalScrollbar, + 'interactive-child-elements': InteractiveChildElements, + 'multi-sort': MultiSortTable, 'pagination-position': PaginationPosition, + 'predefined-settings': PredefinedSettings, 'render-cell': TableRenderCell, 'sort-data': TableSortData, - 'with-footer': TableWithFooter, 'stateful-with-selection': TableStatefulWithSelection, 'stateful-with-single-selection': TableStatefulWithSingleSelection, + stateless: TableStateless, 'stateless-with-selection': TableStatelessWithSelection, 'stateless-with-single-selection': TableStatelessWithSingleSelection, + 'with-footer': TableWithFooter, 'with-pagination': TableWithPagination, - 'interactive-child-elements': InteractiveChildElements, - stateless: TableStateless, - 'multi-sort': MultiSortTable, }, }; diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png index a94363f5007..efc5eb065c1 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png index ac54223bc0e..9b641ee094e 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png index 335d84ed7fb..99411e94068 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png index 8690103f082..43d320aedb1 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png index a3e81536b6a..1aa1c281c94 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png index 7f78601f30b..7db703b2882 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png index 0ad54085305..c3d8de64f63 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png index b29600642ad..2f46b24c3e7 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png index fe79f15b460..602df487c6e 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png index 21c04cfb45c..3017fdbe5f3 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png index 6e7e2372a06..25d81ab6487 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png index 1d3d5c2f4cf..de6897ca604 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png index a03d4056161..0116f32ca16 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png index 35b8c5b4407..4efdca6c179 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png index c886f202c2e..7f132186d87 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png index a67cec894d9..d99cfd1c9cf 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png index 1473f64749d..f55d771b024 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png index f70c1dd5f6d..c05e929d683 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png index 0a5b1e0d233..0d59f3645fe 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png index 67d13a04f9c..ac0aa90cbd5 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png index a31fd00c7b4..1d87be00a28 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png index d568d70b929..f1c94229ac4 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png index c54d0a7c16f..317902002f9 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png differ diff --git a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png index 89ef6dcf060..a5bfa2895f5 100644 Binary files a/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png and b/packages/test-tag-name-transformer/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png index a94363f5007..efc5eb065c1 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png index ac54223bc0e..9b641ee094e 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-button-short-key-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png index 335d84ed7fb..99411e94068 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png index 8690103f082..43d320aedb1 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-column-alignment-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png index a3e81536b6a..1aa1c281c94 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png index 7f78601f30b..7db703b2882 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-complex-headers-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png index 0ad54085305..c3d8de64f63 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png index b29600642ad..2f46b24c3e7 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-pagination-position-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png index fe79f15b460..602df487c6e 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png index 21c04cfb45c..3017fdbe5f3 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-sort-data-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png index 6e7e2372a06..25d81ab6487 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png index 1d3d5c2f4cf..de6897ca604 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-selection-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png index a03d4056161..0116f32ca16 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png index 35b8c5b4407..4efdca6c179 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateful-with-single-selection-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png index c886f202c2e..7f132186d87 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png index a67cec894d9..d99cfd1c9cf 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png index 1473f64749d..f55d771b024 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-selection-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png index f70c1dd5f6d..c05e929d683 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png index 0a5b1e0d233..0d59f3645fe 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-with-single-selection-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png index 67d13a04f9c..ac0aa90cbd5 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-stateless-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png index a31fd00c7b4..1d87be00a28 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png index d568d70b929..f1c94229ac4 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-footer-zoom-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png index c54d0a7c16f..317902002f9 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-firefox-linux.png differ diff --git a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png index 89ef6dcf060..a5bfa2895f5 100644 Binary files a/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png and b/packages/themes/default/snapshots/theme-default/snapshot-for-table-with-pagination-zoom-firefox-linux.png differ diff --git a/packages/themes/default/src/components/table-stateful.scss b/packages/themes/default/src/components/table-stateful.scss index 6603835e939..afa5f7af1b1 100644 --- a/packages/themes/default/src/components/table-stateful.scss +++ b/packages/themes/default/src/components/table-stateful.scss @@ -1,7 +1,7 @@ @use '../mixins/kol-table-stateless-wc' as *; @layer kol-theme-component { - @include kol-table-stateless-wc; + @include kol-table-stateless-theme; .kol-table-stateful { &__pagination { diff --git a/packages/themes/default/src/components/table-stateless.scss b/packages/themes/default/src/components/table-stateless.scss index e9289aede74..72f0ae9f4fa 100644 --- a/packages/themes/default/src/components/table-stateless.scss +++ b/packages/themes/default/src/components/table-stateless.scss @@ -1,5 +1,5 @@ @use '../mixins/kol-table-stateless-wc' as *; @layer kol-theme-component { - @include kol-table-stateless-wc; + @include kol-table-stateless-theme; } diff --git a/packages/themes/default/src/mixins/kol-table-settings-wc.scss b/packages/themes/default/src/mixins/kol-table-settings-wc.scss new file mode 100644 index 00000000000..1721cf7d795 --- /dev/null +++ b/packages/themes/default/src/mixins/kol-table-settings-wc.scss @@ -0,0 +1,37 @@ +@use './rem' as *; +@use './button' as *; +@use './focus-outline' as *; + +@mixin kol-table-settings-theme { + .kol-table-settings { + right: rem(8); + top: rem(8); + + &__content { + padding: rem(24); + @include kol-button('kol-button'); // use buttons styles only in the content, not for the popover button + } + + &__error-message { + display: block; + margin-top: rem(16); + } + + &__columns { + padding: rem(8); // some space for hover and focus styles + } + + &__actions { + border-top: var(--border-width) solid var(--color-mute-variant); + display: flex; + gap: rem(8); + justify-content: flex-end; + margin-top: rem(16); + padding-top: rem(16); + } + } + + .kol-button:focus { + @include focus-outline; + } +} diff --git a/packages/themes/default/src/mixins/kol-table-stateless-wc.scss b/packages/themes/default/src/mixins/kol-table-stateless-wc.scss index 3912d23521a..18c2787b3b8 100644 --- a/packages/themes/default/src/mixins/kol-table-stateless-wc.scss +++ b/packages/themes/default/src/mixins/kol-table-stateless-wc.scss @@ -1,6 +1,11 @@ @use './rem' as *; +@use './kol-table-settings-wc' as *; +@use './alert-wc' as *; + +@mixin kol-table-stateless-theme { + @include kol-table-settings-theme; + @include kol-alert-theme; -@mixin kol-table-stateless-wc { .kol-table { $root: &; @@ -26,7 +31,8 @@ } &__caption { - padding: rem(8); + padding: rem(8) var(--a11y-min-size) rem(8) rem(8); + min-height: calc(var(--a11y-min-size) + 2px); // leave some extra space so the settings button doesn't overlap the table borders } &__focus-element { diff --git a/packages/themes/ecl/src/ecl-ec/mixins/kol-table-stateless-wc.scss b/packages/themes/ecl/src/ecl-ec/mixins/kol-table-stateless-wc.scss index bc23ac1ba17..5e57a9c31de 100644 --- a/packages/themes/ecl/src/ecl-ec/mixins/kol-table-stateless-wc.scss +++ b/packages/themes/ecl/src/ecl-ec/mixins/kol-table-stateless-wc.scss @@ -1,15 +1,20 @@ @use '../../mixins/rem' as *; +@use './alert-wc' as *; @mixin kol-table-stateless-wc { + @include kol-alert-theme; + .kol-table { $root: &; display: block; font-family: var(--font-family); - overflow-x: auto; - overflow-y: hidden; - padding: 0.5em; + &__scroll-container { + overflow-x: auto; + overflow-y: hidden; + padding: 0.5em; + } &__caption { padding: 0.5em; diff --git a/packages/themes/ecl/src/ecl-eu/mixins/kol-table-stateless-wc.scss b/packages/themes/ecl/src/ecl-eu/mixins/kol-table-stateless-wc.scss index fa6466442bd..4630cb5bd32 100644 --- a/packages/themes/ecl/src/ecl-eu/mixins/kol-table-stateless-wc.scss +++ b/packages/themes/ecl/src/ecl-eu/mixins/kol-table-stateless-wc.scss @@ -1,6 +1,9 @@ @use '../../mixins/rem' as *; +@use './alert-wc' as *; @mixin kol-table-stateless-wc { + @include kol-alert-theme; + .kol-table { $root: &;