From 9fed7331da584bb45b70667f61b0eb1eae36f52d Mon Sep 17 00:00:00 2001 From: danielwiehl Date: Thu, 28 Sep 2023 17:29:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(=C9=B5components):=20add=20toggle=20button?= =?UTF-8?q?=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usage: ```html ``` --- apps/components/src/app/app.routes.ts | 5 ++ .../sci-toggle-button-page.component.html | 13 +++ .../sci-toggle-button-page.component.scss | 30 +++++++ .../sci-toggle-button-page.component.ts | 55 ++++++++++++ .../toggle-button/ng-package.json | 3 + .../toggle-button/src/public_api.ts | 16 ++++ .../src/toggle-button.component.html | 2 + .../src/toggle-button.component.scss | 75 +++++++++++++++++ .../src/toggle-button.component.ts | 83 +++++++++++++++++++ tsconfig.json | 3 + 10 files changed, 285 insertions(+) create mode 100644 apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.html create mode 100644 apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.scss create mode 100644 apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.ts create mode 100644 projects/scion/components.internal/toggle-button/ng-package.json create mode 100644 projects/scion/components.internal/toggle-button/src/public_api.ts create mode 100644 projects/scion/components.internal/toggle-button/src/toggle-button.component.html create mode 100644 projects/scion/components.internal/toggle-button/src/toggle-button.component.scss create mode 100644 projects/scion/components.internal/toggle-button/src/toggle-button.component.ts diff --git a/apps/components/src/app/app.routes.ts b/apps/components/src/app/app.routes.ts index 5f526cc3..5181792e 100644 --- a/apps/components/src/app/app.routes.ts +++ b/apps/components/src/app/app.routes.ts @@ -70,4 +70,9 @@ export const routes: Routes = [ loadComponent: () => import('./sci-throbber-page/sci-throbber-page.component'), data: {internal: false}, }, + { + path: 'sci-toggle-button', + loadComponent: () => import('./sci-toggle-button-page/sci-toggle-button-page.component'), + data: {internal: true}, + }, ]; diff --git a/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.html b/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.html new file mode 100644 index 00000000..23c75004 --- /dev/null +++ b/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.html @@ -0,0 +1,13 @@ +

sci-toggle-button (ɵ)

