Motiva
Мои коллекции
← каталог

Toggle Switch

Пружинный переключатель: ручка прыгает spring-ом, трек плавно меняет цвет; доступен как role=switch.

motionclickspringbasic~18 КБreduced-motion ✓

Пропсы

Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.

ПропТипПо умолчаниюЧто это
label?string"Уведомления"
defaultOn?booleanfalse
onChange?(on: boolean) => void;

Установка

npx shadcn@latest add https://motiva.pages.dev/r/toggle-switch.json

зависимости: motion@^12.42.2

Что нужно в проекте до установки
  1. components.json в корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.
  2. Алиас @ объявлен дважды: "paths": {"@/*": ["./src/*"]} в корневом tsconfig.json (в Vite он часто содержит только references — тогда CLI создаёт папку@) и resolve.alias в конфиге сборщика.
  3. Токены подключены один раз на приложение:import "@/styles/motiva-tokens.css" — они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему.
  4. Своя типографика не должна спорить: если у вас на обёрткеtext-align: center, он протечёт внутрь блоков.
  5. Проект без TypeScript? Всё равно нужен минимальныйtsconfig.json с pathsи "tsx": true в components.json: CLI грузит tsconfig безусловно, а с "tsx": false идёт за jsconfig.json и падает так же. Приехавший рядом.tsx можно не трогать — вам нужен.vanilla.js.

Экспорт

↓ Скачать .zip

Код

"use client";

import { useState } from "react";
import { motion } from "motion/react";

export interface ToggleSwitchProps {
  label?: string;
  defaultOn?: boolean;
  onChange?: (on: boolean) => void;
}

/** Пружинный toggle: ручка прыгает spring-ом, трек меняет цвет. Уважает reduced-motion / data-shot. */
export function ToggleSwitch({ label = "Уведомления", defaultOn = false, onChange }: ToggleSwitchProps) {
  const [on, setOn] = useState(defaultOn);
  const frozen =
    typeof document !== "undefined" &&
    (document.documentElement.hasAttribute("data-shot") ||
      window.matchMedia("(prefers-reduced-motion: reduce)").matches);

  const toggle = () => {
    setOn((v) => {
      onChange?.(!v);
      return !v;
    });
  };

  return (
    <label
      style={{
        display: "flex",
        alignItems: "center",
        gap: 12,
        cursor: "pointer",
        color: "oklch(0.92 0.01 265)",
        fontSize: "0.95rem",
        userSelect: "none",
      }}
    >
      <button
        role="switch"
        aria-checked={on}
        onClick={toggle}
        style={{
          position: "relative",
          width: 52,
          height: 30,
          borderRadius: 999,
          border: "1px solid oklch(0.32 0.02 270)",
          background: on ? "oklch(0.65 0.17 300)" : "oklch(0.25 0.02 270)",
          transition: frozen ? "none" : "background 0.25s",
          padding: 0,
          cursor: "pointer",
        }}
      >
        <motion.span
          animate={{ x: on ? 22 : 0 }}
          transition={frozen ? { duration: 0 } : { type: "spring", stiffness: 500, damping: 28 }}
          style={{
            position: "absolute",
            top: 2,
            left: 3,
            width: 24,
            height: 24,
            borderRadius: "50%",
            background: "oklch(0.97 0 0)",
            boxShadow: "0 2px 6px rgb(0 0 0 / 0.35)",
            display: "block",
          }}
        />
      </button>
      {label}
    </label>
  );
}
лицензия: MITавтор: Motivaисточник: оригинал (Motiva)v1.0.0