Run the CLI
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add toggleA two-state button that can be either on or off.
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-default',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="Default toggle">
<z-icon zType="bold" />
</z-toggle>
`,
})
export class ZardDemoToggleDefaultComponent {}
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add togglepnpm dlx @ngzard/ui@latest add toggleyarn dlx @ngzard/ui@latest add togglebunx @ngzard/ui@latest add toggleCreate the component directory structure and add the following files to your project.
import {
ChangeDetectionStrategy,
Component,
forwardRef,
ViewEncapsulation,
signal,
computed,
input,
output,
linkedSignal,
} from '@angular/core';
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import type { ClassValue } from 'clsx';
import { toggleVariants, type ZardToggleVariants } from './toggle.variants';
import { mergeClasses, transform } from '../../shared/utils/utils';
type OnTouchedType = () => void;
type OnChangeType = (value: boolean) => void;
@Component({
selector: 'z-toggle',
template: `
<button
type="button"
[attr.aria-label]="zAriaLabel()"
[attr.aria-pressed]="value()"
[attr.data-state]="value() ? 'on' : 'off'"
[class]="classes()"
[disabled]="disabled()"
(click)="toggle()"
>
<ng-content />
</button>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ZardToggleComponent),
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'(mouseenter)': 'handleHover()',
},
exportAs: 'zToggle',
})
export class ZardToggleComponent implements ControlValueAccessor {
readonly zValue = input<boolean | undefined>();
readonly zDefault = input<boolean>(false);
readonly zDisabled = input(false, { alias: 'disabled', transform });
readonly zType = input<ZardToggleVariants['zType']>('default');
readonly zSize = input<ZardToggleVariants['zSize']>('md');
readonly zAriaLabel = input<string>('', { alias: 'aria-label' });
readonly class = input<ClassValue>('');
readonly zToggleClick = output<void>();
readonly zToggleHover = output<void>();
readonly zToggleChange = output<boolean>();
private readonly isUsingNgModel = signal(false);
protected readonly value = linkedSignal(() => this.zValue() ?? this.zDefault());
protected readonly disabled = linkedSignal(() => this.zDisabled());
protected readonly classes = computed(() =>
mergeClasses(toggleVariants({ zSize: this.zSize(), zType: this.zType() }), this.class()),
);
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onTouched: OnTouchedType = () => {};
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onChangeFn: OnChangeType = () => {};
handleHover() {
this.zToggleHover.emit();
}
toggle() {
if (this.disabled()) return;
const next = !this.value();
if (this.zValue() === undefined) {
this.value.set(next);
}
this.zToggleClick.emit();
this.zToggleChange.emit(next);
this.onChangeFn(next);
this.onTouched();
}
writeValue(val: boolean): void {
this.value.set(val ?? this.zDefault());
}
registerOnChange(fn: any): void {
this.onChangeFn = fn;
this.isUsingNgModel.set(true);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled.set(isDisabled);
}
}
import { cva, type VariantProps } from 'class-variance-authority';
export const toggleVariants = cva(
'inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
{
variants: {
zType: {
default: 'bg-transparent',
outline: 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
},
zSize: {
sm: 'h-8 px-2',
md: 'h-9 px-3',
lg: 'h-10 px-3',
},
},
defaultVariants: {
zType: 'default',
zSize: 'md',
},
},
);
export type ZardToggleVariants = VariantProps<typeof toggleVariants>;
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-default',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="Default toggle">
<z-icon zType="bold" />
</z-toggle>
`,
})
export class ZardDemoToggleDefaultComponent {}
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-with-forms',
imports: [ZardToggleComponent, FormsModule, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="Turn on the light" [(ngModel)]="lightOn">
@if (lightOn) {
<z-icon zType="lightbulb" />
} @else {
<z-icon zType="lightbulb-off" />
}
</z-toggle>
`,
})
export class ZardDemoToggleWithFormsComponent {
protected lightOn = false;
}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-with-default',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="With default" [zDefault]="true">
<z-icon zType="bold" />
</z-toggle>
`,
})
export class ZardDemoToggleWithDefaultComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-outline',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="Toggle outline" zType="outline">
<z-icon zType="bold" />
</z-toggle>
`,
})
export class ZardDemoToggleOutlineComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-with-text',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle>
<z-icon zType="italic" />
Italic
</z-toggle>
`,
})
export class ZardDemoToggleWithTextComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-small',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="Toggle small" zSize="sm">
<z-icon zType="bold" />
</z-toggle>
`,
})
export class ZardDemoToggleSmallComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-large',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="Toggle large" zSize="lg">
<z-icon zType="bold" />
</z-toggle>
`,
})
export class ZardDemoToggleLargeComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardToggleComponent } from '../toggle.component';
@Component({
selector: 'z-demo-toggle-disabled',
imports: [ZardToggleComponent, ZardIconComponent],
standalone: true,
template: `
<z-toggle aria-label="Toggle disabled" disabled>
<z-icon zType="bold" />
</z-toggle>
`,
})
export class ZardDemoToggleDisabledComponent {}