From a7315d0aca4eb9b282e1929ee2e5cb8f3823a34e Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Thu, 7 Sep 2023 17:56:52 +0200 Subject: [PATCH 1/9] feat: initial redesign --- .../sbb-tab-group/sbb-tab-group.scss | 8 +- .../sbb-tab-title/sbb-tab-title.scss | 100 ++++++++---------- .../sbb-tab-title/sbb-tab-title.tsx | 30 +++--- 3 files changed, 66 insertions(+), 72 deletions(-) diff --git a/src/components/sbb-tab-group/sbb-tab-group.scss b/src/components/sbb-tab-group/sbb-tab-group.scss index 6202078a13..303081b0f4 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.scss +++ b/src/components/sbb-tab-group/sbb-tab-group.scss @@ -5,9 +5,13 @@ @include sbb.host-component-properties; .tab-group { - display: flex; + display: inline-flex; flex-wrap: wrap; - gap: var(--sbb-spacing-fixed-3x); + position: relative; + padding: calc(var(--sbb-focus-outline-width) + var(--sbb-focus-outline-offset)); + inset-block-start: calc((var(--sbb-focus-outline-width) + var(--sbb-focus-outline-offset)) * -1); + inset-inline-start: calc((var(--sbb-focus-outline-width) + var(--sbb-focus-outline-offset)) * -1); + overflow: hidden; } .tab-content { diff --git a/src/components/sbb-tab-title/sbb-tab-title.scss b/src/components/sbb-tab-title/sbb-tab-title.scss index 10d8d400e2..9d9e1147ac 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.scss +++ b/src/components/sbb-tab-title/sbb-tab-title.scss @@ -6,16 +6,13 @@ :host { --sbb-tab-title-height: #{sbb.px-to-rem-build(40)}; - --sbb-tab-title-border-radius: var(--sbb-border-radius-infinity); - --sbb-tab-title-color: var(--sbb-color-charcoal-default); + --sbb-tab-title-color: var(--sbb-color-granite-default); --sbb-tab-title-icon-color: var(--sbb-color-black-default); --sbb-tab-title-background-color: var(--sbb-color-white-default); - --sbb-tab-title-border-color: var(--sbb-color-cloud-default); --sbb-tab-title-cursor: pointer; --sbb-tab-title-pointer-events: unset; - --sbb-tab-title-shift: translateY(0); --sbb-tab-title-inset: 0; - --sbb-tab-title-gap: var(--sbb-spacing-fixed-2x); + --sbb-tab-title-marker-inset-inline: 50%; --sbb-tab-title-text-decoration: none; --sbb-tab-title-animation-duration: var(--sbb-animation-duration-2x); --sbb-tab-title-animation-easing: var(--sbb-animation-easing); @@ -33,73 +30,64 @@ --sbb-tab-title-color: ButtonText; --sbb-tab-title-icon-color: ButtonText; --sbb-tab-title-amount-color: ButtonText; - --sbb-tab-title-border-color: CanvasText; } } :host([disabled]:not([disabled='false'])) { - --sbb-tab-title-color: var(--sbb-color-granite-default); --sbb-tab-title-icon-color: var(--sbb-color-granite-default); --sbb-tab-title-background-color: var(--sbb-color-milk-default); - --sbb-tab-title-border-color: var(--sbb-color-cloud-default); --sbb-tab-title-cursor: unset; --sbb-tab-title-pointer-events: none; - --sbb-tab-title-amount-color: var(--sbb-color-granite-default); --sbb-tab-title-text-decoration: line-through; @include sbb.if-forced-colors { --sbb-tab-title-color: GrayText; --sbb-tab-title-icon-color: GrayText; --sbb-tab-title-amount-color: GrayText; - --sbb-tab-title-border-color: GrayText; } } // If active and not disabled :host([active]:not([active='false'], [disabled]:not([disabled='false']))) { - --sbb-tab-title-color: var(--sbb-color-white-default); + --sbb-tab-title-color: var(--sbb-color-charcoal-default); --sbb-tab-title-icon-color: var(--sbb-tab-title-color); --sbb-tab-title-background-color: var(--sbb-color-black-default); - --sbb-tab-title-border-color: var(--sbb-color-black-default); - --sbb-tab-title-amount-color: var(--sbb-color-cement-default); --sbb-tab-title-cursor: unset; --sbb-tab-title-pointer-events: none; + --sbb-tab-title-marker-inset-inline: 0; @include sbb.if-forced-colors { --sbb-tab-title-color: ButtonText; --sbb-tab-title-icon-color: ButtonText; --sbb-tab-title-amount-color: ButtonText; - --sbb-tab-title-border-color: Highlight; } } -// Hover if not disabled, not active and not pressed -:host( - :hover:not( - [disabled]:not([disabled='false']), - [data-active], - :active, - [active]:not([active='false']) - ) - ) { +:host(:hover:not([disabled]:not([disabled='false']))) { @include sbb.hover-mq($hover: true) { - --sbb-tab-title-shift: translateY(calc(-1 * #{sbb.px-to-rem-build(1)})); - --sbb-tab-title-inset: calc(-1 * #{sbb.px-to-rem-build(2)}); - --sbb-tab-title-background-color: var(--sbb-color-milk-default); - - @include sbb.if-forced-colors { - --sbb-tab-title-border-color: Highlight; - } + --sbb-tab-title-marker-inset-inline: 0; } } // Pressed/active state :host(:is([data-active], :active)) { - --sbb-tab-title-border-color: var(--sbb-color-black-default); - --sbb-tab-title-background-color: var(--sbb-color-white-default); + --sbb-tab-title-color: var(--sbb-color-charcoal-default); +} - @include sbb.if-forced-colors { - --sbb-tab-title-border-color: Highlight; +.sbb-tab-title__wrapper { + position: relative; + + // Hide focus outline when focus origin is mouse or touch. This is being used in tooltip as a workaround. + :host(:focus-visible:not([data-focus-origin='mouse'], [data-focus-origin='touch'])) & { + &::before { + content: ''; + position: absolute; + display: block; + inset: calc((var(--sbb-focus-outline-width) + var(--sbb-focus-outline-offset)) * -1); + border: var(--sbb-focus-outline-width) solid var(--sbb-focus-outline-color); + border-radius: var(--sbb-border-radius-2x); + z-index: 1; + } } } @@ -110,43 +98,47 @@ display: flex; align-items: center; padding-block: var(--sbb-spacing-fixed-2x); - padding-inline: var(--sbb-spacing-fixed-5x); - gap: var(--sbb-tab-title-gap); + padding-inline: var(--sbb-spacing-responsive-xs); + gap: var(--sbb-spacing-fixed-2x); user-select: none; cursor: var(--sbb-tab-title-cursor); transition: color var(--sbb-tab-title-animation-duration) var(--sbb-tab-title-animation-easing); color: var(--sbb-tab-title-icon-color); pointer-events: var(--sbb-tab-title-pointer-events); + // Show a border between flex rows when the tab titles wrap to a new line + &::after { + content: ''; + position: absolute; + inset-inline-start: 0; + inset-block-end: 0; + width: 100vw; + height: var(--sbb-border-width-1x); + background-color: var(--sbb-color-cloud-default); + } + &::before { position: absolute; content: ''; - inset: var(--sbb-tab-title-inset); - border: var(--sbb-border-width-1x) solid var(--sbb-tab-title-border-color); - border-radius: var(--sbb-tab-title-border-radius); - background-color: var(--sbb-tab-title-background-color); + inset-inline: var(--sbb-tab-title-marker-inset-inline); + inset-block-end: 0; + height: #{sbb.px-to-rem-build(3)}; + background-color: var(--sbb-tab-title-color); transition-duration: var(--sbb-tab-title-animation-duration); transition-timing-function: var(--sbb-tab-title-animation-easing); - transition-property: inset, background-color, border-color, box-shadow; - - // Hide focus outline when focus origin is mouse or touch. This is being used in tooltip as a workaround. - :host(:focus-visible:not([data-focus-origin='mouse'], [data-focus-origin='touch'])) & { - @include sbb.focus-outline; - } - - @include sbb.if-forced-colors { - border-width: var(--sbb-border-width-2x); - } + transition-property: inset, background-color; + z-index: 1; } } .sbb-tab-title__icon { display: flex; flex-shrink: 0; + color: var(--sbb-tab-title-color); } .sbb-tab-title__text { - @include sbb.text-xs--bold; + @include sbb.text-m--bold; @include sbb.font-smoothing; @include sbb.ellipsis; @@ -155,7 +147,7 @@ } .sbb-tab-title__amount { - @include sbb.text-xs--regular; + @include sbb.text-m--regular; @include sbb.font-smoothing; display: flex; @@ -166,9 +158,5 @@ .sbb-tab-title__icon, .sbb-tab-title__text, .sbb-tab-title__amount { - transition: transform var(--sbb-tab-title-animation-duration) - var(--sbb-tab-title-animation-easing); - transform: var(--sbb-tab-title-shift); - will-change: transform; text-decoration: var(--sbb-tab-title-text-decoration); } diff --git a/src/components/sbb-tab-title/sbb-tab-title.tsx b/src/components/sbb-tab-title/sbb-tab-title.tsx index c3e1e613ca..c6b98cbc55 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.tsx +++ b/src/components/sbb-tab-title/sbb-tab-title.tsx @@ -62,21 +62,23 @@ export class SbbTabTitle { const TAGNAME = `h${Number(this.level) < 7 ? this.level : '1'}`; return ( - - {(this.iconName || this._namedSlots['icon']) && ( - - {this.iconName && } +
+ + {(this.iconName || this._namedSlots['icon']) && ( + + {this.iconName && } + + )} + + - )} - - - - {(this.amount || this._namedSlots['amount']) && ( - - {this.amount} - - )} - + {(this.amount || this._namedSlots['amount']) && ( + + {this.amount} + + )} + +
); } } From f48adfd13e33665fcf8a0fc27a507551855a9e24 Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Fri, 8 Sep 2023 11:56:07 +0200 Subject: [PATCH 2/9] feat: refactor logic for row divider --- .../sbb-tab-group/sbb-tab-group.scss | 5 --- .../sbb-tab-group/sbb-tab-group.tsx | 34 ++++++++++++++++--- .../sbb-tab-title/sbb-tab-title.scss | 20 ++++++----- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/components/sbb-tab-group/sbb-tab-group.scss b/src/components/sbb-tab-group/sbb-tab-group.scss index 303081b0f4..1f71df3812 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.scss +++ b/src/components/sbb-tab-group/sbb-tab-group.scss @@ -7,11 +7,6 @@ .tab-group { display: inline-flex; flex-wrap: wrap; - position: relative; - padding: calc(var(--sbb-focus-outline-width) + var(--sbb-focus-outline-offset)); - inset-block-start: calc((var(--sbb-focus-outline-width) + var(--sbb-focus-outline-offset)) * -1); - inset-inline-start: calc((var(--sbb-focus-outline-width) + var(--sbb-focus-outline-offset)) * -1); - overflow: hidden; } .tab-content { diff --git a/src/components/sbb-tab-group/sbb-tab-group.tsx b/src/components/sbb-tab-group/sbb-tab-group.tsx index 027cf7e505..5d25f92b5c 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.tsx +++ b/src/components/sbb-tab-group/sbb-tab-group.tsx @@ -13,7 +13,7 @@ import { } from '@stencil/core'; import { InterfaceSbbTabGroupTab } from './sbb-tab-group.custom'; import { isArrowKeyPressed, getNextElementIndex, interactivityChecker } from '../../global/a11y'; -import { isValidAttribute, hostContext } from '../../global/dom'; +import { isValidAttribute, hostContext, toggleDatasetEntry } from '../../global/dom'; import { throttle } from '../../global/eventing'; import { AgnosticMutationObserver, AgnosticResizeObserver } from '../../global/observers'; @@ -43,12 +43,16 @@ export class SbbTabGroup implements ComponentInterface { public tabs: InterfaceSbbTabGroupTab[] = []; private _selectedTab: InterfaceSbbTabGroupTab; private _isNested: boolean; + private _tabGroupElement: HTMLElement; private _tabContentElement: HTMLElement; private _tabAttributeObserver = new AgnosticMutationObserver( this._onTabAttributesChange.bind(this), ); - private _tabContentResizeObserver = new AgnosticResizeObserver( - this._onTabContentElementResize.bind(this), + private _tabGroupResizeObserver = new AgnosticResizeObserver((entries) => + this._onTabGroupElementResize(entries), + ); + private _tabContentResizeObserver = new AgnosticResizeObserver((entries) => + this._onTabContentElementResize(entries), ); @Element() private _element: HTMLElement; @@ -114,11 +118,13 @@ export class SbbTabGroup implements ComponentInterface { this.tabs = this._getTabs(); this.tabs.forEach((tab) => this._configure(tab)); this._initSelection(); + this._tabGroupResizeObserver.observe(this._tabGroupElement); } public disconnectedCallback(): void { this._tabAttributeObserver.disconnect(); this._tabContentResizeObserver.disconnect(); + this._tabGroupResizeObserver.disconnect(); } private _onContentSlotChange = (): void => { @@ -186,6 +192,26 @@ export class SbbTabGroup implements ComponentInterface { } } + private _onTabGroupElementResize(entries: ResizeObserverEntry[]): void { + for (const entry of entries) { + const tabTitles = ( + entry.target.firstElementChild as HTMLSlotElement + ).assignedElements() as HTMLSbbTabTitleElement[]; + + tabTitles.forEach((tab: HTMLSbbTabTitleElement) => { + toggleDatasetEntry( + tab, + 'hasDivider', + tab === tabTitles[0] || tab.offsetLeft === tabTitles[0].offsetLeft, + ); + this._element.style.setProperty( + '--sbb-tab-group-width', + `${this._tabGroupElement.clientWidth}px`, + ); + }); + } + } + private _onTabContentElementResize(entries: ResizeObserverEntry[]): void { for (const entry of entries) { const contentHeight = Math.floor(entry.contentRect.height); @@ -309,7 +335,7 @@ export class SbbTabGroup implements ComponentInterface { public render(): JSX.Element { return ( -
+
(this._tabGroupElement = el)}>
diff --git a/src/components/sbb-tab-title/sbb-tab-title.scss b/src/components/sbb-tab-title/sbb-tab-title.scss index 9d9e1147ac..8c4ae3cb1e 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.scss +++ b/src/components/sbb-tab-title/sbb-tab-title.scss @@ -106,15 +106,17 @@ color: var(--sbb-tab-title-icon-color); pointer-events: var(--sbb-tab-title-pointer-events); - // Show a border between flex rows when the tab titles wrap to a new line - &::after { - content: ''; - position: absolute; - inset-inline-start: 0; - inset-block-end: 0; - width: 100vw; - height: var(--sbb-border-width-1x); - background-color: var(--sbb-color-cloud-default); + // Show a border under the tab-group and between flex rows when the tab titles wrap to a new line + :host([data-has-divider]) & { + &::after { + content: ''; + position: absolute; + inset-inline-start: 0; + inset-block-end: 0; + width: var(--sbb-tab-group-width); + height: var(--sbb-border-width-1x); + background-color: var(--sbb-color-cloud-default); + } } &::before { From eb2d3eeba917bd7a796cf285e00d0f4c6af47e97 Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Fri, 8 Sep 2023 12:53:02 +0200 Subject: [PATCH 3/9] feat: add size variants --- src/components.d.ts | 10 ++++ src/components/sbb-tab-group/readme.md | 7 +-- .../sbb-tab-group/sbb-tab-group.custom.d.ts | 1 + .../sbb-tab-group/sbb-tab-group.stories.tsx | 35 ++++++++++--- .../sbb-tab-group/sbb-tab-group.tsx | 52 ++++++++++++------- .../sbb-tab-title/sbb-tab-title.scss | 20 ++++--- 6 files changed, 91 insertions(+), 34 deletions(-) diff --git a/src/components.d.ts b/src/components.d.ts index 6a88e9880b..15a92b6e3a 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -38,6 +38,7 @@ import { InterfaceSbbRadioButtonGroupAttributes } from "./components/sbb-radio-b import { SelectChange } from "./components/sbb-select/sbb-select.custom"; import { InterfaceSbbSelectionPanelAttributes } from "./components/sbb-selection-panel/sbb-selection-panel.custom"; import { InterfaceSignetAttributes } from "./components/sbb-signet/sbb-signet.custom"; +import { InterfaceSbbTabGroupTab } from "./components/sbb-tab-group/sbb-tab-group.custom"; import { TagStateChange } from "./components/sbb-tag/sbb-tag.custom"; import { InterfaceTimetableParkAndRailAttributes } from "./components/sbb-timetable-park-and-rail/sbb-timetable-park-and-rail.custom"; import { Boarding, Price } from "./components/sbb-timetable-row/sbb-timetable-row.custom"; @@ -84,6 +85,7 @@ export { InterfaceSbbRadioButtonGroupAttributes } from "./components/sbb-radio-b export { SelectChange } from "./components/sbb-select/sbb-select.custom"; export { InterfaceSbbSelectionPanelAttributes } from "./components/sbb-selection-panel/sbb-selection-panel.custom"; export { InterfaceSignetAttributes } from "./components/sbb-signet/sbb-signet.custom"; +export { InterfaceSbbTabGroupTab } from "./components/sbb-tab-group/sbb-tab-group.custom"; export { TagStateChange } from "./components/sbb-tag/sbb-tag.custom"; export { InterfaceTimetableParkAndRailAttributes } from "./components/sbb-timetable-park-and-rail/sbb-timetable-park-and-rail.custom"; export { Boarding, Price } from "./components/sbb-timetable-row/sbb-timetable-row.custom"; @@ -1554,6 +1556,10 @@ export namespace Components { * Sets the initial tab. If it matches a disabled tab or exceeds the length of the tab group, the first enabled tab will be selected. */ "initialSelectedIndex": number; + /** + * Size variant, either l or xl. + */ + "size": InterfaceSbbTabGroupTab['size']; } interface SbbTabTitle { /** @@ -4394,6 +4400,10 @@ declare namespace LocalJSX { * Emits an event on selected tab change */ "onDid-change"?: (event: SbbTabGroupCustomEvent) => void; + /** + * Size variant, either l or xl. + */ + "size"?: InterfaceSbbTabGroupTab['size']; } interface SbbTabTitle { /** diff --git a/src/components/sbb-tab-group/readme.md b/src/components/sbb-tab-group/readme.md index bc664afea0..dec14047ee 100644 --- a/src/components/sbb-tab-group/readme.md +++ b/src/components/sbb-tab-group/readme.md @@ -46,9 +46,10 @@ or using the `amount` slot of the `sbb-tab-title`. ## Properties -| Property | Attribute | Description | Type | Default | -| ---------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -| `initialSelectedIndex` | `initial-selected-index` | Sets the initial tab. If it matches a disabled tab or exceeds the length of the tab group, the first enabled tab will be selected. | `number` | `0` | +| Property | Attribute | Description | Type | Default | +| ---------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------- | +| `initialSelectedIndex` | `initial-selected-index` | Sets the initial tab. If it matches a disabled tab or exceeds the length of the tab group, the first enabled tab will be selected. | `number` | `0` | +| `size` | `size` | Size variant, either l or xl. | `"l" \| "xl"` | `'l'` | ## Events diff --git a/src/components/sbb-tab-group/sbb-tab-group.custom.d.ts b/src/components/sbb-tab-group/sbb-tab-group.custom.d.ts index 5dc3f7a70c..2d445617d6 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.custom.d.ts +++ b/src/components/sbb-tab-group/sbb-tab-group.custom.d.ts @@ -13,4 +13,5 @@ export interface InterfaceSbbTabGroupTab extends HTMLStencilElement { relatedContent?: HTMLElement; index?: number; tabGroupActions?: InterfaceSbbTabGroupActions; + size: 'l' | 'xl'; } diff --git a/src/components/sbb-tab-group/sbb-tab-group.stories.tsx b/src/components/sbb-tab-group/sbb-tab-group.stories.tsx index 5c8df1d2ca..3b4bbd90f6 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.stories.tsx +++ b/src/components/sbb-tab-group/sbb-tab-group.stories.tsx @@ -41,7 +41,7 @@ const tabPanelFour = (): JSX.Element => ( ); const DefaultTemplate = (args): JSX.Element => ( - + {firstTabTitle(args)} {tabPanelOne()} @@ -57,7 +57,7 @@ const DefaultTemplate = (args): JSX.Element => ( ); const IconsAndNumbersTemplate = (args): JSX.Element => ( - + {firstTabTitle(args)} {tabPanelOne()} @@ -79,9 +79,9 @@ const IconsAndNumbersTemplate = (args): JSX.Element => ( ); const NestedTemplate = (args): JSX.Element => ( - + {firstTabTitle(args)} - + Nested tab
Diam maecenas ultricies mi eget mauris pharetra et ultrices neque ornare aenean euismod @@ -143,16 +143,25 @@ const amount: InputType = { }, }; +const size: InputType = { + control: { + type: 'inline-radio', + }, + options: ['l', 'xl'], +}; + const basicArgTypes: ArgTypes = { label, 'icon-name': iconName, amount: amount, + size: size, }; const basicArgs: Args = { label: 'Tab label one', 'icon-name': undefined, amount: undefined, + size: size.options[0], }; const templateRes = [ @@ -164,20 +173,34 @@ const templateRes = [ withActions as Decorator, ]; -export const defaultTabs: StoryObj = { +export const defaultTabsSizeL: StoryObj = { render: DefaultTemplate, argTypes: basicArgTypes, args: { ...basicArgs }, decorators: templateRes, }; -export const numbersAndIcons: StoryObj = { +export const numbersAndIconsSizeL: StoryObj = { render: IconsAndNumbersTemplate, argTypes: basicArgTypes, args: { ...basicArgs, amount: 16, 'icon-name': iconName.options[0] }, decorators: templateRes, }; +export const defaultTabsSizeXL: StoryObj = { + render: DefaultTemplate, + argTypes: basicArgTypes, + args: { ...basicArgs, size: size.options[1] }, + decorators: templateRes, +}; + +export const numbersAndIconsSizeXL: StoryObj = { + render: IconsAndNumbersTemplate, + argTypes: basicArgTypes, + args: { ...basicArgs, amount: 16, 'icon-name': iconName.options[0], size: size.options[1] }, + decorators: templateRes, +}; + export const nestedTabGroups: StoryObj = { render: NestedTemplate, argTypes: basicArgTypes, diff --git a/src/components/sbb-tab-group/sbb-tab-group.tsx b/src/components/sbb-tab-group/sbb-tab-group.tsx index 5d25f92b5c..e7e4cffaa2 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.tsx +++ b/src/components/sbb-tab-group/sbb-tab-group.tsx @@ -10,6 +10,7 @@ import { Listen, Method, Prop, + Watch, } from '@stencil/core'; import { InterfaceSbbTabGroupTab } from './sbb-tab-group.custom'; import { isArrowKeyPressed, getNextElementIndex, interactivityChecker } from '../../global/a11y'; @@ -40,13 +41,13 @@ let nextId = 0; tag: 'sbb-tab-group', }) export class SbbTabGroup implements ComponentInterface { - public tabs: InterfaceSbbTabGroupTab[] = []; + private _tabs: InterfaceSbbTabGroupTab[] = []; private _selectedTab: InterfaceSbbTabGroupTab; private _isNested: boolean; private _tabGroupElement: HTMLElement; private _tabContentElement: HTMLElement; - private _tabAttributeObserver = new AgnosticMutationObserver( - this._onTabAttributesChange.bind(this), + private _tabAttributeObserver = new AgnosticMutationObserver((mutationsList) => + this._onTabAttributesChange(mutationsList), ); private _tabGroupResizeObserver = new AgnosticResizeObserver((entries) => this._onTabGroupElementResize(entries), @@ -57,12 +58,24 @@ export class SbbTabGroup implements ComponentInterface { @Element() private _element: HTMLElement; + /** + * Size variant, either l or xl. + */ + @Prop() public size: InterfaceSbbTabGroupTab['size'] = 'l'; + /** * Sets the initial tab. If it matches a disabled tab or exceeds the length of * the tab group, the first enabled tab will be selected. */ @Prop() public initialSelectedIndex = 0; + @Watch('size') + public updateSize(): void { + for (const tab of this._tabs) { + tab.setAttribute('data-size', this.size); + } + } + /** * Emits an event on selected tab change */ @@ -77,7 +90,7 @@ export class SbbTabGroup implements ComponentInterface { */ @Method() public async disableTab(tabIndex: number): Promise { - this.tabs[tabIndex]?.tabGroupActions.disable(); + this._tabs[tabIndex]?.tabGroupActions.disable(); } /** @@ -86,7 +99,7 @@ export class SbbTabGroup implements ComponentInterface { */ @Method() public async enableTab(tabIndex: number): Promise { - this.tabs[tabIndex]?.tabGroupActions.enable(); + this._tabs[tabIndex]?.tabGroupActions.enable(); } /** @@ -95,7 +108,7 @@ export class SbbTabGroup implements ComponentInterface { */ @Method() public async activateTab(tabIndex: number): Promise { - this.tabs[tabIndex]?.tabGroupActions.select(); + this._tabs[tabIndex]?.tabGroupActions.select(); } private _getTabs(): InterfaceSbbTabGroupTab[] { @@ -105,7 +118,7 @@ export class SbbTabGroup implements ComponentInterface { } private get _enabledTabs(): InterfaceSbbTabGroupTab[] { - return this.tabs.filter( + return this._tabs.filter( (t) => !isValidAttribute(t, 'disabled') && interactivityChecker.isVisible(t), ); } @@ -115,8 +128,8 @@ export class SbbTabGroup implements ComponentInterface { } public componentDidLoad(): void { - this.tabs = this._getTabs(); - this.tabs.forEach((tab) => this._configure(tab)); + this._tabs = this._getTabs(); + this._tabs.forEach((tab) => this._configure(tab)); this._initSelection(); this._tabGroupResizeObserver.observe(this._tabGroupElement); } @@ -129,12 +142,12 @@ export class SbbTabGroup implements ComponentInterface { private _onContentSlotChange = (): void => { this._tabContentElement = this._element.shadowRoot.querySelector('div.tab-content'); - const loadedTabs = this._getTabs().filter((tab) => !this.tabs.includes(tab)); + const loadedTabs = this._getTabs().filter((tab) => !this._tabs.includes(tab)); // if a new tab/content is added to the tab group if (loadedTabs.length) { loadedTabs.forEach((tab) => this._configure(tab)); - this.tabs = this.tabs.concat(loadedTabs); + this._tabs = this._tabs.concat(loadedTabs); } }; @@ -142,14 +155,15 @@ export class SbbTabGroup implements ComponentInterface { const tabs = this._getTabs(); // if a tab is removed from the tab group - if (tabs.length < this.tabs.length) { - const removedTabs = this.tabs.filter((tab) => !tabs.includes(tab)); + if (tabs.length < this._tabs.length) { + const removedTabs = this._tabs.filter((tab) => !tabs.includes(tab)); removedTabs.forEach((removedTab) => { removedTab.relatedContent?.remove(); }); - this.tabs = tabs; + this._tabs = tabs; } + this._tabs.forEach((tab: HTMLSbbTabTitleElement) => tab.setAttribute('data-size', this.size)); }; private _assignId(): string { @@ -159,10 +173,10 @@ export class SbbTabGroup implements ComponentInterface { private _initSelection(): void { if ( this.initialSelectedIndex >= 0 && - this.initialSelectedIndex < this.tabs.length && - !this.tabs[this.initialSelectedIndex].disabled + this.initialSelectedIndex < this._tabs.length && + !this._tabs[this.initialSelectedIndex].disabled ) { - this.tabs[this.initialSelectedIndex].tabGroupActions.select(); + this._tabs[this.initialSelectedIndex].tabGroupActions.select(); } else { this._enabledTabs[0]?.tabGroupActions.select(); } @@ -198,7 +212,7 @@ export class SbbTabGroup implements ComponentInterface { entry.target.firstElementChild as HTMLSlotElement ).assignedElements() as HTMLSbbTabTitleElement[]; - tabTitles.forEach((tab: HTMLSbbTabTitleElement) => { + for (const tab of tabTitles) { toggleDatasetEntry( tab, 'hasDivider', @@ -208,7 +222,7 @@ export class SbbTabGroup implements ComponentInterface { '--sbb-tab-group-width', `${this._tabGroupElement.clientWidth}px`, ); - }); + } } } diff --git a/src/components/sbb-tab-title/sbb-tab-title.scss b/src/components/sbb-tab-title/sbb-tab-title.scss index 8c4ae3cb1e..2ee6ae95d1 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.scss +++ b/src/components/sbb-tab-title/sbb-tab-title.scss @@ -133,6 +133,12 @@ } } +.sbb-tab-title__icon, +.sbb-tab-title__text, +.sbb-tab-title__amount { + text-decoration: var(--sbb-tab-title-text-decoration); +} + .sbb-tab-title__icon { display: flex; flex-shrink: 0; @@ -144,6 +150,10 @@ @include sbb.font-smoothing; @include sbb.ellipsis; + :host([data-size='xl']) & { + @include sbb.text-xl--bold; + } + color: var(--sbb-tab-title-color); transition: color var(--sbb-tab-title-animation-duration) var(--sbb-tab-title-animation-easing); } @@ -152,13 +162,11 @@ @include sbb.text-m--regular; @include sbb.font-smoothing; + :host([data-size='xl']) & { + @include sbb.text-xl--regular; + } + display: flex; color: var(--sbb-tab-title-amount-color); transition: color var(--sbb-tab-title-animation-duration) var(--sbb-tab-title-animation-easing); } - -.sbb-tab-title__icon, -.sbb-tab-title__text, -.sbb-tab-title__amount { - text-decoration: var(--sbb-tab-title-text-decoration); -} From 075713102ee4f4314144465fa419e01472255c1d Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Fri, 8 Sep 2023 13:00:26 +0200 Subject: [PATCH 4/9] feat: improve animation --- src/components/sbb-tab-title/sbb-tab-title.scss | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/sbb-tab-title/sbb-tab-title.scss b/src/components/sbb-tab-title/sbb-tab-title.scss index 2ee6ae95d1..a5f925b509 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.scss +++ b/src/components/sbb-tab-title/sbb-tab-title.scss @@ -12,7 +12,7 @@ --sbb-tab-title-cursor: pointer; --sbb-tab-title-pointer-events: unset; --sbb-tab-title-inset: 0; - --sbb-tab-title-marker-inset-inline: 50%; + --sbb-tab-title-marker-transform: scale(0); --sbb-tab-title-text-decoration: none; --sbb-tab-title-animation-duration: var(--sbb-animation-duration-2x); --sbb-tab-title-animation-easing: var(--sbb-animation-easing); @@ -54,7 +54,7 @@ --sbb-tab-title-background-color: var(--sbb-color-black-default); --sbb-tab-title-cursor: unset; --sbb-tab-title-pointer-events: none; - --sbb-tab-title-marker-inset-inline: 0; + --sbb-tab-title-marker-transform: scale(1); @include sbb.if-forced-colors { --sbb-tab-title-color: ButtonText; @@ -65,7 +65,7 @@ :host(:hover:not([disabled]:not([disabled='false']))) { @include sbb.hover-mq($hover: true) { - --sbb-tab-title-marker-inset-inline: 0; + --sbb-tab-title-marker-transform: scale(1); } } @@ -122,13 +122,14 @@ &::before { position: absolute; content: ''; - inset-inline: var(--sbb-tab-title-marker-inset-inline); + inset-inline: 0; inset-block-end: 0; height: #{sbb.px-to-rem-build(3)}; background-color: var(--sbb-tab-title-color); + transform: var(--sbb-tab-title-marker-transform); transition-duration: var(--sbb-tab-title-animation-duration); transition-timing-function: var(--sbb-tab-title-animation-easing); - transition-property: inset, background-color; + transition-property: transform, background-color; z-index: 1; } } From cad21cc84c4ea264a11db8a5209e19d3ade07ce3 Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Fri, 8 Sep 2023 14:37:58 +0200 Subject: [PATCH 5/9] fix: tests --- .../sbb-tab-title/sbb-tab-title.spec.ts | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/components/sbb-tab-title/sbb-tab-title.spec.ts b/src/components/sbb-tab-title/sbb-tab-title.spec.ts index 620cae0755..b68745c04b 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.spec.ts +++ b/src/components/sbb-tab-title/sbb-tab-title.spec.ts @@ -11,11 +11,13 @@ describe('sbb-tab-title', () => { expect(root).toEqualHtml(` -

- - - -

+
+

+ + + +

+
`); @@ -28,18 +30,20 @@ describe('sbb-tab-title', () => { }); expect(root).toEqualHtml(` - - -

- - - - - - -

-
-
+ + +
+

+ + + + + + +

+
+
+
`); }); @@ -52,14 +56,16 @@ describe('sbb-tab-title', () => { expect(root).toEqualHtml(` -

- - - - - 78 - -

+
+

+ + + + + 78 + +

+
`); From 04c7159cb009c90d5f04c28abe08aa8deed0640d Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Fri, 8 Sep 2023 15:42:32 +0200 Subject: [PATCH 6/9] fix: pointer events on disabled tab titles --- src/components/sbb-tab-title/sbb-tab-title.scss | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/sbb-tab-title/sbb-tab-title.scss b/src/components/sbb-tab-title/sbb-tab-title.scss index a5f925b509..7e4bd1a157 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.scss +++ b/src/components/sbb-tab-title/sbb-tab-title.scss @@ -20,6 +20,7 @@ display: inline-block; max-width: 100%; + pointer-events: var(--sbb-tab-title-pointer-events); -webkit-tap-highlight-color: transparent; // Use !important here to not interfere with Firefox focus ring definition @@ -104,7 +105,6 @@ cursor: var(--sbb-tab-title-cursor); transition: color var(--sbb-tab-title-animation-duration) var(--sbb-tab-title-animation-easing); color: var(--sbb-tab-title-icon-color); - pointer-events: var(--sbb-tab-title-pointer-events); // Show a border under the tab-group and between flex rows when the tab titles wrap to a new line :host([data-has-divider]) & { @@ -127,9 +127,12 @@ height: #{sbb.px-to-rem-build(3)}; background-color: var(--sbb-tab-title-color); transform: var(--sbb-tab-title-marker-transform); - transition-duration: var(--sbb-tab-title-animation-duration); - transition-timing-function: var(--sbb-tab-title-animation-easing); - transition-property: transform, background-color; + transition: { + duration: var(--sbb-tab-title-animation-duration); + timing-function: var(--sbb-tab-title-animation-easing); + property: transform, background-color; + } + z-index: 1; } } @@ -144,6 +147,7 @@ display: flex; flex-shrink: 0; color: var(--sbb-tab-title-color); + transition: color var(--sbb-tab-title-animation-duration) var(--sbb-tab-title-animation-easing); } .sbb-tab-title__text { From 8642d85c07332d6e541c12b2bade6c63db627c5b Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Mon, 18 Sep 2023 12:13:06 +0200 Subject: [PATCH 7/9] fix: ui review --- src/components/sbb-tab-title/sbb-tab-title.scss | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/sbb-tab-title/sbb-tab-title.scss b/src/components/sbb-tab-title/sbb-tab-title.scss index 7e4bd1a157..5a9272a44d 100644 --- a/src/components/sbb-tab-title/sbb-tab-title.scss +++ b/src/components/sbb-tab-title/sbb-tab-title.scss @@ -5,7 +5,7 @@ @include sbb.host-component-properties; :host { - --sbb-tab-title-height: #{sbb.px-to-rem-build(40)}; + --sbb-tab-title-height: var(--sbb-spacing-fixed-12x); --sbb-tab-title-color: var(--sbb-color-granite-default); --sbb-tab-title-icon-color: var(--sbb-color-black-default); --sbb-tab-title-background-color: var(--sbb-color-white-default); @@ -27,6 +27,10 @@ // which appears in normalize css of several frameworks. outline: none !important; + @include sbb.mq($from: medium) { + --sbb-tab-title-height: var(--sbb-spacing-fixed-14x); + } + @include sbb.if-forced-colors { --sbb-tab-title-color: ButtonText; --sbb-tab-title-icon-color: ButtonText; @@ -98,7 +102,6 @@ min-height: var(--sbb-tab-title-height); display: flex; align-items: center; - padding-block: var(--sbb-spacing-fixed-2x); padding-inline: var(--sbb-spacing-responsive-xs); gap: var(--sbb-spacing-fixed-2x); user-select: none; From 095721ecb77fbe21225316f1027ca0def4a24cdd Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Fri, 22 Sep 2023 10:31:17 +0200 Subject: [PATCH 8/9] fix: review --- .../sbb-tab-group/sbb-tab-group.stories.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/sbb-tab-group/sbb-tab-group.stories.tsx b/src/components/sbb-tab-group/sbb-tab-group.stories.tsx index 3b4bbd90f6..5ac5f0c30b 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.stories.tsx +++ b/src/components/sbb-tab-group/sbb-tab-group.stories.tsx @@ -6,7 +6,7 @@ import { withActions } from '@storybook/addon-actions/decorator'; import type { Meta, StoryObj, ArgTypes, Args, Decorator } from '@storybook/html'; import type { InputType } from '@storybook/types'; -const firstTabTitle = ({ label, ...args }): JSX.Element => ( +const firstTabTitle = (label, args): JSX.Element => ( {label} ); @@ -40,9 +40,9 @@ const tabPanelFour = (): JSX.Element => ( ); -const DefaultTemplate = (args): JSX.Element => ( - - {firstTabTitle(args)} +const DefaultTemplate = ({ size, label, ...args }): JSX.Element => ( + + {firstTabTitle(label, args)} {tabPanelOne()} Tab title two @@ -56,9 +56,9 @@ const DefaultTemplate = (args): JSX.Element => ( ); -const IconsAndNumbersTemplate = (args): JSX.Element => ( - - {firstTabTitle(args)} +const IconsAndNumbersTemplate = ({ size, label, ...args }): JSX.Element => ( + + {firstTabTitle(label, args)} {tabPanelOne()} @@ -78,10 +78,10 @@ const IconsAndNumbersTemplate = (args): JSX.Element => ( ); -const NestedTemplate = (args): JSX.Element => ( - - {firstTabTitle(args)} - +const NestedTemplate = ({ size, label, ...args }): JSX.Element => ( + + {firstTabTitle(label, args)} + Nested tab
Diam maecenas ultricies mi eget mauris pharetra et ultrices neque ornare aenean euismod From bd2668c5f988e746b7f8076384359240302ac51d Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Wed, 27 Sep 2023 17:44:08 +0200 Subject: [PATCH 9/9] fix: use flex for tab group --- src/components/sbb-tab-group/sbb-tab-group.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/sbb-tab-group/sbb-tab-group.scss b/src/components/sbb-tab-group/sbb-tab-group.scss index 1f71df3812..cc5a8b3e6b 100644 --- a/src/components/sbb-tab-group/sbb-tab-group.scss +++ b/src/components/sbb-tab-group/sbb-tab-group.scss @@ -5,7 +5,7 @@ @include sbb.host-component-properties; .tab-group { - display: inline-flex; + display: flex; flex-wrap: wrap; }