From d81a565d776499472b91d9c82b99511034985d8e Mon Sep 17 00:00:00 2001 From: Tommaso Menga Date: Mon, 12 Aug 2024 14:15:03 +0200 Subject: [PATCH] feat(sbb-link-list-anchor): component implementation (#2987) --- src/elements/core/styles/mixins/link.scss | 2 +- src/elements/link-list.ts | 2 + src/elements/link-list/common.ts | 3 + .../link-list/common/link-list-base.scss | 23 +++ .../link-list/common/link-list-base.ts | 83 ++++++++++ src/elements/link-list/link-list-anchor.ts | 1 + .../link-list-anchor.snapshot.spec.snap.js | 152 ++++++++++++++++++ .../link-list/link-list-anchor/index.ts | 1 + .../link-list-anchor/link-list-anchor.scss | 53 ++++++ .../link-list-anchor.snapshot.spec.ts | 38 +++++ .../link-list-anchor/link-list-anchor.spec.ts | 33 ++++ .../link-list-anchor.ssr.spec.ts | 27 ++++ .../link-list-anchor.stories.ts | 147 +++++++++++++++++ .../link-list-anchor/link-list-anchor.ts | 24 +++ .../link-list-anchor.visual.spec.ts | 86 ++++++++++ .../link-list/link-list-anchor/readme.md | 53 ++++++ src/elements/link-list/link-list.ts | 99 +----------- .../link-list.snapshot.spec.snap.js | 0 .../link-list/{ => link-list}/link-list.scss | 22 +-- .../link-list.snapshot.spec.ts | 4 +- .../{ => link-list}/link-list.spec.ts | 6 +- .../{ => link-list}/link-list.ssr.spec.ts | 6 +- .../{ => link-list}/link-list.stories.ts | 19 ++- src/elements/link-list/link-list/link-list.ts | 32 ++++ .../{ => link-list}/link-list.visual.spec.ts | 4 +- .../link-list/{ => link-list}/readme.md | 2 +- .../fullscreen-diff/fullscreen-diff.scss | 4 +- 27 files changed, 788 insertions(+), 138 deletions(-) create mode 100644 src/elements/link-list/common.ts create mode 100644 src/elements/link-list/common/link-list-base.scss create mode 100644 src/elements/link-list/common/link-list-base.ts create mode 100644 src/elements/link-list/link-list-anchor.ts create mode 100644 src/elements/link-list/link-list-anchor/__snapshots__/link-list-anchor.snapshot.spec.snap.js create mode 100644 src/elements/link-list/link-list-anchor/index.ts create mode 100644 src/elements/link-list/link-list-anchor/link-list-anchor.scss create mode 100644 src/elements/link-list/link-list-anchor/link-list-anchor.snapshot.spec.ts create mode 100644 src/elements/link-list/link-list-anchor/link-list-anchor.spec.ts create mode 100644 src/elements/link-list/link-list-anchor/link-list-anchor.ssr.spec.ts create mode 100644 src/elements/link-list/link-list-anchor/link-list-anchor.stories.ts create mode 100644 src/elements/link-list/link-list-anchor/link-list-anchor.ts create mode 100644 src/elements/link-list/link-list-anchor/link-list-anchor.visual.spec.ts create mode 100644 src/elements/link-list/link-list-anchor/readme.md rename src/elements/link-list/{ => link-list}/__snapshots__/link-list.snapshot.spec.snap.js (100%) rename src/elements/link-list/{ => link-list}/link-list.scss (61%) rename src/elements/link-list/{ => link-list}/link-list.snapshot.spec.ts (94%) rename src/elements/link-list/{ => link-list}/link-list.spec.ts (93%) rename src/elements/link-list/{ => link-list}/link-list.ssr.spec.ts (91%) rename src/elements/link-list/{ => link-list}/link-list.stories.ts (91%) create mode 100644 src/elements/link-list/link-list/link-list.ts rename src/elements/link-list/{ => link-list}/link-list.visual.spec.ts (95%) rename src/elements/link-list/{ => link-list}/readme.md (98%) diff --git a/src/elements/core/styles/mixins/link.scss b/src/elements/core/styles/mixins/link.scss index 57d257b2a5..8cc67a7096 100644 --- a/src/elements/core/styles/mixins/link.scss +++ b/src/elements/core/styles/mixins/link.scss @@ -57,7 +57,7 @@ @mixin link-hover-rules { @include mediaqueries.hover-mq($hover: true) { color: var(--sbb-link-color-hover); - text-decoration: underline; + text-decoration: var(--sbb-link-hover-text-decoration, underline); } } diff --git a/src/elements/link-list.ts b/src/elements/link-list.ts index 49a1597610..281b0decf6 100644 --- a/src/elements/link-list.ts +++ b/src/elements/link-list.ts @@ -1 +1,3 @@ export * from './link-list/link-list.js'; +export * from './link-list/link-list-anchor.js'; +export * from './link-list/common.js'; diff --git a/src/elements/link-list/common.ts b/src/elements/link-list/common.ts new file mode 100644 index 0000000000..13886eba28 --- /dev/null +++ b/src/elements/link-list/common.ts @@ -0,0 +1,3 @@ +export * from './common/link-list-base.js'; + +export { default as linkListBaseStyle } from './common/link-list-base.scss?lit&inline'; diff --git a/src/elements/link-list/common/link-list-base.scss b/src/elements/link-list/common/link-list-base.scss new file mode 100644 index 0000000000..49f193501e --- /dev/null +++ b/src/elements/link-list/common/link-list-base.scss @@ -0,0 +1,23 @@ +@use '../../core/styles' as sbb; + +// Box-sizing rules contained in typography are not traversing Shadow DOM boundaries. We need to include box-sizing mixin in every component. +@include sbb.box-sizing; + +:host { + display: block; +} + +.sbb-link-list-wrapper { + display: flex; + flex-direction: column; + gap: var(--sbb-spacing-fixed-3x); +} + +.sbb-link-list-title { + // Overwrite sbb-title default margin + margin: 0; + + :host(:not([data-slot-names~='title'], [title-content])) & { + display: none; + } +} diff --git a/src/elements/link-list/common/link-list-base.ts b/src/elements/link-list/common/link-list-base.ts new file mode 100644 index 0000000000..7fd38f317a --- /dev/null +++ b/src/elements/link-list/common/link-list-base.ts @@ -0,0 +1,83 @@ +import type { PropertyValues, TemplateResult } from 'lit'; +import { html, LitElement, nothing } from 'lit'; +import { property } from 'lit/decorators.js'; + +import { slotState } from '../../core/decorators.js'; +import { + SbbNamedSlotListMixin, + SbbNegativeMixin, + type WithListChildren, +} from '../../core/mixins.js'; +import type { + SbbBlockLinkButtonElement, + SbbBlockLinkElement, + SbbBlockLinkStaticElement, + SbbLinkSize, +} from '../../link.js'; +import type { SbbTitleLevel } from '../../title.js'; + +import '../../title.js'; + +/** + * It displays a list of `sbb-block-link`. + * + * @slot - Use the unnamed slot to add one or more `sbb-block-link`. + * @slot title - Use this slot to provide a title. + */ +@slotState() +export class SbbLinkListBaseElement extends SbbNegativeMixin( + SbbNamedSlotListMixin< + SbbBlockLinkElement | SbbBlockLinkButtonElement | SbbBlockLinkStaticElement, + typeof LitElement + >(LitElement), +) { + protected override readonly listChildLocalNames = [ + 'sbb-block-link', + 'sbb-block-link-button', + 'sbb-block-link-static', + ]; + + /** The title text we want to show before the list. */ + @property({ attribute: 'title-content', reflect: true }) public titleContent?: string; + + /** The semantic level of the title, e.g. 2 = h2. */ + @property({ attribute: 'title-level' }) public titleLevel: SbbTitleLevel = '2'; + + /** + * Text size of the nested sbb-block-link instances. + * This will overwrite the size attribute of nested sbb-block-link instances. + */ + @property({ reflect: true }) public size: SbbLinkSize = 's'; + + protected override willUpdate(changedProperties: PropertyValues>): void { + super.willUpdate(changedProperties); + + if ( + changedProperties.has('size') || + changedProperties.has('negative') || + changedProperties.has('listChildren') + ) { + for (const link of this.listChildren) { + link.negative = this.negative; + link.size = this.size; + } + } + } + + protected override render(): TemplateResult { + return html` + + `; + } +} diff --git a/src/elements/link-list/link-list-anchor.ts b/src/elements/link-list/link-list-anchor.ts new file mode 100644 index 0000000000..3d47fccf30 --- /dev/null +++ b/src/elements/link-list/link-list-anchor.ts @@ -0,0 +1 @@ +export * from './link-list-anchor/link-list-anchor.js'; diff --git a/src/elements/link-list/link-list-anchor/__snapshots__/link-list-anchor.snapshot.spec.snap.js b/src/elements/link-list/link-list-anchor/__snapshots__/link-list-anchor.snapshot.spec.snap.js new file mode 100644 index 0000000000..ed3bbd9eb1 --- /dev/null +++ b/src/elements/link-list/link-list-anchor/__snapshots__/link-list-anchor.snapshot.spec.snap.js @@ -0,0 +1,152 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["sbb-link-list-anchor renders DOM"] = +` + + Link 0 + + + Link 1 + + + Link 2 + + +`; +/* end snapshot sbb-link-list-anchor renders DOM */ + +snapshots["sbb-link-list-anchor renders Shadow DOM"] = +` +`; +/* end snapshot sbb-link-list-anchor renders Shadow DOM */ + +snapshots["sbb-link-list-anchor renders A11y tree Chrome"] = +`

+ { + "role": "WebArea", + "name": "", + "children": [ + { + "role": "heading", + "name": "title", + "level": 2 + }, + { + "role": "link", + "name": "Link 0" + }, + { + "role": "link", + "name": "Link 1" + }, + { + "role": "link", + "name": "Link 2" + } + ] +} +

+`; +/* end snapshot sbb-link-list-anchor renders A11y tree Chrome */ + +snapshots["sbb-link-list-anchor renders A11y tree Firefox"] = +`

+ { + "role": "document", + "name": "", + "children": [ + { + "role": "heading", + "name": "title", + "level": 2 + }, + { + "role": "link", + "name": "Link 0", + "value": "https://www.sbb.ch/" + }, + { + "role": "link", + "name": "Link 1", + "value": "https://www.sbb.ch/" + }, + { + "role": "link", + "name": "Link 2", + "value": "https://www.sbb.ch/" + } + ] +} +

+`; +/* end snapshot sbb-link-list-anchor renders A11y tree Firefox */ + diff --git a/src/elements/link-list/link-list-anchor/index.ts b/src/elements/link-list/link-list-anchor/index.ts new file mode 100644 index 0000000000..cc5dc88a50 --- /dev/null +++ b/src/elements/link-list/link-list-anchor/index.ts @@ -0,0 +1 @@ +export * from './link-list-anchor.js'; diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.scss b/src/elements/link-list/link-list-anchor/link-list-anchor.scss new file mode 100644 index 0000000000..ab7d7796cd --- /dev/null +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.scss @@ -0,0 +1,53 @@ +@use '../../core/styles/index' as sbb; + +:host { + --sbb-link-list-anchor-border-color: var(--sbb-color-cloud); +} + +:host([negative]) { + --sbb-link-list-anchor-border-color: var(--sbb-color-granite); +} + +::slotted([data-link]) { + --sbb-link-text-decoration: none; + --sbb-link-hover-text-decoration: none; + --sbb-link-padding: var(--sbb-spacing-fixed-1x) 0 var(--sbb-spacing-fixed-1x) + var(--sbb-spacing-fixed-4x); + --sbb-link-color-hover: var(--sbb-color-charcoal); + --sbb-link-color-active: var(--sbb-color-granite); + + border-inline-start: var(--sbb-border-width-1x) solid var(--sbb-link-list-anchor-border-color); + + :host([negative]) & { + --sbb-link-color-normal: var(--sbb-color-smoke); + --sbb-link-color-hover: var(--sbb-color-milk); + --sbb-link-color-active: var(--sbb-color-smoke); + } +} + +::slotted([data-link]:is(:active, [data-active]):not([disabled])) { + --sbb-link-list-anchor-border-color: var(--sbb-color-iron); + + :host([negative]) & { + --sbb-link-list-anchor-border-color: var(--sbb-color-silver); + } +} + +::slotted([data-link]:hover:not([disabled])) { + --sbb-link-list-anchor-border-color: var(--sbb-color-charcoal); + + :host([negative]) & { + --sbb-link-list-anchor-border-color: var(--sbb-color-milk); + } +} + +.sbb-link-list-anchor { + @include sbb.list-reset; + + display: flex; + flex-flow: column; + + > li { + margin-inline-start: var(--sbb-spacing-fixed-4x); + } +} diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.snapshot.spec.ts b/src/elements/link-list/link-list-anchor/link-list-anchor.snapshot.spec.ts new file mode 100644 index 0000000000..0b3f8af0bf --- /dev/null +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.snapshot.spec.ts @@ -0,0 +1,38 @@ +import { expect } from '@open-wc/testing'; +import { html } from 'lit/static-html.js'; + +import { fixture, testA11yTreeSnapshot } from '../../core/testing/private.js'; + +import type { SbbLinkListAnchorElement } from './link-list-anchor.js'; +import './link-list-anchor.js'; +import '../../link/block-link.js'; + +describe(`sbb-link-list-anchor`, () => { + describe('renders', () => { + let element: SbbLinkListAnchorElement; + + beforeEach(async () => { + element = await fixture(html` + + ${new Array(3) + .fill('') + .map( + (_v, i) => html` + Link ${i} + `, + )} + + `); + }); + + it('DOM', async () => { + await expect(element).dom.to.be.equalSnapshot(); + }); + + it('Shadow DOM', async () => { + await expect(element).shadowDom.to.be.equalSnapshot(); + }); + + testA11yTreeSnapshot(); + }); +}); diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.spec.ts b/src/elements/link-list/link-list-anchor/link-list-anchor.spec.ts new file mode 100644 index 0000000000..27045b070b --- /dev/null +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.spec.ts @@ -0,0 +1,33 @@ +import { assert, expect } from '@open-wc/testing'; +import { html } from 'lit/static-html.js'; + +import { fixture } from '../../core/testing/private.js'; +import { waitForLitRender } from '../../core/testing.js'; + +import { SbbLinkListAnchorElement } from './link-list-anchor.js'; +import '../../link/block-link.js'; + +describe('sbb-link-list-anchor', () => { + let element: SbbLinkListAnchorElement; + + beforeEach(async () => { + element = await fixture(html` + + ${new Array(3) + .fill('') + .map((_v, i) => html` Link ${i} `)} + + `); + }); + + it('renders', async () => { + assert.instanceOf(element, SbbLinkListAnchorElement); + }); + + it('should sync negative', async () => { + element.toggleAttribute('negative', true); + await waitForLitRender(element); + const links = Array.from(element.querySelectorAll('sbb-block-link')); + expect(links.every((l) => l.negative)).to.be.true; + }); +}); diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.ssr.spec.ts b/src/elements/link-list/link-list-anchor/link-list-anchor.ssr.spec.ts new file mode 100644 index 0000000000..04f6d65d2f --- /dev/null +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.ssr.spec.ts @@ -0,0 +1,27 @@ +import { assert } from '@open-wc/testing'; +import { html } from 'lit/static-html.js'; + +import { ssrHydratedFixture } from '../../core/testing/private.js'; + +import { SbbLinkListAnchorElement } from './link-list-anchor.js'; +import '../../link.js'; + +describe(`sbb-link-list-anchor ssr`, () => { + let root: SbbLinkListAnchorElement; + + beforeEach(async () => { + root = await ssrHydratedFixture( + html` + Link 1 + Link 2 + `, + { + modules: ['./link-list-anchor.js', '../../link.js'], + }, + ); + }); + + it('renders', () => { + assert.instanceOf(root, SbbLinkListAnchorElement); + }); +}); diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.stories.ts b/src/elements/link-list/link-list-anchor/link-list-anchor.stories.ts new file mode 100644 index 0000000000..a2d72a19f9 --- /dev/null +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.stories.ts @@ -0,0 +1,147 @@ +import { withActions } from '@storybook/addon-actions/decorator'; +import type { InputType } from '@storybook/types'; +import type { + Args, + ArgTypes, + Decorator, + Meta, + StoryContext, + StoryObj, +} from '@storybook/web-components'; +import type { TemplateResult } from 'lit'; +import { html } from 'lit'; + +import { sbbSpread } from '../../../storybook/helpers/spread.js'; + +import readme from './readme.md?raw'; +import './link-list-anchor.js'; +import '../../link/block-link.js'; + +const links = ['Refunds', 'Lost property office', 'Complaints', 'Praise', 'Report property damage']; + +const LinkTemplate = (args: Args): TemplateResult => html` + + ${args.linkTitle} + +`; + +const TemplateSlottedTitle = ({ + 'title-content': titleContent, + ...args +}: Args): TemplateResult => html` + + ${titleContent} + ${links.map((linkTitle) => html` ${LinkTemplate({ linkTitle })} `)} + +`; + +const Template = (args: Args): TemplateResult => html` + + ${links.map((linkTitle) => html` ${LinkTemplate({ linkTitle })} `)} + +`; + +const negative: InputType = { + control: { + type: 'boolean', + }, + options: [true, false], +}; + +const size: InputType = { + control: { + type: 'select', + }, + options: ['xs', 's', 'm'], +}; + +const titleContent: InputType = { + control: { + type: 'text', + }, + table: { + category: 'List Title', + }, +}; + +const titleLevel: InputType = { + control: { + type: 'inline-radio', + }, + options: [2, 3, 4, 5, 6], + table: { + category: 'List Title', + }, +}; + +const defaultArgTypes: ArgTypes = { + negative, + size, + 'title-level': titleLevel, + 'title-content': titleContent, +}; + +const defaultArgs: Args = { + negative: false, + size: size.options![1], + 'title-level': titleLevel.options![0], + 'title-content': 'Help & Contact', +}; + +export const Default: StoryObj = { + render: Template, + argTypes: defaultArgTypes, + args: { ...defaultArgs }, +}; + +export const SizeXS: StoryObj = { + render: Template, + argTypes: defaultArgTypes, + args: { + ...defaultArgs, + size: size.options![0], + }, +}; + +export const SizeM: StoryObj = { + render: Template, + argTypes: defaultArgTypes, + args: { + ...defaultArgs, + size: size.options![2], + }, +}; + +export const SlottedTitle: StoryObj = { + render: TemplateSlottedTitle, + argTypes: defaultArgTypes, + args: { + ...defaultArgs, + }, +}; + +export const Negative: StoryObj = { + render: Template, + argTypes: defaultArgTypes, + args: { + ...defaultArgs, + negative: true, + }, +}; + +const meta: Meta = { + decorators: [withActions as Decorator], + parameters: { + backgroundColor: (context: StoryContext) => + context.args.negative ? 'var(--sbb-color-black)' : 'var(--sbb-color-white)', + docs: { + extractComponentDescription: () => readme, + }, + }, + title: 'elements/sbb-link-list/sbb-link-list-anchor', +}; + +export default meta; diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.ts b/src/elements/link-list/link-list-anchor/link-list-anchor.ts new file mode 100644 index 0000000000..c8789964d6 --- /dev/null +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.ts @@ -0,0 +1,24 @@ +import type { CSSResultGroup } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +import { linkListBaseStyle, SbbLinkListBaseElement } from '../common.js'; + +import style from './link-list-anchor.scss?lit&inline'; + +/** + * It displays a list of `sbb-block-link`. + * + * @slot - Use the unnamed slot to add one or more `sbb-block-link`. + * @slot title - Use this slot to provide a title. + */ +@customElement('sbb-link-list-anchor') +export class SbbLinkListAnchorElement extends SbbLinkListBaseElement { + public static override styles: CSSResultGroup = [linkListBaseStyle, style]; +} + +declare global { + interface HTMLElementTagNameMap { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'sbb-link-list-anchor': SbbLinkListAnchorElement; + } +} diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.visual.spec.ts b/src/elements/link-list/link-list-anchor/link-list-anchor.visual.spec.ts new file mode 100644 index 0000000000..a8bc3abbb4 --- /dev/null +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.visual.spec.ts @@ -0,0 +1,86 @@ +import { html, nothing, type TemplateResult } from 'lit'; + +import { + describeEach, + describeViewports, + visualDiffActive, + visualDiffDefault, + visualDiffFocus, + visualDiffHover, + visualRegressionFixture, +} from '../../core/testing/private.js'; + +import './link-list-anchor.js'; +import '../../link/block-link.js'; + +const links = (): TemplateResult[] => + new Array(5).fill('').map((_v, i) => html` Link ${i} `); +const title = 'Help & Contact'; +const listAnchor = ( + negative: boolean, + size: string, + titleContent?: boolean, + slottedTitle?: boolean, +): TemplateResult => html` + + ${slottedTitle ? html`Slotted title` : nothing} ${links()} + +`; + +describe(`sbb-link-list-anchor`, () => { + const cases = { negative: [false, true] }; + + describeViewports({ viewports: ['zero', 'medium', 'wide'] }, () => { + describeEach(cases, ({ negative }) => { + let root: HTMLElement; + + beforeEach(async function () { + root = await visualRegressionFixture(listAnchor(negative, 's', true), { + backgroundColor: negative ? 'var(--sbb-color-charcoal)' : undefined, + }); + }); + + for (const state of [visualDiffActive, visualDiffHover, visualDiffFocus]) { + it( + state.name, + state.with((setup) => { + setup.withSnapshotElement(root); + }), + ); + } + }); + + describeEach({ ...cases, size: ['xs', 's', 'm'] }, ({ negative, size }) => { + it( + 'title', + visualDiffDefault.with(async (setup) => { + await setup.withFixture(listAnchor(negative, size, true), { + backgroundColor: negative ? 'var(--sbb-color-charcoal)' : undefined, + }); + }), + ); + + it( + 'no title', + visualDiffDefault.with(async (setup) => { + await setup.withFixture(listAnchor(negative, size), { + backgroundColor: negative ? 'var(--sbb-color-charcoal)' : undefined, + }); + }), + ); + + it( + 'slotted title', + visualDiffDefault.with(async (setup) => { + await setup.withFixture(listAnchor(negative, size, false, true), { + backgroundColor: negative ? 'var(--sbb-color-charcoal)' : undefined, + }); + }), + ); + }); + }); +}); diff --git a/src/elements/link-list/link-list-anchor/readme.md b/src/elements/link-list/link-list-anchor/readme.md new file mode 100644 index 0000000000..e8105800df --- /dev/null +++ b/src/elements/link-list/link-list-anchor/readme.md @@ -0,0 +1,53 @@ +The `sbb-link-list-anchor` is a component that can be used to collect and display [sbb-block-link](/docs/elements-sbb-link-sbb-block-link--docs). +It is mainly intended to be used as a link list for page anchors. + +```html + + Refunds + Loss Report + ... + +``` + +## Slots + +The component can display an optional title, +which is visually shown as a level-5 [sbb-title](/docs/elements-sbb-title--docs) +and is used as the `aria-labelledby` attribute of the `ul` element. + +The title can be set using the `titleContent` property or, alternatively, can be projected using the `title` slot. + +```html + ... +``` + +## Style + +The component will accept only `sbb-block-link` or `sbb-block-link-button` instances, +and it will sync its `size` and `negative` property with the inner links. + +```html + + Refunds + Loss Report + ... + +``` + + + +## Properties + +| Name | Attribute | Privacy | Type | Default | Description | +| -------------- | --------------- | ------- | --------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `size` | `size` | public | `SbbLinkSize` | `'s'` | Text size of the nested sbb-block-link instances. This will overwrite the size attribute of nested sbb-block-link instances. | +| `titleContent` | `title-content` | public | `string \| undefined` | | The title text we want to show before the list. | +| `titleLevel` | `title-level` | public | `SbbTitleLevel` | `'2'` | The semantic level of the title, e.g. 2 = h2. | + +## Slots + +| Name | Description | +| ------- | --------------------------------------------------------- | +| | Use the unnamed slot to add one or more `sbb-block-link`. | +| `title` | Use this slot to provide a title. | diff --git a/src/elements/link-list/link-list.ts b/src/elements/link-list/link-list.ts index d0c8d77c9a..49a1597610 100644 --- a/src/elements/link-list/link-list.ts +++ b/src/elements/link-list/link-list.ts @@ -1,98 +1 @@ -import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; -import { html, LitElement, nothing } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; - -import { slotState } from '../core/decorators.js'; -import type { SbbHorizontalFrom, SbbOrientation } from '../core/interfaces.js'; -import { SbbNamedSlotListMixin, SbbNegativeMixin, type WithListChildren } from '../core/mixins.js'; -import type { - SbbBlockLinkButtonElement, - SbbBlockLinkElement, - SbbBlockLinkStaticElement, - SbbLinkSize, -} from '../link.js'; -import type { SbbTitleLevel } from '../title.js'; - -import style from './link-list.scss?lit&inline'; - -import '../title.js'; - -/** - * It displays a list of `sbb-block-link`. - * - * @slot - Use the unnamed slot to add one or more `sbb-block-link`. - * @slot title - Use this slot to provide a title. - */ -@customElement('sbb-link-list') -@slotState() -export class SbbLinkListElement extends SbbNegativeMixin( - SbbNamedSlotListMixin< - SbbBlockLinkElement | SbbBlockLinkButtonElement | SbbBlockLinkStaticElement, - typeof LitElement - >(LitElement), -) { - public static override styles: CSSResultGroup = style; - protected override readonly listChildLocalNames = [ - 'sbb-block-link', - 'sbb-block-link-button', - 'sbb-block-link-static', - ]; - - /** The title text we want to show before the list. */ - @property({ attribute: 'title-content', reflect: true }) public titleContent?: string; - - /** The semantic level of the title, e.g. 2 = h2. */ - @property({ attribute: 'title-level' }) public titleLevel: SbbTitleLevel = '2'; - - /** - * Text size of the nested sbb-block-link instances. This will overwrite the size attribute of - * nested sbb-block-link instances. - */ - @property({ reflect: true }) public size: SbbLinkSize = 's'; - - /** Selected breakpoint from which the list is rendered horizontally. */ - @property({ attribute: 'horizontal-from', reflect: true }) - public horizontalFrom?: SbbHorizontalFrom; - - /** The orientation in which the list will be shown vertical or horizontal. */ - @property({ reflect: true }) public orientation: SbbOrientation = 'vertical'; - - protected override willUpdate(changedProperties: PropertyValues>): void { - super.willUpdate(changedProperties); - - if ( - changedProperties.has('size') || - changedProperties.has('negative') || - changedProperties.has('listChildren') - ) { - for (const link of this.listChildren) { - link.negative = this.negative; - link.size = this.size; - } - } - } - - protected override render(): TemplateResult { - return html` - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'sbb-link-list': SbbLinkListElement; - } -} +export * from './link-list/link-list.js'; diff --git a/src/elements/link-list/__snapshots__/link-list.snapshot.spec.snap.js b/src/elements/link-list/link-list/__snapshots__/link-list.snapshot.spec.snap.js similarity index 100% rename from src/elements/link-list/__snapshots__/link-list.snapshot.spec.snap.js rename to src/elements/link-list/link-list/__snapshots__/link-list.snapshot.spec.snap.js diff --git a/src/elements/link-list/link-list.scss b/src/elements/link-list/link-list/link-list.scss similarity index 61% rename from src/elements/link-list/link-list.scss rename to src/elements/link-list/link-list/link-list.scss index 7c2f882fea..aa9c545ae5 100644 --- a/src/elements/link-list/link-list.scss +++ b/src/elements/link-list/link-list/link-list.scss @@ -1,7 +1,4 @@ -@use '../core/styles' as sbb; - -// Box-sizing rules contained in typography are not traversing Shadow DOM boundaries. We need to include box-sizing mixin in every component. -@include sbb.box-sizing; +@use '../../core/styles/index' as sbb; @mixin horizontal-orientation { --sbb-link-list-flex-flow: row wrap; @@ -15,29 +12,12 @@ $breakpoints: 'zero', 'micro', 'small', 'medium', 'large', 'wide', 'ultra'; :host { --sbb-link-list-flex-flow: column nowrap; - - display: block; } :host([orientation='horizontal']) { @include horizontal-orientation; } -.sbb-link-list-wrapper { - display: flex; - flex-direction: column; - gap: var(--sbb-spacing-fixed-3x); -} - -.sbb-link-list-title { - // Overwrite sbb-title default margin - margin: 0; - - :host(:not([data-slot-names~='title'], [title-content])) & { - display: none; - } -} - .sbb-link-list { @include sbb.list-reset; diff --git a/src/elements/link-list/link-list.snapshot.spec.ts b/src/elements/link-list/link-list/link-list.snapshot.spec.ts similarity index 94% rename from src/elements/link-list/link-list.snapshot.spec.ts rename to src/elements/link-list/link-list/link-list.snapshot.spec.ts index 9e6e258ea5..14d2255a09 100644 --- a/src/elements/link-list/link-list.snapshot.spec.ts +++ b/src/elements/link-list/link-list/link-list.snapshot.spec.ts @@ -1,11 +1,11 @@ import { expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; -import { fixture, testA11yTreeSnapshot } from '../core/testing/private.js'; +import { fixture, testA11yTreeSnapshot } from '../../core/testing/private.js'; import type { SbbLinkListElement } from './link-list.js'; -import '../link/block-link.js'; +import '../../link/block-link.js'; import './link-list.js'; describe(`sbb-link-list`, () => { diff --git a/src/elements/link-list/link-list.spec.ts b/src/elements/link-list/link-list/link-list.spec.ts similarity index 93% rename from src/elements/link-list/link-list.spec.ts rename to src/elements/link-list/link-list/link-list.spec.ts index db7b44033b..f48de91bb3 100644 --- a/src/elements/link-list/link-list.spec.ts +++ b/src/elements/link-list/link-list/link-list.spec.ts @@ -1,11 +1,11 @@ import { assert, expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; -import { fixture } from '../core/testing/private.js'; -import { waitForLitRender } from '../core/testing.js'; +import { fixture } from '../../core/testing/private.js'; +import { waitForLitRender } from '../../core/testing.js'; import { SbbLinkListElement } from './link-list.js'; -import '../link/block-link.js'; +import '../../link/block-link.js'; describe(`sbb-link-list`, () => { let element: SbbLinkListElement; diff --git a/src/elements/link-list/link-list.ssr.spec.ts b/src/elements/link-list/link-list/link-list.ssr.spec.ts similarity index 91% rename from src/elements/link-list/link-list.ssr.spec.ts rename to src/elements/link-list/link-list/link-list.ssr.spec.ts index 0ce4e93d4f..419215dfce 100644 --- a/src/elements/link-list/link-list.ssr.spec.ts +++ b/src/elements/link-list/link-list/link-list.ssr.spec.ts @@ -1,11 +1,11 @@ import { assert } from '@open-wc/testing'; import { html } from 'lit'; -import { ssrHydratedFixture } from '../core/testing/private.js'; +import { ssrHydratedFixture } from '../../core/testing/private.js'; import { SbbLinkListElement } from './link-list.js'; -import '../link.js'; +import '../../link.js'; describe(`sbb-link-list ssr`, () => { let root: SbbLinkListElement; @@ -37,7 +37,7 @@ describe(`sbb-link-list ssr`, () => { > `, - { modules: ['./link-list.js', '../link.js'] }, + { modules: ['./link-list.js', '../../link.js'] }, ); }); diff --git a/src/elements/link-list/link-list.stories.ts b/src/elements/link-list/link-list/link-list.stories.ts similarity index 91% rename from src/elements/link-list/link-list.stories.ts rename to src/elements/link-list/link-list/link-list.stories.ts index de9274a252..4a8053213c 100644 --- a/src/elements/link-list/link-list.stories.ts +++ b/src/elements/link-list/link-list/link-list.stories.ts @@ -3,12 +3,14 @@ import type { Meta, StoryObj, ArgTypes, Args, StoryContext } from '@storybook/we import type { TemplateResult } from 'lit'; import { html } from 'lit'; -import { sbbSpread } from '../../storybook/helpers/spread.js'; +import { sbbSpread } from '../../../storybook/helpers/spread.js'; import readme from './readme.md?raw'; -import '../link/block-link.js'; +import '../../link/block-link.js'; import './link-list.js'; +const links = ['Refunds', 'Lost property office', 'Complaints', 'Praise', 'Report property damage']; + const LinkTemplate = (args: Args): TemplateResult => html` html` `; -const links = ['Refunds', 'Lost property office', 'Complaints', 'Praise', 'Report property damage']; - // SlottedTitle const TemplateSlottedTitle = ({ 'title-content': titleContent, @@ -119,6 +119,15 @@ export const LinkListXS: StoryObj = { }, }; +export const LinkListM: StoryObj = { + render: Template, + argTypes: defaultArgTypes, + args: { + ...defaultArgs, + size: size.options![2], + }, +}; + export const LinkListNoTitle: StoryObj = { render: Template, argTypes: defaultArgTypes, @@ -162,7 +171,7 @@ const meta: Meta = { extractComponentDescription: () => readme, }, }, - title: 'elements/sbb-link-list', + title: 'elements/sbb-link-list/sbb-link-list', }; export default meta; diff --git a/src/elements/link-list/link-list/link-list.ts b/src/elements/link-list/link-list/link-list.ts new file mode 100644 index 0000000000..199780c781 --- /dev/null +++ b/src/elements/link-list/link-list/link-list.ts @@ -0,0 +1,32 @@ +import type { CSSResultGroup } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +import type { SbbHorizontalFrom, SbbOrientation } from '../../core/interfaces.js'; +import { linkListBaseStyle, SbbLinkListBaseElement } from '../common.js'; + +import style from './link-list.scss?lit&inline'; + +/** + * It displays a list of `sbb-block-link`. + * + * @slot - Use the unnamed slot to add one or more `sbb-block-link`. + * @slot title - Use this slot to provide a title. + */ +@customElement('sbb-link-list') +export class SbbLinkListElement extends SbbLinkListBaseElement { + public static override styles: CSSResultGroup = [linkListBaseStyle, style]; + + /** Selected breakpoint from which the list is rendered horizontally. */ + @property({ attribute: 'horizontal-from', reflect: true }) + public horizontalFrom?: SbbHorizontalFrom; + + /** The orientation in which the list will be shown vertical or horizontal. */ + @property({ reflect: true }) public orientation: SbbOrientation = 'vertical'; +} + +declare global { + interface HTMLElementTagNameMap { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'sbb-link-list': SbbLinkListElement; + } +} diff --git a/src/elements/link-list/link-list.visual.spec.ts b/src/elements/link-list/link-list/link-list.visual.spec.ts similarity index 95% rename from src/elements/link-list/link-list.visual.spec.ts rename to src/elements/link-list/link-list/link-list.visual.spec.ts index 8cd8108a90..2934335d6f 100644 --- a/src/elements/link-list/link-list.visual.spec.ts +++ b/src/elements/link-list/link-list/link-list.visual.spec.ts @@ -5,10 +5,10 @@ import { describeViewports, visualDiffDefault, visualRegressionFixture, -} from '../core/testing/private.js'; +} from '../../core/testing/private.js'; import './link-list.js'; -import '../link/block-link.js'; +import '../../link/block-link.js'; describe(`sbb-link-list`, () => { let root: HTMLElement; diff --git a/src/elements/link-list/readme.md b/src/elements/link-list/link-list/readme.md similarity index 98% rename from src/elements/link-list/readme.md rename to src/elements/link-list/link-list/readme.md index 63359f54ae..104822716e 100644 --- a/src/elements/link-list/readme.md +++ b/src/elements/link-list/link-list/readme.md @@ -1,4 +1,4 @@ -The `sbb-link-list` is a component that can be used to collect and display more [sbb-block-link](/docs/elements-sbb-link-sbb-block-link--docs). +The `sbb-link-list` is a component that can be used to collect and display [sbb-block-link](/docs/elements-sbb-link-sbb-block-link--docs). ```html diff --git a/src/visual-regression-app/src/components/test-case/image-diff/fullscreen-diff/fullscreen-diff.scss b/src/visual-regression-app/src/components/test-case/image-diff/fullscreen-diff/fullscreen-diff.scss index ded25e8815..3de9ddf855 100644 --- a/src/visual-regression-app/src/components/test-case/image-diff/fullscreen-diff/fullscreen-diff.scss +++ b/src/visual-regression-app/src/components/test-case/image-diff/fullscreen-diff/fullscreen-diff.scss @@ -15,7 +15,7 @@ } .app-scroll-container { - @include sbb.scrollbar; - overflow: auto; + + @include sbb.scrollbar; }