컴포넌트

Switch

설정을 켜거나 끕니다.

설치

pnpm dlx ply-ui add switch

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

사용법

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

구성

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

크기

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

비활성

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

상태 제어

checked와 onCheckedChange를 연결해 설정값을 관리합니다.

속성

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

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
@layer theme, base, components, utilities;
@layer components.ply-base, components.ply;

@layer components.ply {
  /* 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)
    );
  }
}

목차