Esta página se tradujo automáticamente y puede contener errores. Leer el original en inglés

Elementos

Switch

Un switch activa o desactiva un ajuste, y el cambio surte efecto al instante. Se encarga del comportamiento: pulsaciones, activación con teclado, el estado deshabilitado, y el rol de switch y su valor para los lectores de pantalla. El aspecto es de la app: un Switch no tiene colores, tamaños, movimiento ni etiqueta propios. Se compone de un track con un thumb dentro, y después sus hijos (una etiqueta); la app da estilo a cada parte, con clases checked: y disabled: que siguen al 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 de nsh/base, el crate nsh-base de Rust. El valor es de la app: una pulsación informa del siguiente valor con onChange, y el switch muestra el checked que la app le devuelve. En Rust, switch() crea uno con un setter por prop (.checked(..), .track_style(..)), un listener por evento (.on_change(..)), los métodos de estilo tipados para el propio switch y .child(..) para su etiqueta; el estilo de una parte admite los métodos de estado (.checked(..), .disabled_style(..)) que siguen al switch.

#Props

Prop Rust Tipo Valor por defecto Descripción
checked .checked(..) boolean false Activado o desactivado. Una pulsación informa del siguiente valor con onChange; el switch muestra el valor que la app le devuelve.
disabled .disabled(..) boolean false Sin pulsaciones ni respuesta visual a ellas; los estilos disabled: se aplican al switch y a sus partes, y los lectores de pantalla indican que está atenuado.
checkedStyle .checked_style(|s| ..) StyleProp<ViewStyle> ninguno El estilo del switch mientras está checked y no deshabilitado, como clases checked:.
accessibilityLabel .accessibility_label(..) string ninguno Lo que dicen los lectores de pantalla: qué activa el switch, cuando sus hijos no tienen texto que lo indique.
trackClass, trackStyle .track_style(|s| ..) string, StyleProp<ViewStyle> ninguno El track, la parte por la que se mueve el thumb.
thumbClass, thumbStyle .thumb_style(|s| ..) string, StyleProp<ViewStyle> ninguno El thumb, dentro del track.
hitSlop .hit_slop(..) number | Partial<Edges> ninguno Hasta qué distancia fuera de sus límites siguen contando las pulsaciones, en puntos: un número para todos los lados, o los lados indicados por nombre. Como el hitSlop de una vista.
children .child(..) JSX.Element ninguno El contenido tras el track: una etiqueta, que los lectores de pantalla leen.
class, style métodos de estilo string, StyleProp<ViewStyle> ninguno El propio switch: la fila que rodea al track y la etiqueta.
activeStyle, hoverStyle, focusStyle, disabledStyle .active(|s| ..), .hover(..), .focus(..), .disabled_style(..) StyleProp<ViewStyle> ninguno El estilo del switch mientras está pulsado, con el cursor encima, con el foco o deshabilitado, como las clases <state>:.

#Eventos

Evento Rust Descripción
onChange .on_change(|checked: &bool| ..) Se pulsó el switch: un toque, un clic, Espacio o Intro mientras tiene el foco del teclado, o la activación de un lector de pantalla. Informa del valor que el switch solicita como checked, el opuesto del checked que tiene.

Un switch deshabilitado no informa de nada.

#Partes

Parte Dentro de Se estiliza con Estados
El switch class, style checked:, disabled:, active:, hover:, focus: (o checkedStyle, disabledStyle, …)
El track el switch, primero trackClass, trackStyle checked:, disabled: siguen al switch
El thumb el track thumbClass, thumbStyle checked:, disabled: siguen al switch
La etiqueta el switch, después del track sus propios elementos (children) group-checked: y las demás variantes group-*: con group en el class del switch

El thumb se mueve mediante sus propios estilos checked:: una traslación (checked:translate-x-5), o un margen o la alineación del track (checked:justify-end en el track). Una clase transition-* anima el cambio de forma nativa. Para colocar la etiqueta antes del track, invierte la fila: class="flex-row-reverse justify-between".

#Deshabilitado

Un switch deshabilitado no admite pulsaciones, y los lectores de pantalla lo anuncian como atenuado. Sus clases disabled: y las de su track y su thumb se aplican por encima de las 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 área pequeña con hitSlop

Un switch sin etiqueta a su lado puede ser más pequeño que un dedo. hitSlop hace que cuenten las pulsaciones que caen cerca de él, en puntos más allá de cada lado, sin cambiar su diseño ni su aspecto: un número para todos los lados, o los lados indicados por nombre ({ top: 8, bottom: 8 }), igual que los admite el hitSlop de una vista.

<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"
/>

#Accesibilidad

  • Un switch es un único elemento para los lectores de pantalla, con el rol "switch" y su valor (activado o desactivado); el track y el thumb forman parte de él. Su etiqueta es el texto de sus hijos; indica accessibilityLabel cuando no tiene ninguno.
  • disabled se anuncia como lo anuncia la plataforma (VoiceOver, TalkBack, UI Automation, el árbol de accesibilidad del navegador).
  • En macOS y la web, un switch es una parada del foco del teclado: Tab llega hasta él, Espacio e Intro lo alternan, y los estilos focus: muestran dónde está el foco.

Consulta Accesibilidad para ver etiquetas, roles y anuncios en general.

#Notas por plataforma

  • iOS, Android y Mac Catalyst: una pulsación es un toque que termina sobre el switch, incluida su etiqueta.
  • Escritorio (macOS, Windows, Linux, GPUI) y la web: una pulsación es un clic. En macOS y la web, Espacio e Intro también alternan el switch que tiene el foco del teclado.
  • Cada plataforma dibuja lo que la app estiliza; el thumb se mueve según indican los estilos y las transiciones de la app.