+ +
+
+
State
+ + + + +
+
+ + diff --git a/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.scss b/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.scss new file mode 100644 index 00000000..3b3e2a62 --- /dev/null +++ b/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.scss @@ -0,0 +1,30 @@ +:host { + display: flex; + flex-direction: column; + gap: 1em; + + > h1 { + flex: none; + } + + > form { + flex: none; + + > section.state { + display: flex; + flex-direction: column; + border: 1px solid var(--sci-color-border); + border-radius: var(--sci-corner); + padding: 1em; + + > header { + font-weight: bold; + margin-bottom: 1em; + } + } + } + + > sci-toggle-button { + flex: none; + } +} diff --git a/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.ts b/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.ts new file mode 100644 index 00000000..39ebfeb2 --- /dev/null +++ b/apps/components/src/app/sci-toggle-button-page/sci-toggle-button-page.component.ts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2023 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import {Component} from '@angular/core'; +import {NonNullableFormBuilder, ReactiveFormsModule} from '@angular/forms'; +import {SciFormFieldComponent} from '@scion/components.internal/form-field'; +import {SciToggleButtonComponent} from '@scion/components.internal/toggle-button'; +import {SciCheckboxComponent} from '@scion/components.internal/checkbox'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'sci-toggle-button-page', + templateUrl: './sci-toggle-button-page.component.html', + styleUrls: ['./sci-toggle-button-page.component.scss'], + standalone: true, + imports: [ + ReactiveFormsModule, + SciFormFieldComponent, + SciToggleButtonComponent, + SciCheckboxComponent, + ], +}) +export default class SciToggleButtonPageComponent { + + public form = this._formBuilder.group({ + toggleButton: this._formBuilder.control(true), + state: this._formBuilder.group({ + disabled: this._formBuilder.control(false), + }), + }); + + constructor(private _formBuilder: NonNullableFormBuilder) { + this.installToggleButtonDisabler(); + } + + private installToggleButtonDisabler(): void { + this.form.controls.state.controls.disabled.valueChanges + .pipe(takeUntilDestroyed()) + .subscribe(disabled => { + if (disabled) { + this.form.controls.toggleButton.disable(); + } + else { + this.form.controls.toggleButton.enable(); + } + }); + } +} diff --git a/projects/scion/components.internal/toggle-button/ng-package.json b/projects/scion/components.internal/toggle-button/ng-package.json new file mode 100644 index 00000000..d63c8cd7 --- /dev/null +++ b/projects/scion/components.internal/toggle-button/ng-package.json @@ -0,0 +1,3 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json" +} diff --git a/projects/scion/components.internal/toggle-button/src/public_api.ts b/projects/scion/components.internal/toggle-button/src/public_api.ts new file mode 100644 index 00000000..51657650 --- /dev/null +++ b/projects/scion/components.internal/toggle-button/src/public_api.ts @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2018-2019 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/* + * Secondary entrypoint: '@scion/components.internal/toggle-button' + * + * @see https://github.com/ng-packagr/ng-packagr/blob/master/docs/secondary-entrypoints.md + */ +export {SciToggleButtonComponent} from './toggle-button.component'; diff --git a/projects/scion/components.internal/toggle-button/src/toggle-button.component.html b/projects/scion/components.internal/toggle-button/src/toggle-button.component.html new file mode 100644 index 00000000..6eed526b --- /dev/null +++ b/projects/scion/components.internal/toggle-button/src/toggle-button.component.html @@ -0,0 +1,2 @@ + + diff --git a/projects/scion/components.internal/toggle-button/src/toggle-button.component.scss b/projects/scion/components.internal/toggle-button/src/toggle-button.component.scss new file mode 100644 index 00000000..8693a4d5 --- /dev/null +++ b/projects/scion/components.internal/toggle-button/src/toggle-button.component.scss @@ -0,0 +1,75 @@ +$size: 1.25rem; + +:host { + display: inline-grid; + border-radius: $size; + padding: 1px; + background-color: var(--sci-color-border); + width: 2 * $size; + height: $size; + box-sizing: content-box; + + > input[type=checkbox] { + all: unset; + height: 0; + width: 0; + } + + > label { // checkbox label filling the entire component + cursor: pointer; + + &:after { // on/off circle of the toggle button + content: ''; + display: block; + height: $size; + width: $size; + border-radius: 50%; + transition: transform 125ms ease-out; + background-color: var(--sci-color-accent-inverse); + border: 1px solid var(--sci-color-border); + box-sizing: border-box; + } + } + + &:has(> input:disabled) { + > label { + cursor: unset; + } + } + + > input:checked + label:after { + transform: translateX(100%); // "on" state + } + + &:has(> input:checked:not(:disabled)) { + background-color: var(--sci-color-accent); + + > label:after { // "on" state + border-color: var(--sci-color-accent); + } + } + + &:has(> input:checked:disabled) { + background-color: var(--sci-color-background-input-disabled); + + > label:after { // "on" state + background-color: var(--sci-color-gray-100); + border-color: var(--sci-color-gray-100); + } + } + + &:has(> input:not(:checked):disabled) { + background-color: var(--sci-color-background-input-disabled); + + > label:after { // "off" state + background-color: var(--sci-color-gray-100); + border-color: var(--sci-color-background-input-disabled); + } + } + + &:has(> input:focus-visible:not(:disabled) ) { + outline: 1px solid var(--sci-color-accent); + border-color: transparent; + background-clip: content-box; + } +} diff --git a/projects/scion/components.internal/toggle-button/src/toggle-button.component.ts b/projects/scion/components.internal/toggle-button/src/toggle-button.component.ts new file mode 100644 index 00000000..46373961 --- /dev/null +++ b/projects/scion/components.internal/toggle-button/src/toggle-button.component.ts @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018-2023 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import {Component, forwardRef} from '@angular/core'; +import {UUID} from '@scion/toolkit/uuid'; +import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {noop} from 'rxjs'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'sci-toggle-button', + templateUrl: './toggle-button.component.html', + styleUrls: ['./toggle-button.component.scss'], + standalone: true, + imports: [ + ReactiveFormsModule, + ], + providers: [ + {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => SciToggleButtonComponent)}, + ], +}) +export class SciToggleButtonComponent implements ControlValueAccessor { + + private _cvaChangeFn: (value: any) => void = noop; + private _cvaTouchedFn: () => void = noop; + + protected formControl = new FormControl(false); + protected id = UUID.randomUUID(); + + constructor() { + this.formControl.valueChanges + .pipe(takeUntilDestroyed()) + .subscribe(value => { + this._cvaChangeFn(value); + this._cvaTouchedFn(); + }); + } + + /** + * Method implemented as part of `ControlValueAccessor` to work with Angular forms API + * @docs-private + */ + public registerOnChange(fn: any): void { + this._cvaChangeFn = fn; + } + + /** + * Method implemented as part of `ControlValueAccessor` to work with Angular forms API + * @docs-private + */ + public registerOnTouched(fn: any): void { + this._cvaTouchedFn = fn; + } + + /** + * Method implemented as part of `ControlValueAccessor` to work with Angular forms API + * @docs-private + */ + public setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.formControl.disable(); + } + else { + this.formControl.enable(); + } + } + + /** + * Method implemented as part of `ControlValueAccessor` to work with Angular forms API + * @docs-private + */ + public writeValue(value: any): void { + this.formControl.setValue(coerceBooleanProperty(value), {emitEvent: false}); + } +} diff --git a/tsconfig.json b/tsconfig.json index 6100533b..1c8450b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -108,6 +108,9 @@ // ], // "@scion/components.internal/tabbar": [ // "projects/scion/components.internal/tabbar/src/public_api" + // ], + // "@scion/components.internal/toggle-button": [ + // "projects/scion/components.internal/toggle-button/src/public_api" // ] // } },