컴포넌트
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
"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
/* 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)
);
}