컴포넌트

Switch

설정을 켜거나 끕니다.

설치

pnpm dlx ply-ui add switch

공통 설치 설정을 먼저 적용합니다.

사용법

하나의 설정을 켜거나 끌 때 사용합니다. RootThumb을 조합하고 label로 설정 이름을 연결합니다.

구성

구성 요소역할
Root켜짐 상태와 크기를 관리합니다. checkedonCheckedChange로 제어합니다.
Thumb현재 상태에 따라 움직이는 손잡이입니다. 체크 아이콘은 내부에 포함되어 있습니다.

크기

size의 기본값은 md입니다. 너비 × 높이는 xs 28×16, sm 32×20, md 40×24, lg 44×24px입니다.

비활성

disabled로 상태 전환을 막습니다.

상태 제어

checkedonCheckedChange를 연결해 설정값을 관리합니다.

속성

이 프로젝트에서 추가하거나 제한한 속성입니다.

Root

속성

타입

구현 코드

components/ui/switch.tsx
switch.tsx
"use client";
import type { ComponentProps } from "react";
import { Switch as Primitive } from "@base-ui/react/switch";
import { withClassName } from "../../lib/cx";
import { Icon } from "./icon";
import "./switch.css";

export type SwitchRootProps = ComponentProps<typeof Primitive.Root> & {
  /** 스위치 크기입니다. @defaultValue "md" */
  size?: "xs" | "sm" | "md" | "lg";
};
/** 두 spacer의 flex-grow가 바뀌면서 손잡이가 이동합니다. RTL도 자연스럽게 반전됩니다. */
function SwitchRoot({
  size = "md",
  children,
  className,
  ...props
}: SwitchRootProps) {
  return (
    <Primitive.Root
      {...props}
      data-size={size}
      className={withClassName("rbx-switch", className)}
    >
      <span
        className="rbx-switch-spacer rbx-switch-spacer-start"
        aria-hidden="true"
      />
      {children}
      <span
        className="rbx-switch-spacer rbx-switch-spacer-end"
        aria-hidden="true"
      />
    </Primitive.Root>
  );
}
function SwitchThumb({
  children,
  className,
  ...props
}: ComponentProps<typeof Primitive.Thumb>) {
  return (
    <Primitive.Thumb
      {...props}
      className={withClassName("rbx-switch-thumb", className)}
    >
      {children ?? <Icon name="icon-filled-check" />}
    </Primitive.Thumb>
  );
}
export const Switch = { ...Primitive, Root: SwitchRoot, Thumb: SwitchThumb };
components/ui/switch.css
switch.css
/* Foundation Web Toggle (private server configuration). See measured values and public bundle provenance. */
.rbx-switch {
  --rbx-switch-thumb-size: 20px;
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  width: 40px;
  height: 24px;
  padding: 2px;
  border: 0;
  border-radius: 9999px;
  background: var(--rbx-color-action-standard-background);
  cursor: pointer;
  transition: background-color var(--rbx-motion-duration-feedback)
    var(--rbx-ease-linear);
}
.rbx-switch[data-size="xs"] {
  width: 28px;
  height: 16px;
  --rbx-switch-thumb-size: 12px;
}
.rbx-switch[data-size="sm"] {
  width: 32px;
  height: 20px;
  --rbx-switch-thumb-size: 16px;
}
.rbx-switch[data-size="lg"] {
  width: 44px;
  height: 24px;
}
.rbx-switch[data-checked] {
  background: var(--rbx-color-system-contrast);
}
.rbx-switch-spacer {
  flex: 0 1 0%;
  width: 0;
  height: 0;
  transition: flex-grow var(--rbx-motion-duration-panel)
    var(--rbx-motion-ease-enter);
}
.rbx-switch-spacer-end,
.rbx-switch[data-checked] .rbx-switch-spacer-start {
  flex-grow: 1;
}
.rbx-switch[data-checked] .rbx-switch-spacer-end {
  flex-grow: 0;
}
.rbx-switch-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--rbx-switch-thumb-size);
  height: var(--rbx-switch-thumb-size);
  border-radius: 9999px;
  background: var(--rbx-color-content-emphasis);
  color: var(--rbx-color-content-emphasis);
  transition: background-color var(--rbx-motion-duration-feedback)
    var(--rbx-ease-linear);
}
.rbx-switch-thumb[data-checked] {
  background: var(--rbx-inverse-content-emphasis);
}
.rbx-switch-thumb > .rbx-icon {
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity var(--rbx-motion-duration-feedback) var(--rbx-ease-linear);
}
.rbx-switch-thumb[data-checked] > .rbx-icon {
  opacity: 1;
  transition-delay: var(--rbx-time-50);
}
.rbx-switch:hover:not([data-disabled]) {
  background-image: linear-gradient(
    var(--rbx-color-state-hover),
    var(--rbx-color-state-hover)
  );
}
.rbx-switch:active:not([data-disabled]) {
  background-image: linear-gradient(
    var(--rbx-color-state-press),
    var(--rbx-color-state-press)
  );
}

목차