Questa pagina è stata tradotta automaticamente e può contenere errori. Leggi l'originale in inglese

Elementi

Switch

Uno switch attiva o disattiva un’impostazione, e la modifica ha effetto immediato. Gestisce il comportamento: pressioni, attivazione da tastiera, stato disabilitato, il ruolo di switch e il suo valore per gli screen reader. L’aspetto spetta all’app: un Switch non ha colori, dimensioni, animazioni né etichetta propri. È composto da una traccia (track) con un cursore (thumb) al suo interno, poi dai suoi figli (un’etichetta), e l’app stilizza ogni parte, con classi checked: e disabled: che seguono lo switch.

el-switch.tsx
el-switch.tsxLive
import { mount, signal, Text, View } from "nsh";
import { Switch } from "nsh/base";
import { desktop, focusRing, hoverFill, Log, Panel, size, Split, ui } from "./palette";

// The parts: geometry and motion in classes (`checked:` moves the thumb,
// `disabled:` dims the row, both following the switch), colors from the
// palette by the value the app owns. A pointer gets the compact size; the
// pointer's hover and the keyboard's focus ring come from the palette, and
// the thumb and track stay still with reduced motion.
const row = `flex-row-reverse items-center justify-between gap-3 ${desktop ? "px-3 py-2" : "px-4 py-3"} active:opacity-70 disabled:opacity-40`;
const track = `${desktop ? "w-9" : "w-12"} rounded-full p-0.5 transition-colors motion-reduce:transition-none`;
const thumb = desktop ? "size-4 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-4" : "size-5.5 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-5";

function Setting(props: { label: string; detail: string; checked: boolean; disabled?: boolean; onChange: (e: { checked: boolean }) => void }) {
  return (
    <Switch
      checked={props.checked}
      disabled={props.disabled}
      onChange={props.onChange}
      class={row}
      hoverStyle={props.disabled ? undefined : hoverFill()}
      focusStyle={focusRing()}
      trackClass={track}
      trackStyle={props.checked ? ui().accentFill : ui().inset}
      thumbClass={thumb}
      thumbStyle={props.checked ? { backgroundColor: ui().onAccent } : ui().raised}
    >
      <View style={{ flex: 1, gap: 2 }}>
        <Text style={{ fontSize: size.label, fontWeight: "500", color: ui().text }}>{props.label}</Text>
        <Text style={{ fontSize: size.detail, color: ui().muted }}>{props.detail}</Text>
      </View>
    </Switch>
  );
}

function App() {
  const wifi = signal(true);
  const bluetooth = signal(false);
  const ask = signal(true);
  const events = signal<string[]>([]);
  const change = (name: string, value: { set: (v: boolean) => void }) => ({ checked }: { checked: boolean }) => {
    value.set(checked);
    events.set([`${name} → ${checked}`, ...events()].slice(0, 3));
  };
  const line = () => <View style={{ height: 1, marginLeft: desktop ? 12 : 16, backgroundColor: ui().line }} />;
  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>switch</Text>
      <Text style={{ color: ui().muted }}>
        {desktop ? "Click a row, or Tab to a switch and press Space" : "Tap a row: the label toggles it too"}
      </Text>

      <Split
        gap={14}
        inspector={
          <>
            <Panel title="checked · disabled">
              <Text style={{ fontSize: 13, color: ui().text }}>
                Wi-Fi {wifi() ? "on" : "off"} · ask {ask() ? "on" : "off"}
                {wifi() ? "" : " (disabled)"} · Bluetooth {bluetooth() ? "on" : "off"}
              </Text>
            </Panel>
            <Log title="onChange({ checked })" lines={events()} minHeight={92} />
          </>
        }
      >
        <View style={{ ...ui().card, borderRadius: 16, overflow: "hidden" }}>
          <Setting label="Wi-Fi" detail={wifi() ? "Connected to Studio" : "Off"} checked={wifi()} onChange={change("Wi-Fi", wifi)} />
          {line()}
          <Setting
            label="Ask to join networks"
            detail={wifi() ? "Known networks join on their own" : "Turn on Wi-Fi first"}
            checked={ask()}
            disabled={!wifi()}
            onChange={change("Ask to join", ask)}
          />
          {line()}
          <Setting label="Bluetooth" detail={bluetooth() ? "Discoverable as “Desk”" : "Off"} checked={bluetooth()} onChange={change("Bluetooth", bluetooth)} />
        </View>
      </Split>
    </View>
  );
}

mount(App);
use nsh_base::{switch, theme};
use nsh_core::prelude::*;

fn wifi() -> impl IntoElement {
    let on = signal(false);
    switch()
        .checked(on)
        .on_change(move |next: &bool| on.set(*next))
        .flex_row()
        .items_center()
        .gap_3()
        .track_style(|s| {
            s.w(px(44.0))
                .p(px(2.0))
                .rounded_full()
                .bg(move || theme().muted)
                .transition_colors()
                .checked(|s| s.bg(move || theme().primary))
        })
        .thumb_style(|s| {
            s.size(px(20.0))
                .rounded_full()
                .bg(move || theme().background)
                .shadow_sm()
                .transition_transform()
                .checked(|s| s.translate_x(px(20.0)))
        })
        .child(text("Wi-Fi"))
}

Switch proviene da nsh/base, il crate nsh-base in Rust. Il valore è dell’app: una pressione comunica il valore successivo con onChange, e lo switch mostra il checked che l’app restituisce. In Rust, switch() ne crea uno con un setter per ogni prop (.checked(..), .track_style(..)), un listener per ogni evento (.on_change(..)), i metodi di stile tipizzati per lo switch stesso e .child(..) per la sua etichetta; lo stile di una parte accetta i metodi di stato (.checked(..), .disabled_style(..)) che seguono lo switch.

