diff --git a/packages/angular-test-app/angular.json b/packages/angular-test-app/angular.json index c54a5f5a48f..ea051ff35d3 100644 --- a/packages/angular-test-app/angular.json +++ b/packages/angular-test-app/angular.json @@ -108,6 +108,7 @@ "cli": { "cache": { "enabled": false - } + }, + "analytics": false } } diff --git a/packages/angular-test-app/src/preview-examples/button-loading.ts b/packages/angular-test-app/src/preview-examples/button-loading.ts new file mode 100644 index 00000000000..1ef1bfdc817 --- /dev/null +++ b/packages/angular-test-app/src/preview-examples/button-loading.ts @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: 2023 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-example', + template: ` + + Button + + + + Button + + + + + + + Button + + + + `, +}) +export default class ButtonLoading { + loading = false; + loading2 = false; + loading3 = false; + + toggle() { + this.loading = true; + setTimeout(() => { + this.loading = false; + }, 2500); + } + + toggle2() { + this.loading2 = true; + setTimeout(() => { + this.loading2 = false; + }, 2500); + } + + toggle3() { + this.loading3 = true; + setTimeout(() => { + this.loading3 = false; + }, 2500); + } +} diff --git a/packages/angular/src/components.ts b/packages/angular/src/components.ts index 3f4c18ef120..de2faaacb09 100644 --- a/packages/angular/src/components.ts +++ b/packages/angular/src/components.ts @@ -206,14 +206,14 @@ export declare interface IxBreadcrumbItem extends Components.IxBreadcrumbItem {} @ProxyCmp({ - inputs: ['disabled', 'ghost', 'invisible', 'outline', 'selected', 'type', 'variant'] + inputs: ['disabled', 'ghost', 'icon', 'invisible', 'loading', 'outline', 'selected', 'type', 'variant'] }) @Component({ selector: 'ix-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'ghost', 'invisible', 'outline', 'selected', 'type', 'variant'], + inputs: ['disabled', 'ghost', 'icon', 'invisible', 'loading', 'outline', 'selected', 'type', 'variant'], }) export class IxButton { protected el: HTMLElement; @@ -1020,14 +1020,14 @@ export declare interface IxIcon extends Components.IxIcon {} @ProxyCmp({ - inputs: ['color', 'disabled', 'ghost', 'icon', 'invisible', 'outline', 'oval', 'selected', 'size', 'type', 'variant'] + inputs: ['color', 'disabled', 'ghost', 'icon', 'invisible', 'loading', 'outline', 'oval', 'selected', 'size', 'type', 'variant'] }) @Component({ selector: 'ix-icon-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'ghost', 'icon', 'invisible', 'outline', 'oval', 'selected', 'size', 'type', 'variant'], + inputs: ['color', 'disabled', 'ghost', 'icon', 'invisible', 'loading', 'outline', 'oval', 'selected', 'size', 'type', 'variant'], }) export class IxIconButton { protected el: HTMLElement; diff --git a/packages/core/component-doc.json b/packages/core/component-doc.json index dcb72b2a4e7..74211416871 100644 --- a/packages/core/component-doc.json +++ b/packages/core/component-doc.json @@ -327,6 +327,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-basic-navigation": [ @@ -388,6 +389,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -712,8 +714,15 @@ "ix-time-picker", "ix-upload" ], - "dependencies": [], + "dependencies": [ + "ix-spinner", + "ix-icon" + ], "dependencyGraph": { + "ix-button": [ + "ix-spinner", + "ix-icon" + ], "ix-card-list": [ "ix-button" ], @@ -777,6 +786,22 @@ "optional": false, "required": false }, + { + "name": "icon", + "type": "string", + "mutable": false, + "attr": "icon", + "reflectToAttr": false, + "docs": "Icon name", + "docsTags": [], + "values": [ + { + "type": "string" + } + ], + "optional": false, + "required": false + }, { "name": "invisible", "type": "boolean", @@ -800,6 +825,23 @@ "optional": false, "required": false }, + { + "name": "loading", + "type": "boolean", + "mutable": false, + "attr": "loading", + "reflectToAttr": false, + "docs": "Loading button", + "docsTags": [], + "default": "false", + "values": [ + { + "type": "boolean" + } + ], + "optional": false, + "required": false + }, { "name": "outline", "type": "boolean", @@ -1092,6 +1134,11 @@ "ix-button" ], "ix-icon-button": [ + "ix-spinner", + "ix-icon" + ], + "ix-button": [ + "ix-spinner", "ix-icon" ] }, @@ -1331,6 +1378,7 @@ "ix-dropdown" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-filter-chip": [ @@ -1775,6 +1823,7 @@ "ix-typography" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -2036,6 +2085,11 @@ "ix-icon" ], "ix-icon-button": [ + "ix-spinner", + "ix-icon" + ], + "ix-button": [ + "ix-spinner", "ix-icon" ], "ix-datetime-picker": [ @@ -2367,6 +2421,11 @@ "ix-icon" ], "ix-icon-button": [ + "ix-spinner", + "ix-icon" + ], + "ix-button": [ + "ix-spinner", "ix-icon" ], "ix-time-picker": [ @@ -2796,6 +2855,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -3317,7 +3377,12 @@ "ix-icon-button", "ix-dropdown" ], + "ix-button": [ + "ix-spinner", + "ix-icon" + ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -3678,6 +3743,10 @@ "ix-icon", "ix-typography", "ix-button" + ], + "ix-button": [ + "ix-spinner", + "ix-icon" ] }, "props": [ @@ -4049,6 +4118,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -4168,6 +4238,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-category-filter": [ @@ -4234,6 +4305,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -4399,6 +4471,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-group-item": [ @@ -4587,6 +4660,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-group": [ @@ -4868,10 +4942,12 @@ "ix-toast" ], "dependencies": [ + "ix-spinner", "ix-icon" ], "dependencyGraph": { "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-burger-menu": [ @@ -4997,7 +5073,7 @@ "mutable": false, "attr": "icon", "reflectToAttr": false, - "docs": "Button icon", + "docs": "Icon name", "docsTags": [], "values": [ { @@ -5029,6 +5105,23 @@ "optional": false, "required": false }, + { + "name": "loading", + "type": "boolean", + "mutable": false, + "attr": "loading", + "reflectToAttr": false, + "docs": "Loading button", + "docsTags": [], + "default": "false", + "values": [ + { + "type": "boolean" + } + ], + "optional": false, + "required": false + }, { "name": "outline", "type": "boolean", @@ -5500,6 +5593,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-application-header": [ @@ -5682,6 +5776,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-map-navigation": [ @@ -5781,6 +5876,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-menu-item": [ @@ -6212,6 +6308,7 @@ "ix-tabs" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-tabs": [ @@ -6354,6 +6451,11 @@ "ix-button" ], "ix-icon-button": [ + "ix-spinner", + "ix-icon" + ], + "ix-button": [ + "ix-spinner", "ix-icon" ] }, @@ -6998,6 +7100,7 @@ "ix-tabs" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-tabs": [ @@ -7136,6 +7239,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -7666,6 +7770,11 @@ "ix-modal-container" ], "ix-icon-button": [ + "ix-spinner", + "ix-icon" + ], + "ix-button": [ + "ix-spinner", "ix-icon" ], "ix-modal-container": [ @@ -7715,6 +7824,7 @@ "ix-select-item" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-select": [ @@ -8256,6 +8366,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-pagination": [ @@ -8629,10 +8740,18 @@ "docsTags": [], "encapsulation": "shadow", "dependents": [ + "ix-button", + "ix-icon-button", "ix-upload" ], "dependencies": [], "dependencyGraph": { + "ix-button": [ + "ix-spinner" + ], + "ix-icon-button": [ + "ix-spinner" + ], "ix-upload": [ "ix-spinner" ] @@ -8640,7 +8759,7 @@ "props": [ { "name": "size", - "type": "\"large\" | \"medium\"", + "type": "\"large\" | \"medium\" | \"small\"", "mutable": false, "attr": "size", "reflectToAttr": false, @@ -8655,6 +8774,10 @@ { "value": "medium", "type": "string" + }, + { + "value": "small", + "type": "string" } ], "optional": false, @@ -8662,7 +8785,7 @@ }, { "name": "variant", - "type": "\"primary\" | \"secondary\" | \"sencodary\"", + "type": "\"primary\" | \"secondary\"", "mutable": false, "attr": "variant", "reflectToAttr": false, @@ -8677,10 +8800,6 @@ { "value": "secondary", "type": "string" - }, - { - "value": "sencodary", - "type": "string" } ], "optional": false, @@ -9428,6 +9547,11 @@ "ix-button" ], "ix-icon-button": [ + "ix-spinner", + "ix-icon" + ], + "ix-button": [ + "ix-spinner", "ix-icon" ], "ix-datetime-picker": [ @@ -9709,6 +9833,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ], "ix-toast-container": [ @@ -9871,6 +9996,7 @@ "ix-icon-button" ], "ix-icon-button": [ + "ix-spinner", "ix-icon" ] }, @@ -10539,6 +10665,10 @@ "ix-upload": [ "ix-spinner", "ix-button" + ], + "ix-button": [ + "ix-spinner", + "ix-icon" ] }, "props": [ diff --git a/packages/core/scss/components/_buttons.scss b/packages/core/scss/components/_buttons.scss index f00acb273c9..542b9dfc64f 100644 --- a/packages/core/scss/components/_buttons.scss +++ b/packages/core/scss/components/_buttons.scss @@ -52,6 +52,8 @@ $button-categories: (primary, secondary); background-color: var(--theme-btn-#{$name}--background); color: var(--theme-btn-#{$name}--color); + --ix-button-color: var(--theme-btn-#{$name}--color); + @if $enable-border { border-width: var(--theme-btn--border-thickness); border-color: var(--theme-btn-#{$name}--border-color); @@ -126,6 +128,8 @@ $button-categories: (primary, secondary); background-color: var(--theme-btn-#{$name}--background--disabled); color: var(--theme-btn-#{$name}--color--disabled); opacity: 1; + + --ix-button-color: var(--theme-btn-#{$name}--color--disabled); } } } diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index bb1a39a10ef..ccb458ad5e4 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -189,11 +189,19 @@ export namespace Components { * Button with no background or outline */ "ghost": boolean; + /** + * Icon name + */ + "icon": string; /** * Invisible button * @deprecated use ghost property */ "invisible": boolean; + /** + * Loading button + */ + "loading": boolean; /** * Outline button */ @@ -919,7 +927,7 @@ export namespace Components { */ "ghost": boolean; /** - * Button icon + * Icon name */ "icon": string; /** @@ -927,6 +935,10 @@ export namespace Components { * @deprecated Use ghost property */ "invisible": boolean; + /** + * Loading button + */ + "loading": boolean; /** * Button outline */ @@ -1522,14 +1534,15 @@ export namespace Components { "value": any; } interface IxSpinner { + "hideTrack": boolean; /** * Size of spinner */ - "size": 'medium' | 'large'; + "size": 'small' | 'medium' | 'large'; /** * Variant of spinner */ - "variant": 'primary' | 'sencodary' | 'secondary'; + "variant": 'primary' | 'secondary'; } interface IxSplitButton { /** @@ -2910,11 +2923,19 @@ declare namespace LocalJSX { * Button with no background or outline */ "ghost"?: boolean; + /** + * Icon name + */ + "icon"?: string; /** * Invisible button * @deprecated use ghost property */ "invisible"?: boolean; + /** + * Loading button + */ + "loading"?: boolean; /** * Outline button */ @@ -3749,7 +3770,7 @@ declare namespace LocalJSX { */ "ghost"?: boolean; /** - * Button icon + * Icon name */ "icon"?: string; /** @@ -3757,6 +3778,10 @@ declare namespace LocalJSX { * @deprecated Use ghost property */ "invisible"?: boolean; + /** + * Loading button + */ + "loading"?: boolean; /** * Button outline */ @@ -4375,14 +4400,15 @@ declare namespace LocalJSX { "value": any; } interface IxSpinner { + "hideTrack"?: boolean; /** * Size of spinner */ - "size"?: 'medium' | 'large'; + "size"?: 'small' | 'medium' | 'large'; /** * Variant of spinner */ - "variant"?: 'primary' | 'sencodary' | 'secondary'; + "variant"?: 'primary' | 'secondary'; } interface IxSplitButton { /** diff --git a/packages/core/src/components/button/button.scss b/packages/core/src/components/button/button.scss index 69a94707da4..0581ef76476 100644 --- a/packages/core/src/components/button/button.scss +++ b/packages/core/src/components/button/button.scss @@ -27,6 +27,10 @@ button:not(:disabled) { cursor: pointer; } + + ix-spinner { + margin-right: 0.25rem; + } } :host(.disabled) { diff --git a/packages/core/src/components/button/button.tsx b/packages/core/src/components/button/button.tsx index 479cb816e6e..68eba853a55 100644 --- a/packages/core/src/components/button/button.tsx +++ b/packages/core/src/components/button/button.tsx @@ -55,6 +55,16 @@ export class Button { */ @Prop() type: 'button' | 'submit' = 'button'; + /** + * Loading button + */ + @Prop() loading: boolean = false; + + /** + * Icon name + */ + @Prop() icon: string; + @Element() hostElement: HTMLIxButtonElement; /** @@ -97,9 +107,15 @@ export class Button { false, false, this.selected, - this.disabled + this.disabled || this.loading )} > + {this.loading ? ( + + ) : null} + {this.icon && !this.loading ? ( + + ) : null} diff --git a/packages/core/src/components/button/test/button.ct.ts b/packages/core/src/components/button/test/button.ct.ts new file mode 100644 index 00000000000..6def798501a --- /dev/null +++ b/packages/core/src/components/button/test/button.ct.ts @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2023 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { expect } from '@playwright/test'; +import { test } from '@utils/test'; + +test('renders', async ({ mount, page }) => { + await mount(`Content`); + const button = page.locator('ix-button'); + await expect(button).toHaveClass(/hydrated/); +}); + +test('show icon', async ({ mount, page }) => { + await mount(`Content`); + const button = page.locator('ix-button'); + await expect(button.locator('ix-icon')).toBeVisible(); +}); + +test('show spinner while loading', async ({ mount, page }) => { + await mount(`Content`); + const button = page.locator('ix-button'); + + await expect(button.locator('ix-spinner')).not.toBeVisible(); + await button.evaluate((btn: HTMLIxButtonElement) => (btn.loading = true)); + await expect(button.locator('ix-spinner')).toBeVisible(); +}); + +test('replace icon with spinner while loading', async ({ mount, page }) => { + await mount(`Content`); + const button = page.locator('ix-button'); + + await expect(button.locator('ix-spinner')).not.toBeVisible(); + await button.evaluate((btn: HTMLIxButtonElement) => (btn.loading = true)); + await expect(button.locator('ix-spinner')).toBeVisible(); + await expect(button.locator('ix-icon')).not.toBeVisible(); +}); diff --git a/packages/core/src/components/icon-button/icon-button.scss b/packages/core/src/components/icon-button/icon-button.scss index 604595a3294..9a09b8e5aef 100644 --- a/packages/core/src/components/icon-button/icon-button.scss +++ b/packages/core/src/components/icon-button/icon-button.scss @@ -24,6 +24,27 @@ } } +:host(.btn-icon-12) { + ix-spinner { + height: 12px; + width: 12px; + } +} + +:host(.btn-icon-16) { + ix-spinner { + height: 16px; + width: 16px; + } +} + +:host(.btn-icon-32) { + ix-spinner { + height: 24px; + width: 24px; + } +} + :host(.disabled) { pointer-events: none; } diff --git a/packages/core/src/components/icon-button/icon-button.tsx b/packages/core/src/components/icon-button/icon-button.tsx index a8a3abbb85b..c2a494f6447 100644 --- a/packages/core/src/components/icon-button/icon-button.tsx +++ b/packages/core/src/components/icon-button/icon-button.tsx @@ -49,7 +49,7 @@ export class IconButton implements Button { @Prop() oval: boolean; /** - * Button icon + * Icon name */ @Prop() icon: string; @@ -78,6 +78,11 @@ export class IconButton implements Button { */ @Prop() type: 'button' | 'submit' = 'button'; + /** + * Loading button + */ + @Prop() loading = false; + /** * Temp. workaround until stencil issue is fixed (https://github.com/ionic-team/stencil/issues/2284) */ @@ -118,7 +123,7 @@ export class IconButton implements Button { true, this.oval, this.selected, - this.disabled + this.disabled || this.loading ), 'icon-button': true, ...this.getIconSizeClass(), @@ -133,10 +138,16 @@ export class IconButton implements Button { type={this.type} onClick={() => this.dispatchFormEvents()} > - -
- -
+ {this.loading ? ( + + ) : null} + {this.icon && !this.loading ? ( + + ) : null} ); diff --git a/packages/core/src/components/icon-button/test/icon-button.ct.ts b/packages/core/src/components/icon-button/test/icon-button.ct.ts new file mode 100644 index 00000000000..ced00773c2c --- /dev/null +++ b/packages/core/src/components/icon-button/test/icon-button.ct.ts @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2023 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { expect } from '@playwright/test'; +import { test } from '@utils/test'; + +test('renders', async ({ mount, page }) => { + await mount(`Content`); + const button = page.locator('ix-icon-button'); + await expect(button).toHaveClass(/hydrated/); + expect(button.allInnerTexts).not.toEqual('Content'); + + await expect(button.locator('ix-icon')).toBeVisible(); + await expect(button.locator('ix-icon')).toHaveAttribute('name', 'rocket'); +}); + +test('show spinner while loading', async ({ mount, page }) => { + await mount(``); + const button = page.locator('ix-icon-button'); + + await expect(button.locator('ix-spinner')).not.toBeVisible(); + await button.evaluate((btn: HTMLIxButtonElement) => (btn.loading = true)); + await expect(button.locator('ix-spinner')).toBeVisible(); +}); diff --git a/packages/core/src/components/spinner/spinner.scss b/packages/core/src/components/spinner/spinner.scss index 4b295e17822..ede922f6329 100644 --- a/packages/core/src/components/spinner/spinner.scss +++ b/packages/core/src/components/spinner/spinner.scss @@ -9,46 +9,98 @@ @import 'mixins/shadow-dom/component'; -:host { - @include ix-component; - - display: flex; - position: relative; - height: 2rem; - width: 2rem; - justify-content: center; - align-items: center; - - svg { - height: 2rem; - width: 2rem; - } +@mixin ix-spinner($host-class: 'medium', $size: 2rem, $track-size: 4px) { + :host(.#{$host-class}) { + @include ix-component; + display: block; + position: relative; - #fill { - fill: var(--theme-color-weak-text); - } + width: $size; + height: $size; + border-radius: 100%; + position: relative; + animation: rotate 2s linear infinite; - #stroke { - stroke: var(--theme-color-weak-text); + @keyframes rotate { + 100% { + transform: rotate(360deg); + } + } + + @keyframes clipMask { + 0% { + clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0); + } + 12.5% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0); + } + 25% { + clip-path: polygon( + 50% 50%, + 0 0, + 100% 0, + 100% 100%, + 100% 100%, + 100% 100% + ); + } + 37.5% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%); + } + 50% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0); + } + 62.5% { + clip-path: polygon(50% 50%, 100% 0, 100% 0, 100% 100%, 0 100%, 0 0); + } + 75% { + clip-path: polygon( + 50% 50%, + 100% 100%, + 100% 100%, + 100% 100%, + 0 100%, + 0 0 + ); + } + 87.5% { + clip-path: polygon(50% 50%, 0 100%, 0 100%, 0 100%, 0 100%, 0 0); + } + 100% { + clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0); + } + } } -} -:host(.primary) { - #fill { - fill: var(--theme-color-primary); + :host(.#{$host-class})::after { + content: ''; + box-sizing: border-box; + position: absolute; + inset: 8.33%; + border-radius: 100%; + border: $track-size solid + var(--ix-button-color, var(--theme-color-std-text)); + animation: clipMask 3s linear infinite; } - #stroke { - stroke: var(--theme-color-primary); + :host(:not(.hide-track)):host(.#{$host-class})::before { + content: ''; + box-sizing: border-box; + position: absolute; + inset: 8.33%; + border-radius: 100%; + border: $track-size solid var(--theme-color-ghost--hover); } -} -:host(.large) { - height: 6rem; - width: 6rem; + :host(.primary)::after { + border-color: var(--theme-color-dynamic); + } - svg { - height: 5.75rem; - width: 6rem; + :host(.primary)::before { + border-color: var(--theme-color-ghost--hover); } } + +@include ix-spinner($host-class: 'small', $size: 1.5rem, $track-size: 2px); +@include ix-spinner($host-class: 'medium', $size: 2rem, $track-size: 2px); +@include ix-spinner($host-class: 'large', $size: 6rem, $track-size: 4px); diff --git a/packages/core/src/components/spinner/spinner.tsx b/packages/core/src/components/spinner/spinner.tsx index c490cad7b40..3b8ca47d870 100644 --- a/packages/core/src/components/spinner/spinner.tsx +++ b/packages/core/src/components/spinner/spinner.tsx @@ -7,7 +7,7 @@ * LICENSE file in the root directory of this source tree. */ -import { Component, h, Host, Prop } from '@stencil/core'; +import { Component, Element, h, Host, Prop } from '@stencil/core'; @Component({ tag: 'ix-spinner', @@ -15,452 +15,38 @@ import { Component, h, Host, Prop } from '@stencil/core'; shadow: true, }) export class Spinner { + @Element() hostElement!: HTMLIxSpinnerElement; + /** * Variant of spinner */ - @Prop() variant: 'primary' | 'sencodary' | 'secondary' = 'secondary'; + @Prop() variant: 'primary' | 'secondary' = 'secondary'; /** * Size of spinner */ - @Prop() size: 'medium' | 'large' = 'medium'; + @Prop() size: 'small' | 'medium' | 'large' = 'medium'; + + /** + * @internal + */ + @Prop() hideTrack = false; + + componentDidRender() { + console.log(this.hostElement.clientWidth); + } render() { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + > ); } } diff --git a/packages/core/src/tests/button/button.e2e.ts b/packages/core/src/tests/button/button.e2e.ts index 9f3581cd944..00341d24fd0 100644 --- a/packages/core/src/tests/button/button.e2e.ts +++ b/packages/core/src/tests/button/button.e2e.ts @@ -11,6 +11,11 @@ import { expect } from '@playwright/test'; import { regressionTest } from '@utils/test'; regressionTest.describe('button: basic', () => { + regressionTest('should show loading spinner', async ({ page }) => { + await page.goto('button/loading'); + expect(await page.screenshot({ animations: 'disabled' })).toMatchSnapshot(); + }); + regressionTest('should not have visual regressions', async ({ page }) => { await page.goto('button/basic'); expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(); diff --git a/packages/core/src/tests/button/button.e2e.ts-snapshots/button-basic-should-show-loading-spinner-1-chromium---theme-classic-dark-linux.png b/packages/core/src/tests/button/button.e2e.ts-snapshots/button-basic-should-show-loading-spinner-1-chromium---theme-classic-dark-linux.png new file mode 100644 index 00000000000..69c558508de Binary files /dev/null and b/packages/core/src/tests/button/button.e2e.ts-snapshots/button-basic-should-show-loading-spinner-1-chromium---theme-classic-dark-linux.png differ diff --git a/packages/core/src/tests/button/button.e2e.ts-snapshots/button-basic-should-show-loading-spinner-1-chromium---theme-classic-light-linux.png b/packages/core/src/tests/button/button.e2e.ts-snapshots/button-basic-should-show-loading-spinner-1-chromium---theme-classic-light-linux.png new file mode 100644 index 00000000000..1f06c558dbb Binary files /dev/null and b/packages/core/src/tests/button/button.e2e.ts-snapshots/button-basic-should-show-loading-spinner-1-chromium---theme-classic-light-linux.png differ diff --git a/packages/core/src/tests/button/loading/index.html b/packages/core/src/tests/button/loading/index.html new file mode 100644 index 00000000000..a83e5eaaf5f --- /dev/null +++ b/packages/core/src/tests/button/loading/index.html @@ -0,0 +1,37 @@ + + + + + + + Stencil Component Starter + + + + Hallo + Hallo + Hallo + Hallo + Hallo + Hallo + + Hallo + Hallo + Hallo + Hallo + Hallo + Hallo + + + diff --git a/packages/core/src/tests/icon-button/icon-button.e2e.ts b/packages/core/src/tests/icon-button/icon-button.e2e.ts index cf8649bd1de..301cd8dc1db 100644 --- a/packages/core/src/tests/icon-button/icon-button.e2e.ts +++ b/packages/core/src/tests/icon-button/icon-button.e2e.ts @@ -15,4 +15,9 @@ regressionTest.describe('icon-button', () => { await page.goto('icon-button/basic'); expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(); }); + + regressionTest('should show loading spinner', async ({ page }) => { + await page.goto('icon-button/loading'); + expect(await page.screenshot({ animations: 'disabled' })).toMatchSnapshot(); + }); }); diff --git a/packages/core/src/tests/icon-button/icon-button.e2e.ts-snapshots/icon-button-should-show-loading-spinner-1-chromium---theme-classic-dark-linux.png b/packages/core/src/tests/icon-button/icon-button.e2e.ts-snapshots/icon-button-should-show-loading-spinner-1-chromium---theme-classic-dark-linux.png new file mode 100644 index 00000000000..ad62d8af776 Binary files /dev/null and b/packages/core/src/tests/icon-button/icon-button.e2e.ts-snapshots/icon-button-should-show-loading-spinner-1-chromium---theme-classic-dark-linux.png differ diff --git a/packages/core/src/tests/icon-button/icon-button.e2e.ts-snapshots/icon-button-should-show-loading-spinner-1-chromium---theme-classic-light-linux.png b/packages/core/src/tests/icon-button/icon-button.e2e.ts-snapshots/icon-button-should-show-loading-spinner-1-chromium---theme-classic-light-linux.png new file mode 100644 index 00000000000..94996cebf2d Binary files /dev/null and b/packages/core/src/tests/icon-button/icon-button.e2e.ts-snapshots/icon-button-should-show-loading-spinner-1-chromium---theme-classic-light-linux.png differ diff --git a/packages/core/src/tests/icon-button/loading/index.html b/packages/core/src/tests/icon-button/loading/index.html new file mode 100644 index 00000000000..6bab8bcdf1f --- /dev/null +++ b/packages/core/src/tests/icon-button/loading/index.html @@ -0,0 +1,294 @@ + + + + + + + Stencil Component Starter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/core/src/tests/spinner/basic/index.html b/packages/core/src/tests/spinner/basic/index.html new file mode 100644 index 00000000000..c1798bcc3b3 --- /dev/null +++ b/packages/core/src/tests/spinner/basic/index.html @@ -0,0 +1,28 @@ + + + + + + + Stencil Component Starter + + + + + + + + + + + + + + diff --git a/packages/core/src/tests/spinner/spinner.e2e.ts b/packages/core/src/tests/spinner/spinner.e2e.ts new file mode 100644 index 00000000000..4948a92c8af --- /dev/null +++ b/packages/core/src/tests/spinner/spinner.e2e.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2023 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { expect } from '@playwright/test'; +import { regressionTest } from '@utils/test'; + +regressionTest.describe('spinner', () => { + regressionTest('basic', async ({ page }) => { + await page.goto('spinner/basic'); + expect(await page.screenshot({ animations: 'disabled' })).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/tests/spinner/spinner.e2e.ts-snapshots/spinner-basic-1-chromium---theme-classic-dark-linux.png b/packages/core/src/tests/spinner/spinner.e2e.ts-snapshots/spinner-basic-1-chromium---theme-classic-dark-linux.png new file mode 100644 index 00000000000..b1790b59892 Binary files /dev/null and b/packages/core/src/tests/spinner/spinner.e2e.ts-snapshots/spinner-basic-1-chromium---theme-classic-dark-linux.png differ diff --git a/packages/core/src/tests/spinner/spinner.e2e.ts-snapshots/spinner-basic-1-chromium---theme-classic-light-linux.png b/packages/core/src/tests/spinner/spinner.e2e.ts-snapshots/spinner-basic-1-chromium---theme-classic-light-linux.png new file mode 100644 index 00000000000..56e0944882c Binary files /dev/null and b/packages/core/src/tests/spinner/spinner.e2e.ts-snapshots/spinner-basic-1-chromium---theme-classic-light-linux.png differ diff --git a/packages/documentation/docs/controls/_button_code.md b/packages/documentation/docs/controls/_button_code.md index 89cf033f77d..ecea1384f26 100644 --- a/packages/documentation/docs/controls/_button_code.md +++ b/packages/documentation/docs/controls/_button_code.md @@ -12,6 +12,7 @@ import WebComponentButtonTextIcon from './../auto-generated/previews/web-compone import WebComponentButtonGroup from './../auto-generated/previews/web-component/button-group.md' import WebComponentButtonIcon from './../auto-generated/previews/web-component/button-with-icon.md' import WebComponentButtonSelected from './../auto-generated/previews/web-component/button-selected.md' +import WebComponentButtonLoading from './../auto-generated/previews/web-component/button-loading.md' import ReactButtons from './../auto-generated/previews/react/buttons.md' import ReactButtonGroup from './../auto-generated/previews/react/button-group.md' @@ -23,6 +24,7 @@ import ReactButtonGrey from './../auto-generated/previews/react/button-grey.md' import ReactButtonGreySecondary from './../auto-generated/previews/react/button-grey-secondary.md' import ReactButtonGreyGhost from './../auto-generated/previews/react/button-grey-ghost.md' import ReactButtonTextIcon from './../auto-generated/previews/react/button-text-icon.md' +import ReactButtonLoading from './../auto-generated/previews/react/button-loading.md' import AngularButtons from './../auto-generated/previews/angular/buttons.ts.md' import AngularButtonGroup from './../auto-generated/previews/angular/button-group.ts.md' @@ -34,6 +36,7 @@ import AngularButtonGrey from './../auto-generated/previews/angular/button-grey. import AngularButtonGreySecondary from './../auto-generated/previews/angular/button-grey-secondary.ts.md' import AngularButtonGreyGhost from './../auto-generated/previews/angular/button-grey-ghost.ts.md' import AngularButtonTextIcon from './../auto-generated/previews/angular/button-text-icon.ts.md' +import AngularButtonLoading from './../auto-generated/previews/angular/button-loading.ts.md' import VueButtons from './../auto-generated/previews/vue/buttons.md' import VueButtonGroup from './../auto-generated/previews/vue/button-group.md' @@ -45,6 +48,7 @@ import VueButtonGrey from './../auto-generated/previews/vue/button-grey.md' import VueButtonGreySecondary from './../auto-generated/previews/vue/button-grey-secondary.md' import VueButtonGreyGhost from './../auto-generated/previews/vue/button-grey-ghost.md' import VueButtonTextIcon from './../auto-generated/previews/vue/button-text-icon.md' +import VueButtonLoading from './../auto-generated/previews/vue/button-loading.md' ## Usage @@ -204,6 +208,18 @@ frameworks={{ vue: VueButtonIcon }}> +### Loading button + + + ## Properties (ix-button) diff --git a/packages/html-test-app/src/preview-examples/button-loading.html b/packages/html-test-app/src/preview-examples/button-loading.html new file mode 100644 index 00000000000..a8b48ecec0d --- /dev/null +++ b/packages/html-test-app/src/preview-examples/button-loading.html @@ -0,0 +1,46 @@ + + + + + + + Loading button + + + + + + + + Button + + + + Button + + + + + + + Button + + + + + + + + \ No newline at end of file diff --git a/packages/react-test-app/src/preview-examples/button-loading.tsx b/packages/react-test-app/src/preview-examples/button-loading.tsx new file mode 100644 index 00000000000..e4849d02f47 --- /dev/null +++ b/packages/react-test-app/src/preview-examples/button-loading.tsx @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2023 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { IxButton, IxIconButton } from '@siemens/ix-react'; +import React, { useState } from 'react'; + +export default () => { + const [toggle, setToggle] = useState(false); + const [toggle2, setToggle2] = useState(false); + const [toggle3, setToggle3] = useState(false); + return ( + <> + { + setToggle(true); + setTimeout(() => { + setToggle(false); + }, 2500); + }} + loading={toggle} + class="m-1" + outline + variant="Primary" + > + Button + + + { + setToggle2(true); + setTimeout(() => { + setToggle2(false); + }, 2500); + }} + loading={toggle2} + class="m-1" + outline + icon="star" + variant="Primary" + > + Button + + + { + setToggle3(true); + setTimeout(() => { + setToggle3(false); + }, 2500); + }} + loading={toggle3} + class="m-1" + outline + icon="star" + variant="Primary" + > + + { + setToggle(true); + setTimeout(() => { + setToggle(false); + }, 2500); + }} + loading={toggle} + class="m-1" + outline + icon="star" + variant="Primary" + > + + Button + + + + ); +}; diff --git a/packages/vue-test-app/src/preview-examples/button-loading.vue b/packages/vue-test-app/src/preview-examples/button-loading.vue new file mode 100644 index 00000000000..161f8500684 --- /dev/null +++ b/packages/vue-test-app/src/preview-examples/button-loading.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/packages/vue/src/components.ts b/packages/vue/src/components.ts index b3eafd8dfcd..0e2e4fab127 100644 --- a/packages/vue/src/components.ts +++ b/packages/vue/src/components.ts @@ -74,7 +74,9 @@ export const IxButton = /*@__PURE__*/ defineContainer('ix-button', 'ghost', 'selected', 'disabled', - 'type' + 'type', + 'loading', + 'icon' ]); @@ -363,7 +365,8 @@ export const IxIconButton = /*@__PURE__*/ defineContainer('ix- 'color', 'selected', 'disabled', - 'type' + 'type', + 'loading' ]); @@ -601,7 +604,8 @@ export const IxSelectItem = /*@__PURE__*/ defineContainer('ix- export const IxSpinner = /*@__PURE__*/ defineContainer('ix-spinner', undefined, [ 'variant', - 'size' + 'size', + 'hideTrack' ]);