# Switch

A shadcn-style native switch primitive with `ControlValueAccessor` support for `ngModel` and reactive forms.

## Import

```ts
import { FormsModule } from '@angular/forms';
import { SwitchComponent } from '@edsis/component/switch';
```

Add `FormsModule` only when the host component uses `ngModel`. Reactive forms work through the same primitive.

## Basic usage

Use projected content for compact inline labels.

```html
<Switch [(ngModel)]="airplaneMode" name="airplane-mode">Airplane mode</Switch>
```

For helper text or card-like settings rows, move the copy outside the switch and connect it with `aria-labelledby` plus `aria-describedby`.

```html
<div class="flex items-start justify-between gap-4 rounded-xl border border-border bg-card/40 p-4">
  <div class="space-y-1">
    <p id="switch-share-label" class="font-medium">Share across devices</p>
    <p id="switch-share-description" class="text-sm leading-5 text-muted-foreground">
      Focus is shared across devices, and turns off when you leave the app.
    </p>
  </div>
  <Switch
    [ngModel]="shareAcrossDevices()"
    (ngModelChange)="shareAcrossDevices.set($event)"
    name="share-across-devices"
    aria-labelledby="switch-share-label"
    aria-describedby="switch-share-description"
  />
</div>
```

Reactive forms continue to work through the component's value accessor.

```html
<Switch [formControl]="form.controls.notifications" aria-label="Email notifications" />
```

## Common patterns

### Descriptive settings row

Use `aria-labelledby` and `aria-describedby` when the visible copy sits outside the switch.

```html
<div class="flex items-start justify-between gap-4">
  <div class="space-y-1">
    <p id="setting-label" class="font-medium">Share across devices</p>
    <p id="setting-description" class="text-sm text-muted-foreground">
      Focus is shared across devices, and turns off when you leave the app.
    </p>
  </div>
  <Switch
    [ngModel]="shareAcrossDevices()"
    (ngModelChange)="shareAcrossDevices.set($event)"
    name="share-across-devices"
    aria-labelledby="setting-label"
    aria-describedby="setting-description"
  />
</div>
```

### Choice card layout

The upstream shadcn example wraps the whole field in a clickable label. This Angular primitive keeps the switch as the single interactive control and uses the surrounding card only for layout.

```html
<div class="rounded-xl border border-border bg-card/40 p-4">
  <div class="flex items-start justify-between gap-4">
    <div class="space-y-1">
      <p id="notifications-label" class="font-medium">Enable notifications</p>
      <p id="notifications-description" class="text-sm text-muted-foreground">
        Receive notifications when focus mode is enabled or disabled.
      </p>
    </div>
    <Switch
      [ngModel]="notificationsEnabled()"
      (ngModelChange)="notificationsEnabled.set($event)"
      name="notifications-card"
      aria-labelledby="notifications-label"
      aria-describedby="notifications-description"
    />
  </div>
</div>
```

### Invalid state

Forward `aria-invalid="true"` when validation should promote the switch into the destructive token set.

```html
<Switch
  [ngModel]="acceptTerms()"
  (ngModelChange)="acceptTerms.set($event)"
  name="terms-switch"
  aria-invalid="true"
  aria-label="Accept terms and conditions"
/>
```

### Size

Use `size="sm"` for denser settings lists and compact preference panes.

```html
<Switch size="sm" [(ngModel)]="compactPreference" name="compact-preference">Compact mode</Switch>
```

## API reference

| Input              | Type                        | Default        | Notes                                                                     |
| ------------------ | --------------------------- | -------------- | ------------------------------------------------------------------------- |
| `id`               | `string`                    | auto-generated | Forwarded to Material; the focusable switch button becomes `<id>-button`. |
| `name`             | `string \| null`            | `null`         | Forwarded to the underlying switch control.                               |
| `disabled`         | `boolean`                   | `false`        | Merges with form-driven disabled state.                                   |
| `required`         | `boolean`                   | `false`        | Forwarded to the underlying Material switch.                              |
| `aria-label`       | `string \| null`            | `null`         | Use for compact or text-free switch controls.                             |
| `aria-describedby` | `string \| null`            | `null`         | Links helper or error text outside the control.                           |
| `aria-labelledby`  | `string \| null`            | `null`         | Best choice for descriptive rows and card layouts.                        |
| `aria-invalid`     | `boolean \| string \| null` | `null`         | Switches the control into the destructive invalid treatment.              |
| `size`             | `'default' \| 'sm'`         | `'default'`    | Smaller track for dense settings lists.                                   |
| `class`            | `string`                    | `''`           | Adds utility classes to the wrapped Material host.                        |

| Output          | Payload   |
| --------------- | --------- |
| `checkedChange` | `boolean` |

Public method: `focus()`.

## Styling and theming

The component uses a Material bridge so the MDC switch structure is restyled with the library theme tokens.

- Default track and handle colors use the shared `primary`, `input`, and `background` tokens.
- `aria-invalid="true"` switches the switch into the destructive token set.
- `size="sm"` tightens the track and handle sizing without creating a second entrypoint.
- Pass spacing or alignment utilities through `class` when the switch needs to line up with multi-line content.

Additional visual adjustments belong in `switch.component.css` inside the library.

## Accessibility

- Give the switch an accessible name through projected content, `aria-label`, or `aria-labelledby`.
- Use `aria-describedby` for helper or error text rendered outside the control.
- Prefer `aria-labelledby` over `label[for]` when the visible label is external, because the switch renders a focusable button rather than a labelable text input.
- `aria-invalid` is forwarded to the underlying Material host for validation styling and assistive technology cues.

## Keyboard interactions

- `Tab` moves focus to the switch in DOM order.
- `Space` toggles the checked state.

## Angular notes

- `ngModel`, reactive forms, and `checkedChange` all work with the same primitive.
- Passing `id` forwards a stable base id to the control; the focusable button receives the derived `<id>-button` id.
- The direct `disabled` input and `setDisabledState()` from forms are merged, so either source can disable the control.

## Source parity

This Angular implementation follows the shadcn Switch examples while translating the upstream Field helpers and clickable-card label pattern into Angular-friendly descriptive containers, ARIA relationships, and signal-friendly state handling.
