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 @@
+
+
+
+
+
+
+
+ Button
+
+
+ Button
+
+
+
+ Button
+
+
+
+
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'
]);