-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
635 additions
and
29 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export interface ComponentGeneratorSchema { | ||
components: ('avatar' | 'badge' | 'pin' | 'tag')[]; | ||
components: ('avatar' | 'badge' | 'pin' | 'switch' | 'tag')[]; | ||
path: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { NgModule } from '@angular/core'; | ||
|
||
import { ZenSwitchComponent } from './zen-switch.component'; | ||
|
||
@NgModule({ | ||
imports: [ZenSwitchComponent], | ||
exports: [ZenSwitchComponent], | ||
}) | ||
export class ZenSwitchModule {} | ||
export * from './zen-switch.component'; |
11 changes: 11 additions & 0 deletions
11
projects/cli/schematics/components/files/switch/zen-switch.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<button | ||
class="switch" | ||
[attr.aria-checked]="checked()" | ||
[attr.aria-disabled]="disabled()" | ||
[disabled]="disabled()" | ||
(click)="onToggle()" | ||
(keydown)="onKeyDown($event)" | ||
role="switch" | ||
> | ||
<span class="slider"></span> | ||
</button> |
62 changes: 62 additions & 0 deletions
62
projects/cli/schematics/components/files/switch/zen-switch.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Define SCSS variables for better maintainability | ||
$switch-width: 36px; | ||
$switch-height: 22px; | ||
$slider-size: 16px; | ||
$transition-duration: 0.4s; | ||
$switch-bg-color: grey; | ||
$switch-checked-bg-color: green; | ||
$switch-disabled-bg-color: lightgrey; | ||
$slider-bg-color: white; | ||
|
||
// Other | ||
$slider-padding: calc(($switch-height - $slider-size) / 2); | ||
$slider-transform-distance: calc( | ||
$switch-width - $slider-size - $slider-padding * 2 | ||
); | ||
|
||
/* Switch container */ | ||
.switch { | ||
position: relative; | ||
display: inline-block; | ||
width: $switch-width; | ||
height: $switch-height; | ||
background-color: $switch-bg-color; | ||
border: none; | ||
border-radius: 99999px; // force perfectly rounded | ||
cursor: pointer; | ||
transition: background-color $transition-duration; | ||
|
||
/* | ||
outline: none; | ||
&:focus { | ||
box-shadow: 0 0 3px 2px rgba(21, 156, 228, 0.4); | ||
} | ||
*/ | ||
} | ||
|
||
/* Slider */ | ||
.slider { | ||
position: absolute; | ||
top: $slider-padding; | ||
left: $slider-padding; | ||
height: $slider-size; | ||
width: $slider-size; | ||
background-color: $slider-bg-color; | ||
transition: transform $transition-duration; | ||
border-radius: 50%; | ||
} | ||
|
||
/* Styles when the button is checked */ | ||
.switch[aria-checked='true'] { | ||
background-color: $switch-checked-bg-color; | ||
|
||
.slider { | ||
transform: translateX($slider-transform-distance); | ||
} | ||
} | ||
|
||
/* Styles when the button is disabled */ | ||
.switch[aria-disabled='true'] { | ||
background-color: $switch-disabled-bg-color; | ||
cursor: not-allowed; | ||
} |
22 changes: 22 additions & 0 deletions
22
projects/cli/schematics/components/files/switch/zen-switch.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { ZenSwitchComponent } from './zen-switch.component'; | ||
|
||
describe('SwitchComponent', () => { | ||
let component: ZenSwitchComponent; | ||
let fixture: ComponentFixture<ZenSwitchComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [ZenSwitchComponent], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(ZenSwitchComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
123 changes: 123 additions & 0 deletions
123
projects/cli/schematics/components/files/switch/zen-switch.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { NgIf } from '@angular/common'; | ||
import { | ||
ChangeDetectionStrategy, | ||
Component, | ||
forwardRef, | ||
model, | ||
} from '@angular/core'; | ||
import { | ||
ControlValueAccessor, | ||
FormsModule, | ||
NG_VALUE_ACCESSOR, | ||
} from '@angular/forms'; | ||
|
||
type OnChangeFn = (value: boolean) => void; | ||
type OnTouchedFn = () => void; | ||
|
||
/** | ||
* ZenSwitchComponent is a custom switch component that implements ControlValueAccessor to work seamlessly with Angular forms. | ||
* | ||
* @example <zen-switch /> | ||
* | ||
* @export | ||
* @class ZenSwitchComponent | ||
* @implements {ControlValueAccessor} | ||
* | ||
* @license BSD-2-Clause | ||
* @author Konrad Stępień <kord.stp@gmail.com> | ||
* @link https://github.com/Kordrad/ng-zen | ||
*/ | ||
@Component({ | ||
selector: 'zen-switch', | ||
standalone: true, | ||
templateUrl: './zen-switch.component.html', | ||
styleUrl: './zen-switch.component.scss', | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
providers: [ | ||
{ | ||
provide: NG_VALUE_ACCESSOR, | ||
useExisting: forwardRef(() => ZenSwitchComponent), | ||
multi: true, | ||
}, | ||
], | ||
imports: [FormsModule, NgIf], | ||
}) | ||
export class ZenSwitchComponent implements ControlValueAccessor { | ||
/** Model for the checked state of the switch. */ | ||
checked = model<boolean>(false); | ||
|
||
/** Model for the disabled state of the switch. */ | ||
disabled = model<boolean>(false); | ||
|
||
/** @ignore */ | ||
private onChange: OnChangeFn = () => {}; | ||
/** @ignore */ | ||
private onTouched: OnTouchedFn = () => {}; | ||
|
||
/** | ||
* Writes a new value to the component. | ||
* @ignore | ||
*/ | ||
writeValue(value: boolean): void { | ||
this.checked.set(value); | ||
} | ||
|
||
/** | ||
* Registers a function to be called when the value changes. | ||
* @ignore | ||
*/ | ||
registerOnChange(fn: OnChangeFn): void { | ||
this.onChange = fn; | ||
} | ||
|
||
/** | ||
* Registers a function to be called when the component is touched. | ||
* @ignore | ||
*/ | ||
registerOnTouched(fn: OnTouchedFn): void { | ||
this.onTouched = fn; | ||
} | ||
|
||
/** | ||
* Sets the disabled state of the component. | ||
* @ignore | ||
*/ | ||
setDisabledState(isDisabled: boolean): void { | ||
this.disabled.set(isDisabled); | ||
} | ||
|
||
/** | ||
* Toggles the switch value and notifies the change. | ||
*/ | ||
onToggle(check?: boolean): void { | ||
if (this.disabled()) return; | ||
|
||
const value = check ?? !this.checked(); | ||
|
||
this.checked.set(value); | ||
this.onChange(value); | ||
this.onTouched(); | ||
} | ||
|
||
/** | ||
* Handles keyboard events for accessibility. | ||
*/ | ||
onKeyDown(event: KeyboardEvent): void { | ||
switch (event.code) { | ||
case 'Enter': | ||
case 'Space': { | ||
event.preventDefault(); | ||
this.onToggle(); | ||
break; | ||
} | ||
case 'ArrowRight': { | ||
this.onToggle(true); | ||
break; | ||
} | ||
case 'ArrowLeft': { | ||
this.onToggle(false); | ||
break; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { FormsModule } from '@angular/forms'; | ||
import type { Meta, StoryObj } from '@storybook/angular'; | ||
import { moduleMetadata } from '@storybook/angular'; | ||
|
||
import { ZenSwitchComponent } from '../../schematics/components/files/switch'; | ||
|
||
export default { | ||
title: 'Components/Switch', | ||
component: ZenSwitchComponent, | ||
tags: ['autodocs'], | ||
decorators: [ | ||
moduleMetadata({ | ||
imports: [FormsModule], | ||
}), | ||
], | ||
render: args => ({ props: { ...args } }), | ||
} satisfies Meta<ZenSwitchComponent>; | ||
|
||
type Story = StoryObj<ZenSwitchComponent>; | ||
|
||
export const Default: Story = { | ||
render: () => ({ | ||
template: ` | ||
<zen-switch /> | ||
`, | ||
}), | ||
}; | ||
|
||
export const Disabled: Story = { | ||
render: () => ({ | ||
template: `<zen-switch [disabled]="true" />`, | ||
}), | ||
}; | ||
|
||
export const Checked: Story = { | ||
render: () => ({ | ||
template: ` | ||
<zen-switch [ngModel]="true" /> | ||
<br/> | ||
<zen-switch [checked]="true" /> | ||
`, | ||
}), | ||
}; |