diff --git a/src/elements/button/common/common-stories.ts b/src/elements/button/common/common-stories.ts
index d4cce7c369..710b0b1ddc 100644
--- a/src/elements/button/common/common-stories.ts
+++ b/src/elements/button/common/common-stories.ts
@@ -14,7 +14,7 @@ import { html, unsafeStatic } from 'lit/static-html.js';
import { sbbSpread } from '../../../storybook/helpers/spread.js';
import '../../icon.js';
-import '../../loading-indicator.js';
+import '../../loading-indicator-circle.js';
/* eslint-disable lit/binding-positions, @typescript-eslint/naming-convention */
const Template = ({ tag, text, ...args }: Args): TemplateResult => html`
@@ -38,10 +38,9 @@ const IconSlotTemplate = ({
const LoadingIndicatorTemplate = ({ tag, text, ...args }: Args): TemplateResult => html`
<${unsafeStatic(tag)} ${sbbSpread(args)}>
-
+ >
${text}
${unsafeStatic(tag)}>
`;
diff --git a/src/elements/loading-indicator-circle.ts b/src/elements/loading-indicator-circle.ts
new file mode 100644
index 0000000000..a4ac3af4f9
--- /dev/null
+++ b/src/elements/loading-indicator-circle.ts
@@ -0,0 +1 @@
+export * from './loading-indicator-circle/loading-indicator-circle.js';
diff --git a/src/elements/loading-indicator-circle/__snapshots__/loading-indicator-circle.snapshot.spec.snap.js b/src/elements/loading-indicator-circle/__snapshots__/loading-indicator-circle.snapshot.spec.snap.js
new file mode 100644
index 0000000000..428306b2f8
--- /dev/null
+++ b/src/elements/loading-indicator-circle/__snapshots__/loading-indicator-circle.snapshot.spec.snap.js
@@ -0,0 +1,41 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["sbb-loading-indicator-circle renders with variant `circle` DOM"] =
+`
+
+`;
+/* end snapshot sbb-loading-indicator-circle renders with variant `circle` DOM */
+
+snapshots["sbb-loading-indicator-circle renders with variant `circle` Shadow DOM"] =
+`
+
+
+
+`;
+/* end snapshot sbb-loading-indicator-circle renders with variant `circle` Shadow DOM */
+
+snapshots["sbb-loading-indicator-circle renders with variant `circle` A11y tree Chrome"] =
+`
+ {
+ "role": "WebArea",
+ "name": ""
+}
+
+`;
+/* end snapshot sbb-loading-indicator-circle renders with variant `circle` A11y tree Chrome */
+
+snapshots["sbb-loading-indicator-circle renders with variant `circle` A11y tree Firefox"] =
+`
+ {
+ "role": "document",
+ "name": ""
+}
+
+`;
+/* end snapshot sbb-loading-indicator-circle renders with variant `circle` A11y tree Firefox */
+
diff --git a/src/elements/loading-indicator-circle/loading-indicator-circle.scss b/src/elements/loading-indicator-circle/loading-indicator-circle.scss
new file mode 100644
index 0000000000..0f4f57929a
--- /dev/null
+++ b/src/elements/loading-indicator-circle/loading-indicator-circle.scss
@@ -0,0 +1,92 @@
+@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: inline-block;
+ line-height: 0;
+
+ --sbb-loading-indicator-circle-color: var(--sbb-color-red);
+ --sbb-loading-indicator-circle-padding: #{sbb.px-to-rem-build(2)};
+ --sbb-loading-indicator-circle-duration: var(--sbb-disable-animation-zero-duration, 1.5s);
+ --sbb-loading-indicator-circle-background-color: var(--sbb-color-white);
+ --sbb-loading-indicator-circle-background: conic-gradient(
+ from 90deg,
+ var(--sbb-loading-indicator-circle-background-color),
+ var(--sbb-loading-indicator-circle-color)
+ );
+ --sbb-loading-indicator-circle-animated-width: 0.1875em;
+ --sbb-loading-indicator-circle-animated-height: 0.1875em;
+ --sbb-loading-indicator-circle-animated-border-radius: 50%;
+
+ @include sbb.if-forced-colors {
+ --sbb-loading-indicator-circle-color: CanvasText !important;
+ --sbb-loading-indicator-circle-animated-width: 50%;
+ --sbb-loading-indicator-circle-animated-height: 100%;
+ --sbb-loading-indicator-circle-animated-border-radius: 0;
+ --sbb-loading-indicator-circle-background: transparent;
+ }
+}
+
+:host([color='smoke']) {
+ --sbb-loading-indicator-circle-color: var(--sbb-color-smoke);
+}
+
+:host([color='white']) {
+ --sbb-loading-indicator-circle-color: var(--sbb-color-white);
+}
+
+:host([color='white']) {
+ --sbb-loading-indicator-circle-background-color: var(--sbb-color-iron);
+}
+
+.sbb-loading-indicator {
+ display: inline-flex;
+ padding: var(--sbb-loading-indicator-circle-padding);
+ vertical-align: middle;
+ line-height: 1;
+}
+
+.sbb-loading-indicator__animated-element {
+ width: 1.25em;
+ height: 1.25em;
+ display: inline-block;
+ color: transparent;
+ position: relative;
+ margin: 0 auto;
+ overflow: hidden;
+ border-radius: 50%;
+ background: var(--sbb-loading-indicator-circle-background);
+ mask: radial-gradient(
+ circle 0.4375em,
+ #0000 98%,
+ var(--sbb-loading-indicator-circle-background-color)
+ );
+ animation: rotation var(--sbb-loading-indicator-circle-duration) infinite linear;
+
+ // Rounded start of strong color part
+ &::after {
+ content: '';
+ width: var(--sbb-loading-indicator-circle-animated-width);
+ height: var(--sbb-loading-indicator-circle-animated-height);
+ background: var(--sbb-loading-indicator-circle-color);
+ border-radius: var(--sbb-loading-indicator-circle-animated-border-radius);
+ position: absolute;
+ top: 50%;
+ right: 0;
+ translate: 0 -50%;
+ overflow: hidden;
+ margin: auto;
+ }
+}
+
+@keyframes rotation {
+ from {
+ rotate: 0deg;
+ }
+
+ to {
+ rotate: 359deg;
+ }
+}
diff --git a/src/elements/loading-indicator-circle/loading-indicator-circle.snapshot.spec.ts b/src/elements/loading-indicator-circle/loading-indicator-circle.snapshot.spec.ts
new file mode 100644
index 0000000000..06363dbe57
--- /dev/null
+++ b/src/elements/loading-indicator-circle/loading-indicator-circle.snapshot.spec.ts
@@ -0,0 +1,28 @@
+import { expect } from '@open-wc/testing';
+import { html } from 'lit/static-html.js';
+
+import { fixture, testA11yTreeSnapshot } from '../core/testing/private.js';
+
+import type { SbbLoadingIndicatorCircleElement } from './loading-indicator-circle.js';
+
+import './loading-indicator-circle.js';
+
+describe(`sbb-loading-indicator-circle`, () => {
+ let element: SbbLoadingIndicatorCircleElement;
+
+ describe('renders with variant `circle`', () => {
+ beforeEach(async () => {
+ element = await fixture(html``);
+ });
+
+ 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/loading-indicator-circle/loading-indicator-circle.spec.ts b/src/elements/loading-indicator-circle/loading-indicator-circle.spec.ts
new file mode 100644
index 0000000000..f064c0b074
--- /dev/null
+++ b/src/elements/loading-indicator-circle/loading-indicator-circle.spec.ts
@@ -0,0 +1,15 @@
+import { assert } from '@open-wc/testing';
+import { html } from 'lit/static-html.js';
+
+import { fixture } from '../core/testing/private.js';
+
+import { SbbLoadingIndicatorCircleElement } from './loading-indicator-circle.js';
+
+describe(`sbb-loading-indicator-circle`, () => {
+ let element: SbbLoadingIndicatorCircleElement;
+
+ it('renders', async () => {
+ element = await fixture(html``);
+ assert.instanceOf(element, SbbLoadingIndicatorCircleElement);
+ });
+});
diff --git a/src/elements/loading-indicator-circle/loading-indicator-circle.ssr.spec.ts b/src/elements/loading-indicator-circle/loading-indicator-circle.ssr.spec.ts
new file mode 100644
index 0000000000..2c58b2be4b
--- /dev/null
+++ b/src/elements/loading-indicator-circle/loading-indicator-circle.ssr.spec.ts
@@ -0,0 +1,23 @@
+import { assert } from '@open-wc/testing';
+import { html } from 'lit';
+
+import { ssrHydratedFixture } from '../core/testing/private.js';
+
+import { SbbLoadingIndicatorCircleElement } from './loading-indicator-circle.js';
+
+describe(`sbb-loading-indicator-circle ssr`, () => {
+ let root: SbbLoadingIndicatorCircleElement;
+
+ beforeEach(async () => {
+ root = await ssrHydratedFixture(
+ html``,
+ {
+ modules: ['./loading-indicator-circle.js'],
+ },
+ );
+ });
+
+ it('renders', () => {
+ assert.instanceOf(root, SbbLoadingIndicatorCircleElement);
+ });
+});
diff --git a/src/elements/loading-indicator-circle/loading-indicator-circle.stories.ts b/src/elements/loading-indicator-circle/loading-indicator-circle.stories.ts
new file mode 100644
index 0000000000..acabba3db6
--- /dev/null
+++ b/src/elements/loading-indicator-circle/loading-indicator-circle.stories.ts
@@ -0,0 +1,107 @@
+import type { InputType, StoryContext } from '@storybook/types';
+import type { Args, ArgTypes, Meta, StoryObj } from '@storybook/web-components';
+import type { TemplateResult } from 'lit';
+import { html } from 'lit';
+
+import { sbbSpread } from '../../storybook/helpers/spread.js';
+
+import type { SbbLoadingIndicatorCircleElement } from './loading-indicator-circle.js';
+import readme from './readme.md?raw';
+
+import './loading-indicator-circle.js';
+import '../button/button.js';
+import '../title.js';
+import '../card.js';
+
+const createLoadingIndicator = (event: Event): void => {
+ const loader: SbbLoadingIndicatorCircleElement = document.createElement(
+ 'sbb-loading-indicator-circle',
+ );
+ const container = (event.currentTarget as HTMLElement).parentElement!.querySelector(
+ '.loader-container',
+ )!;
+ loader.setAttribute('aria-label', 'Loading, please wait');
+ container.append(loader);
+ setTimeout(() => {
+ const p = document.createElement('p');
+ p.textContent = "Loading complete. Here's your data: ...";
+ container.append(p);
+ loader.remove();
+ }, 5000);
+};
+
+const TemplateAccessibility = (): TemplateResult => html`
+
+ Turn on your screen-reader and click the button to make the loading indicator appear.
+
+
+ createLoadingIndicator(event)}> Show loader
+
+`;
+
+const Template = (args: Args): TemplateResult => html`
+
+ Inline loading
+ indicator
+
+
+ Adaptive to
+ font size
+
+`;
+
+const color: InputType = {
+ control: {
+ type: 'inline-radio',
+ },
+ options: ['default', 'smoke', 'white'],
+};
+
+const defaultArgTypes: ArgTypes = {
+ color,
+};
+
+const defaultArgs: Args = {
+ color: color.options![0],
+};
+
+export const Default: StoryObj = {
+ render: Template,
+ argTypes: defaultArgTypes,
+ args: { ...defaultArgs },
+};
+
+export const Accessibility: StoryObj = {
+ render: TemplateAccessibility,
+ argTypes: defaultArgTypes,
+ args: { ...defaultArgs },
+};
+
+const meta: Meta = {
+ decorators: [
+ (story, context) => {
+ if (context.args.color === 'white') {
+ return html`
+ ${story()}
+
`;
+ }
+ return story();
+ },
+ ],
+ parameters: {
+ backgroundColor: (context: StoryContext) =>
+ context.args.color === 'white' ? 'var(--sbb-color-iron)' : 'var(--sbb-color-white)',
+ docs: {
+ extractComponentDescription: () => readme,
+ },
+ },
+ title: 'elements/sbb-loading-indicator-circle',
+};
+
+export default meta;
diff --git a/src/elements/loading-indicator-circle/loading-indicator-circle.ts b/src/elements/loading-indicator-circle/loading-indicator-circle.ts
new file mode 100644
index 0000000000..74d0fdfef4
--- /dev/null
+++ b/src/elements/loading-indicator-circle/loading-indicator-circle.ts
@@ -0,0 +1,38 @@
+import type { CSSResultGroup, TemplateResult } from 'lit';
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+
+import { hostAttributes } from '../core/decorators.js';
+
+import style from './loading-indicator-circle.scss?lit&inline';
+
+/**
+ * It displays a circle loading indicator.
+ */
+export
+@customElement('sbb-loading-indicator-circle')
+@hostAttributes({
+ role: 'progressbar',
+ 'aria-busy': 'true',
+})
+class SbbLoadingIndicatorCircleElement extends LitElement {
+ public static override styles: CSSResultGroup = style;
+
+ /** Color variant. */
+ @property({ reflect: true }) public accessor color: 'default' | 'smoke' | 'white' = 'default';
+
+ protected override render(): TemplateResult {
+ return html`
+
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ 'sbb-loading-indicator-circle': SbbLoadingIndicatorCircleElement;
+ }
+}
diff --git a/src/elements/loading-indicator-circle/loading-indicator-circle.visual.spec.ts b/src/elements/loading-indicator-circle/loading-indicator-circle.visual.spec.ts
new file mode 100644
index 0000000000..fdab987aa8
--- /dev/null
+++ b/src/elements/loading-indicator-circle/loading-indicator-circle.visual.spec.ts
@@ -0,0 +1,31 @@
+import { html, nothing } from 'lit';
+
+import { describeEach, describeViewports, visualDiffDefault } from '../core/testing/private.js';
+
+import './loading-indicator-circle.js';
+
+describe(`sbb-loading-indicator-circle`, () => {
+ const cases = {
+ color: ['default', 'smoke', 'white'],
+ size: ['s', 'l'],
+ };
+
+ describeViewports({ viewports: ['zero'] }, () => {
+ describeEach(cases, ({ color, size }) => {
+ it(
+ '',
+ visualDiffDefault.with(async (setup) => {
+ await setup.withFixture(
+ html`
+
+ `,
+ { backgroundColor: color === 'white' ? 'var(--sbb-color-charcoal)' : undefined },
+ );
+ }),
+ );
+ });
+ });
+});
diff --git a/src/elements/loading-indicator-circle/readme.md b/src/elements/loading-indicator-circle/readme.md
new file mode 100644
index 0000000000..eb04dbd753
--- /dev/null
+++ b/src/elements/loading-indicator-circle/readme.md
@@ -0,0 +1,35 @@
+The `sbb-loading-indicator-circle` is a component which can be used to indicate progress status
+or an ongoing activity which require some time to complete.
+
+```html
+
+```
+
+It can be slotted in other components (e.g. `sbb-button`) in the icon slot.
+
+```html
+
+
+ Button
+
+```
+
+## Accessibility
+
+If the `sbb-loading-indicator-circle` should be announced by screen-readers, use an element with the correct aria attributes
+(`aria-live` set to `polite` or `assertive`, and possibly `aria-atomic` and `aria-relevant`)
+and then append the `sbb-loading-indicator` on it after giving it the correct `aria-label`.
+
+```html
+
+
+
+```
+
+
+
+## Properties
+
+| Name | Attribute | Privacy | Type | Default | Description |
+| ------- | --------- | ------- | --------------------------------- | ----------- | -------------- |
+| `color` | `color` | public | `'default' \| 'smoke' \| 'white'` | `'default'` | Color variant. |
diff --git a/src/elements/loading-indicator/__snapshots__/loading-indicator.snapshot.spec.snap.js b/src/elements/loading-indicator/__snapshots__/loading-indicator.snapshot.spec.snap.js
index ecdee2654d..f458ae6d14 100644
--- a/src/elements/loading-indicator/__snapshots__/loading-indicator.snapshot.spec.snap.js
+++ b/src/elements/loading-indicator/__snapshots__/loading-indicator.snapshot.spec.snap.js
@@ -7,7 +7,6 @@ snapshots["sbb-loading-indicator renders with variant `window` DOM"] =
color="default"
role="progressbar"
size="s"
- variant="window"
>
`;
diff --git a/src/elements/loading-indicator/loading-indicator.scss b/src/elements/loading-indicator/loading-indicator.scss
index 03f2c0651a..0675cb57b2 100644
--- a/src/elements/loading-indicator/loading-indicator.scss
+++ b/src/elements/loading-indicator/loading-indicator.scss
@@ -5,6 +5,29 @@
:host {
--sbb-loading-indicator-color: var(--sbb-color-red);
+ --sbb-loading-indicator-padding: 0;
+ --sbb-loading-indicator-duration: var(
+ --sbb-disable-animation-zero-duration,
+ var(--sbb-animation-duration-6x)
+ );
+ --sbb-loading-indicator-window-element-rotation: 55.24deg;
+ --_sbb-loading-indicator-window-first-span-width: calc(
+ var(--sbb-loading-indicator-window-height) * 0.58
+ );
+
+ // Size of the gap between the windows
+ --_sbb-loading-indicator-window-unit: calc(
+ var(--_sbb-loading-indicator-window-first-span-width) / 5.5
+ );
+
+ // The spans must move one rectangle plus one gap per animation cycle
+ --_sbb-loading-indicator-window-element-animation-speed: calc(
+ var(--_sbb-loading-indicator-window-unit) * 6.5
+ );
+
+ // Defaults to S
+ --sbb-loading-indicator-window-height: #{sbb.px-to-rem-build(18)};
+ --sbb-loading-indicator-window-element-width: #{sbb.px-to-rem-build(55)};
display: inline-block;
line-height: 0;
@@ -18,166 +41,71 @@
--sbb-loading-indicator-color: var(--sbb-color-white);
}
-:host([variant='circle']) {
- --sbb-loading-indicator-padding: var(--sbb-border-width-2x);
- --sbb-loading-indicator-duration: var(--sbb-disable-animation-zero-duration, 1.5s);
- --sbb-loading-indicator-background-color: var(--sbb-color-white);
- --sbb-loading-indicator-circle-background: conic-gradient(
- from 90deg,
- var(--sbb-loading-indicator-background-color),
- var(--sbb-loading-indicator-color)
- );
- --sbb-loading-indicator-circle-animated-width: 0.1875em;
- --sbb-loading-indicator-circle-animated-height: 0.1875em;
- --sbb-loading-indicator-circle-animated-border-radius: 50%;
-}
-
-:host([color='white'][variant='circle']) {
- --sbb-loading-indicator-background-color: var(--sbb-color-iron);
-}
-
-:host([variant='circle']) .sbb-loading-indicator {
- display: inline-flex;
- height: auto;
- width: auto;
- padding-inline: var(--sbb-loading-indicator-padding);
- padding-block: var(--sbb-loading-indicator-padding);
- vertical-align: middle;
- line-height: 1;
-}
-
-:host([variant='circle']) .sbb-loading-indicator__animated-element {
- width: 1.25em;
- height: 1.25em;
- display: inline-block;
- color: transparent;
- position: relative;
- margin: 0 auto;
- overflow: hidden;
- border-radius: 50%;
- background: var(--sbb-loading-indicator-circle-background);
- // stylelint-disable-next-line property-no-vendor-prefix
- -webkit-mask: radial-gradient(
- circle 0.4375em,
- #0000 98%,
- var(--sbb-loading-indicator-background-color)
- );
- mask: radial-gradient(circle 0.4375em, #0000 98%, var(--sbb-loading-indicator-background-color));
- animation: rotation var(--sbb-loading-indicator-duration) infinite linear;
-
- &::after {
- content: '';
- width: var(--sbb-loading-indicator-circle-animated-width);
- height: var(--sbb-loading-indicator-circle-animated-height);
- background: var(--sbb-loading-indicator-color);
- border-radius: var(--sbb-loading-indicator-circle-animated-border-radius);
- position: absolute;
- top: 50%;
- right: 0;
- transform: translateY(-50%);
- overflow: hidden;
- margin: auto;
- @include sbb.if-forced-colors {
- --sbb-loading-indicator-color: CanvasText;
- --sbb-loading-indicator-circle-animated-width: 50%;
- --sbb-loading-indicator-circle-animated-height: 100%;
- --sbb-loading-indicator-circle-animated-border-radius: 0;
- }
- }
-
- @include sbb.if-forced-colors {
- --sbb-loading-indicator-circle-background: transparent;
- }
-}
-
-:host([color='white'][variant='circle']) .sbb-loading-indicator__animated-element::after {
- @include sbb.if-forced-colors {
- --sbb-loading-indicator-color: var(--sbb-color-white);
- }
+:host([size='l']) {
+ --sbb-loading-indicator-window-height: #{sbb.px-to-rem-build(32)};
+ --sbb-loading-indicator-window-element-width: #{sbb.px-to-rem-build(100)};
}
-:host([variant='window']) {
- --sbb-loading-indicator-padding: 0;
- --sbb-loading-indicator-duration: var(
- --sbb-disable-animation-zero-duration,
- var(--sbb-animation-duration-6x)
- );
+:host([size='xl']) {
+ --sbb-loading-indicator-window-height: #{sbb.px-to-rem-build(51)};
+ --sbb-loading-indicator-window-element-width: #{sbb.px-to-rem-build(140)};
}
-:host([variant='window'][size='s']) {
- --sbb-loading-indicator-window-height: #{sbb.px-to-rem-build(26.66666)};
- --sbb-loading-indicator-window-padding-block-start: #{sbb.px-to-rem-build(10.66666)};
- --sbb-loading-indicator-window-element-width: #{sbb.px-to-rem-build(55.46666)};
- --sbb-loading-indicator-window-element-height: #{sbb.px-to-rem-build(5.33333)};
- --sbb-loading-indicator-window-element-perspective: #{sbb.px-to-rem-build(96)};
- --sbb-loading-indicator-window-element-animation-name: loading-container-small;
- --sbb-loading-indicator-window-last-span-width: #{sbb.px-to-rem-build(8.53333)};
- --sbb-loading-indicator-window-last-span-height: #{sbb.px-to-rem-build(5.33333)};
- --sbb-loading-indicator-window-last-span-margin: #{sbb.px-to-rem-build(3.2)};
- --sbb-loading-indicator-window-last-span-transform: translate3d(
- #{sbb.px-to-rem-build(-1.6)},
- 0,
- 0
- );
+:host([size='xxl']) {
+ --sbb-loading-indicator-window-height: #{sbb.px-to-rem-build(98)};
+ --sbb-loading-indicator-window-element-width: #{sbb.px-to-rem-build(250)};
}
-:host([variant='window'][size='l']) {
- --sbb-loading-indicator-window-height: #{sbb.px-to-rem-build(48)};
- --sbb-loading-indicator-window-padding-block-start: #{sbb.px-to-rem-build(19.2)};
- --sbb-loading-indicator-window-element-width: #{sbb.px-to-rem-build(92.7969)};
- --sbb-loading-indicator-window-element-height: #{sbb.px-to-rem-build(9.59375)};
- --sbb-loading-indicator-window-element-perspective: #{sbb.px-to-rem-build(128)};
- --sbb-loading-indicator-window-element-animation-name: loading-container-large;
- --sbb-loading-indicator-window-last-span-width: #{sbb.px-to-rem-build(16)};
- --sbb-loading-indicator-window-last-span-height: #{sbb.px-to-rem-build(9.59375)};
- --sbb-loading-indicator-window-last-span-margin: #{sbb.px-to-rem-build(3.2)};
+:host([size='xxxl']) {
+ --sbb-loading-indicator-window-height: #{sbb.px-to-rem-build(147)};
+ --sbb-loading-indicator-window-element-width: #{sbb.px-to-rem-build(360)};
}
-:host([variant='window']) span {
+span {
display: inline-block;
}
-:host([variant='window']) .sbb-loading-indicator {
+.sbb-loading-indicator {
display: flex;
height: var(--sbb-loading-indicator-window-height);
- padding-block-start: var(--sbb-loading-indicator-window-padding-block-start);
+ align-items: center;
+
+ @include sbb.zero-width-space;
}
-:host([variant='window']) .sbb-loading-indicator__animated-element {
+.sbb-loading-indicator__animated-element {
+ position: relative;
+ justify-content: center;
+ display: flex;
margin: 0 auto;
transform-origin: center;
- transform: translate3d(-2em, 0, 0);
+ translate: 25%;
backface-visibility: hidden;
- transform-style: preserve-3d;
width: var(--sbb-loading-indicator-window-element-width);
- height: var(--sbb-loading-indicator-window-element-height);
- perspective: var(--sbb-loading-indicator-window-element-perspective);
+ perspective: var(--sbb-loading-indicator-window-height);
}
-:host([variant='window']) .sbb-loading-indicator__animated-element > span {
+.sbb-loading-indicator__animated-element > span {
position: relative;
- transform: rotateY(50deg) translateZ(1em);
- transform-origin: right;
+ align-self: center;
+ transform-origin: left;
+ rotate: y var(--sbb-loading-indicator-window-element-rotation);
backface-visibility: hidden;
}
-:host([variant='window']) .sbb-loading-indicator__animated-element > span > span {
+.sbb-loading-indicator__animated-element > span > span {
position: relative;
display: flex;
- animation-name: var(--sbb-loading-indicator-window-element-animation-name);
- animation-timing-function: linear;
- animation-iteration-count: infinite;
- animation-duration: var(--sbb-loading-indicator-duration);
+ animation: loading-container var(--sbb-loading-indicator-duration) linear infinite;
}
-:host([variant='window']) .sbb-loading-indicator__animated-element > span > span > span {
+.sbb-loading-indicator__animated-element > span > span > span {
background: var(--sbb-loading-indicator-color);
backface-visibility: hidden;
- outline: var(--sbb-border-width-1x) solid rgb(0 0 0 / 0%);
- width: var(--sbb-loading-indicator-window-last-span-width);
- height: var(--sbb-loading-indicator-window-last-span-height);
- margin-right: var(--sbb-loading-indicator-window-last-span-margin);
- transform: var(--sbb-loading-indicator-window-last-span-transform);
+ outline: var(--sbb-border-width-1x) solid transparent;
+ width: var(--_sbb-loading-indicator-window-first-span-width);
+ height: var(--sbb-loading-indicator-window-height);
+ margin-inline-end: var(--_sbb-loading-indicator-window-unit);
&:nth-child(1) {
animation: loading-rectangle1 var(--sbb-loading-indicator-duration) linear infinite;
@@ -200,27 +128,17 @@
}
&:last-child {
- margin-right: 0;
+ margin-inline-end: 0;
}
}
-@keyframes loading-container-small {
+@keyframes loading-container {
0% {
- transform: translateX(0.73333em);
+ translate: var(--_sbb-loading-indicator-window-element-animation-speed);
}
100% {
- transform: translateX(0);
- }
-}
-
-@keyframes loading-container-large {
- 0% {
- transform: translateX(1.2em);
- }
-
- 100% {
- transform: translateX(0);
+ translate: 0;
}
}
@@ -273,13 +191,3 @@
opacity: 0.25;
}
}
-
-@keyframes rotation {
- from {
- transform: rotate(0deg);
- }
-
- to {
- transform: rotate(359deg);
- }
-}
diff --git a/src/elements/loading-indicator/loading-indicator.snapshot.spec.ts b/src/elements/loading-indicator/loading-indicator.snapshot.spec.ts
index 68f295a3c1..d3d283fa32 100644
--- a/src/elements/loading-indicator/loading-indicator.snapshot.spec.ts
+++ b/src/elements/loading-indicator/loading-indicator.snapshot.spec.ts
@@ -4,6 +4,7 @@ import { html } from 'lit/static-html.js';
import { fixture, testA11yTreeSnapshot } from '../core/testing/private.js';
import type { SbbLoadingIndicatorElement } from './loading-indicator.js';
+
import './loading-indicator.js';
describe(`sbb-loading-indicator`, () => {
@@ -11,9 +12,7 @@ describe(`sbb-loading-indicator`, () => {
describe('renders with variant `window`', () => {
beforeEach(async () => {
- element = await fixture(
- html``,
- );
+ element = await fixture(html``);
});
it('DOM', async () => {
@@ -26,20 +25,4 @@ describe(`sbb-loading-indicator`, () => {
testA11yTreeSnapshot();
});
-
- describe('renders with variant `circle`', () => {
- beforeEach(async () => {
- element = await fixture(
- html``,
- );
- });
-
- it('DOM', async () => {
- await expect(element).dom.to.be.equalSnapshot();
- });
-
- it('Shadow DOM', async () => {
- await expect(element).shadowDom.to.be.equalSnapshot();
- });
- });
});
diff --git a/src/elements/loading-indicator/loading-indicator.stories.ts b/src/elements/loading-indicator/loading-indicator.stories.ts
index 259fd8b19f..aead89e1c7 100644
--- a/src/elements/loading-indicator/loading-indicator.stories.ts
+++ b/src/elements/loading-indicator/loading-indicator.stories.ts
@@ -1,5 +1,5 @@
import type { InputType, StoryContext } from '@storybook/types';
-import type { Meta, StoryObj, ArgTypes, Args } from '@storybook/web-components';
+import type { Args, ArgTypes, Meta, StoryObj } from '@storybook/web-components';
import type { TemplateResult } from 'lit';
import { html } from 'lit';
@@ -10,7 +10,6 @@ import readme from './readme.md?raw';
import './loading-indicator.js';
import '../button/button.js';
-import '../title.js';
import '../card.js';
const createLoadingIndicator = (event: Event, args: Args): void => {
@@ -20,7 +19,6 @@ const createLoadingIndicator = (event: Event, args: Args): void => {
)!;
loader.setAttribute('aria-label', 'Loading, please wait');
loader.size = args['size'];
- loader.variant = args['variant'];
container.append(loader);
setTimeout(() => {
const p = document.createElement('p');
@@ -38,32 +36,22 @@ const TemplateAccessibility = (args: Args): TemplateResult => html`
createLoadingIndicator(event, args)}>
Show loader
-
+
`;
const Template = (args: Args): TemplateResult => html`
`;
-const CircleTemplate = (args: Args): TemplateResult => html`
- Inline loading indicator
-
- Adaptive to font size
-
-`;
-
-const variant: InputType = {
- control: {
- type: 'select',
- },
- options: ['window', 'circle'],
-};
-
const size: InputType = {
control: {
type: 'inline-radio',
},
- options: ['s', 'l'],
+ options: ['s', 'l', 'xl', 'xxl', 'xxxl'],
};
const color: InputType = {
@@ -74,79 +62,21 @@ const color: InputType = {
};
const defaultArgTypes: ArgTypes = {
- variant,
size,
color,
};
const defaultArgs: Args = {
- variant: variant.options![0],
size: size.options![0],
color: color.options![0],
};
-export const WindowSmallDefault: StoryObj = {
+export const Default: StoryObj = {
render: Template,
argTypes: defaultArgTypes,
args: { ...defaultArgs },
};
-export const WindowSmallSmoke: StoryObj = {
- render: Template,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, color: color.options![1] },
-};
-
-export const WindowSmallWhite: StoryObj = {
- render: Template,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, color: color.options![2] },
-};
-
-export const WindowLargeDefault: StoryObj = {
- render: Template,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, size: size.options![1] },
-};
-
-export const WindowLargeSmoke: StoryObj = {
- render: Template,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, color: color.options![1], size: size.options![1] },
-};
-
-export const WindowLargeWhite: StoryObj = {
- render: Template,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, color: color.options![2], size: size.options![1] },
-};
-
-export const CircleDefault: StoryObj = {
- render: CircleTemplate,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, variant: variant.options![1] },
-};
-
-export const CircleSmoke: StoryObj = {
- render: CircleTemplate,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, color: color.options![1], variant: variant.options![1] },
-};
-
-export const CircleWhite: StoryObj = {
- render: CircleTemplate,
- argTypes: defaultArgTypes,
- args: { ...defaultArgs, color: color.options![2], variant: variant.options![1] },
- decorators: [
- (story) =>
- html`
- ${story()}
-
`,
- ],
-};
-
export const Accessibility: StoryObj = {
render: TemplateAccessibility,
argTypes: defaultArgTypes,
diff --git a/src/elements/loading-indicator/loading-indicator.ts b/src/elements/loading-indicator/loading-indicator.ts
index a65ec48dc2..90fe192514 100644
--- a/src/elements/loading-indicator/loading-indicator.ts
+++ b/src/elements/loading-indicator/loading-indicator.ts
@@ -1,5 +1,5 @@
import type { CSSResultGroup, TemplateResult } from 'lit';
-import { html, LitElement, nothing } from 'lit';
+import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { hostAttributes } from '../core/decorators.js';
@@ -18,11 +18,8 @@ export
class SbbLoadingIndicatorElement extends LitElement {
public static override styles: CSSResultGroup = style;
- /** Variant of the loading indicator; `circle` is meant to be used inline, while `window` as overlay. */
- @property({ reflect: true }) public accessor variant: 'window' | 'circle' = 'window';
-
/** Size variant, either s or m. */
- @property({ reflect: true }) public accessor size: 's' | 'l' = 's';
+ @property({ reflect: true }) public accessor size: 's' | 'l' | 'xl' | 'xxl' | 'xxxl' = 's';
/** Color variant. */
@property({ reflect: true }) public accessor color: 'default' | 'smoke' | 'white' = 'default';
@@ -31,17 +28,15 @@ class SbbLoadingIndicatorElement extends LitElement {
return html`
- ${this.variant === 'window'
- ? html`
-
-
-
-
-
-
-
- `
- : nothing}
+
+
+
+
+
+
+
+
+
`;
diff --git a/src/elements/loading-indicator/loading-indicator.visual.spec.ts b/src/elements/loading-indicator/loading-indicator.visual.spec.ts
index 239d3d775b..1be4e570db 100644
--- a/src/elements/loading-indicator/loading-indicator.visual.spec.ts
+++ b/src/elements/loading-indicator/loading-indicator.visual.spec.ts
@@ -1,4 +1,4 @@
-import { html, nothing } from 'lit';
+import { html } from 'lit';
import { describeEach, describeViewports, visualDiffDefault } from '../core/testing/private.js';
@@ -7,38 +7,16 @@ import './loading-indicator.js';
describe(`sbb-loading-indicator`, () => {
const cases = {
color: ['default', 'smoke', 'white'],
- size: ['s', 'l'],
+ size: ['s', 'l', 'xl', 'xxl', 'xxxl'],
};
describeViewports({ viewports: ['zero'] }, () => {
describeEach(cases, ({ color, size }) => {
it(
- `variant=window`,
+ '',
visualDiffDefault.with(async (setup) => {
await setup.withFixture(
- html`
-
- `,
- { backgroundColor: color === 'white' ? 'var(--sbb-color-charcoal)' : undefined },
- );
- }),
- );
-
- it(
- `variant=circle`,
- visualDiffDefault.with(async (setup) => {
- await setup.withFixture(
- html`
-
- `,
+ html``,
{ backgroundColor: color === 'white' ? 'var(--sbb-color-charcoal)' : undefined },
);
}),
diff --git a/src/elements/loading-indicator/readme.md b/src/elements/loading-indicator/readme.md
index 8b5ffb28c5..1d2cb4b853 100644
--- a/src/elements/loading-indicator/readme.md
+++ b/src/elements/loading-indicator/readme.md
@@ -1,32 +1,12 @@
The `sbb-loading-indicator` is a component which can be used to indicate progress status
or an ongoing activity which require some time to complete.
-### Variants
-
-The component has two different variants.
-
-In `window` mode, the component completely covers the parent element, preventing interaction with it.
-
-```html
-
-```
-
-While the `circle` mode can be used inline within another component (e.g. button);
-in this case the component adjusts its size to the parent font size.
-
-```html
-
-
- Click me
-
-```
-
### Style
-In `window` mode it's possible to define the `size` of the component, choosing between `s` (default) and `l`.
+It's possible to define the `size` of the component, choosing between `s` (default), `l`, `xl`, `xxl`, and `xxxl`.
```html
-
+
```
## Accessibility
@@ -37,11 +17,7 @@ and then append the `sbb-loading-indicator` on it after giving it the correct `a
```html
-
+
```
@@ -49,8 +25,7 @@ and then append the `sbb-loading-indicator` on it after giving it the correct `a
## Properties
-| Name | Attribute | Privacy | Type | Default | Description |
-| --------- | --------- | ------- | --------------------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
-| `color` | `color` | public | `'default' \| 'smoke' \| 'white'` | `'default'` | Color variant. |
-| `size` | `size` | public | `'s' \| 'l'` | `'s'` | Size variant, either s or m. |
-| `variant` | `variant` | public | `'window' \| 'circle'` | `'window'` | Variant of the loading indicator; `circle` is meant to be used inline, while `window` as overlay. |
+| Name | Attribute | Privacy | Type | Default | Description |
+| ------- | --------- | ------- | --------------------------------------- | ----------- | ---------------------------- |
+| `color` | `color` | public | `'default' \| 'smoke' \| 'white'` | `'default'` | Color variant. |
+| `size` | `size` | public | `'s' \| 'l' \| 'xl' \| 'xxl' \| 'xxxl'` | `'s'` | Size variant, either s or m. |