#Props

PropRustTipoPredefinitoDescrizione
checked.checked(..)booleanfalseAttivo o disattivo. Una pressione comunica il valore successivo con onChange; lo switch mostra il valore che l’app restituisce.
disabled.disabled(..)booleanfalseNessuna pressione e nessun feedback alla pressione; gli stili disabled: si applicano allo switch e alle sue parti, e gli screen reader dicono che è attenuato.
checkedStyle.checked_style(|s| ..)StyleProp<ViewStyle>nessunoLo stile dello switch quando è checked e non disabilitato, come classi checked:.
accessibilityLabel.accessibility_label(..)stringnessunoCiò che dicono gli screen reader: che cosa attiva lo switch, quando i suoi figli non contengono testo che lo dica.
trackClass, trackStyle.track_style(|s| ..)string, StyleProp<ViewStyle>nessunoLa traccia, la parte lungo cui si muove il cursore.
thumbClass, thumbStyle.thumb_style(|s| ..)string, StyleProp<ViewStyle>nessunoIl cursore, all’interno della traccia.
hitSlop.hit_slop(..)number | Partial<Edges>nessunoQuanto oltre i suoi limiti le pressioni vengono ancora conteggiate, in punti: un solo numero per tutti i lati, oppure i lati indicati per nome. Come hitSlop di una view.
children.child(..)JSX.ElementnessunoIl contenuto dopo la traccia: un’etichetta, che gli screen reader leggono.
class, stylemetodi di stilestring, StyleProp<ViewStyle>nessunoLo switch stesso: la riga attorno alla traccia e all’etichetta.
activeStyle, hoverStyle, focusStyle, disabledStyle.active(|s| ..), .hover(..), .focus(..), .disabled_style(..)StyleProp<ViewStyle>nessunoLo stile dello switch quando è premuto, in hover, con il focus o disabilitato, come classi <state>:.

#Eventi

EventoRustDescrizione
onChange.on_change(|checked: &bool| ..)Lo switch è stato premuto: un tocco, un clic, Spazio o Invio mentre ha il focus della tastiera, oppure l’attivazione da parte di uno screen reader. Comunica come checked il valore richiesto dallo switch, l’opposto del checked che ha.

Uno switch disabilitato non comunica nulla.

#Parti

ParteDentroStilizzata conStati
Lo switchclass, stylechecked:, disabled:, active:, hover:, focus: (oppure checkedStyle, disabledStyle, …)
La traccialo switch, per primatrackClass, trackStylechecked:, disabled: seguono lo switch
Il cursorela tracciathumbClass, thumbStylechecked:, disabled: seguono lo switch
L’etichettalo switch, dopo la tracciai propri elementi (children)group-checked: e le altre varianti group-*: con group sul class dello switch

Il cursore si muove tramite i propri stili checked:: una traslazione (checked:translate-x-5), oppure un margine o l’allineamento della traccia (checked:justify-end sulla traccia). Una classe transition-* anima il cambiamento in modo nativo. Per collocare l’etichetta prima della traccia, inverti la riga: class="flex-row-reverse justify-between".

#Disabilitato

Uno switch disabilitato non accetta pressioni, e gli screen reader lo annunciano come attenuato. Le sue classi disabled: e quelle della traccia e del cursore si applicano, sovrascrivendo quelle checked::

<Switch
  checked={on()}
  disabled={!online()}
  onChange={({ checked }) => on.set(checked)}
  class="flex-row items-center gap-3 disabled:opacity-50"
  trackClass="w-11 rounded-full bg-muted p-0.5 checked:bg-primary disabled:bg-muted"
  thumbClass="size-5 rounded-full bg-background checked:translate-x-5"
>
  <Text>Sync over cellular</Text>
</Switch>

#Un’area di tocco piccola con hitSlop

Uno switch senza etichetta accanto può essere più piccolo di un dito. hitSlop fa sì che le pressioni che cadono vicino ad esso vengano conteggiate, in punti oltre ogni lato, senza cambiarne il layout né l’aspetto: un solo numero per tutti i lati, oppure i lati indicati per nome ({ top: 8, bottom: 8 }), come li accetta hitSlop di una view.

<Switch
  checked={on()}
  hitSlop={{ top: 8, bottom: 8 }}
  accessibilityLabel="Notifications"
  onChange={({ checked }) => on.set(checked)}
  trackClass="w-9 rounded-full bg-muted p-0.5 checked:bg-primary"
  thumbClass="size-4 rounded-full bg-background checked:translate-x-4"
/>

#Accessibilità

  • Uno switch è un unico elemento per gli screen reader, con il ruolo "switch" e il suo valore (attivo o disattivo); la traccia e il cursore ne sono parti. La sua etichetta è il testo dei suoi figli; usa accessibilityLabel quando non ne ha.
  • disabled viene annunciato nel modo in cui lo annuncia la piattaforma (VoiceOver, TalkBack, UI Automation, l’albero di accessibilità del browser).
  • Su macOS e sul web uno switch è una tappa del focus da tastiera: Tab lo raggiunge, Spazio e Invio lo commutano, e gli stili focus: mostrano dove si trova il focus.

Consulta Accessibilità per etichette, ruoli e annunci in generale.

#Note sulle piattaforme

  • iOS, Android e Mac Catalyst: una pressione è un tocco che termina sullo switch, etichetta inclusa.
  • Desktop (macOS, Windows, Linux, GPUI) e web: una pressione è un clic. Su macOS e sul web, Spazio e Invio commutano anche lo switch che ha il focus della tastiera.
  • Ogni piattaforma disegna ciò che l’app stilizza; il cursore si muove come indicano gli stili e le transizioni dell’app.