Add switch.component.ts
Create the following file in your project.
import {
booleanAttribute,
ChangeDetectionStrategy,
Component,
computed,
forwardRef,
input,
model,
signal,
ViewEncapsulation,
} from '@angular/core';
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import type { ClassValue } from 'clsx';
import { ZardIdDirective } from '@/shared/core';
import { mergeClasses, noopFn } from '@/shared/utils/merge-classes';
import { switchVariants, type ZardSwitchSizeVariants } from './switch.variants';
type OnTouchedType = () => void;
type OnChangeType = (value: boolean) => void;
@Component({
selector: 'z-switch',
imports: [ZardIdDirective],
template: `
<span class="flex items-center space-x-2" zardId="switch" #z="zardId" [attr.data-checked]="zChecked() ? '' : null">
<button
[id]="zId() || z.id()"
type="button"
role="switch"
[attr.data-state]="status()"
[attr.aria-checked]="zChecked()"
[attr.aria-invalid]="zInvalid() ? 'true' : null"
[class]="classes()"
[disabled]="zDisabled() || formDisabled()"
(click)="onSwitchChange()"
>
<span
[attr.data-size]="zSize()"
[attr.data-state]="status()"
class="bg-background dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground pointer-events-none block rounded-full ring-0 transition-transform data-[size=default]:size-4 data-[size=sm]:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
></span>
</button>
<label
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
[for]="zId() || z.id()"
>
<ng-content><span class="sr-only">toggle switch</span></ng-content>
</label>
</span>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ZardSwitchComponent),
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
exportAs: 'zSwitch',
})
export class ZardSwitchComponent implements ControlValueAccessor {
readonly class = input<ClassValue>('');
readonly zChecked = model<boolean>(false);
readonly zId = input<string>('');
readonly zSize = input<ZardSwitchSizeVariants>('default');
readonly zDisabled = input(false, { transform: booleanAttribute });
readonly zInvalid = input(false, { transform: booleanAttribute });
private onChange: OnChangeType = noopFn;
private onTouched: OnTouchedType = noopFn;
protected readonly status = computed(() => (this.zChecked() ? 'checked' : 'unchecked'));
protected readonly classes = computed(() => mergeClasses(switchVariants({ zSize: this.zSize() }), this.class()));
protected readonly formDisabled = signal(false);
writeValue(val: boolean): void {
this.zChecked.set(val);
}
registerOnChange(fn: OnChangeType): void {
this.onChange = fn;
}
registerOnTouched(fn: OnTouchedType): void {
this.onTouched = fn;
}
onSwitchChange(): void {
if (this.zDisabled() || this.formDisabled()) {
return;
}
this.zChecked.update(checked => !checked);
this.onTouched();
this.onChange(this.zChecked());
}
setDisabledState(isDisabled: boolean): void {
this.formDisabled.set(isDisabled);
}
}