Switch
특정 옵션이나 설정을 켜거나 끌 수 있도록 돕는 토글 컴포넌트입니다.
@seed-design/lynx-react@0.1.0, @seed-design/lynx-css@0.1.0
import { root } from "@lynx-js/react";
import { Switch, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="switch-preview">
<Switch.Root defaultChecked>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch.Root>
</view>
</page>
);
}
root.render(<Root />);
npx @seed-design/cli add ui:switch
pnpm dlx @seed-design/cli add ui:switch
yarn dlx @seed-design/cli add ui:switch
bun x @seed-design/cli add ui:switch
의존성 설치
npm install @seed-design/lynx-react
yarn add @seed-design/lynx-react
pnpm add @seed-design/lynx-react
bun add @seed-design/lynx-react
아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:switch
* @requires @seed-design/lynx-react@>=0.1.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.1.0 <1.0.0
**/
import * as React from "@lynx-js/react";
import { Switch as SeedSwitch } from "@seed-design/lynx-react";
export interface SwitchProps extends SeedSwitch.RootProps {
label?: React.ReactNode;
}
/**
* @see https://seed-design.io/lynx/components/switch
*/
export const Switch = React.forwardRef<unknown, SwitchProps>(
({ label, children, ...otherProps }, ref) => {
return (
<SeedSwitch.Root ref={ref} {...otherProps}>
<SeedSwitch.Control>
<SeedSwitch.Thumb />
</SeedSwitch.Control>
{label != null ? <SeedSwitch.Label>{label}</SeedSwitch.Label> : null}
{children}
</SeedSwitch.Root>
);
},
);
Switch.displayName = "Switch";
export interface SwitchmarkProps extends Omit<SeedSwitch.RootProps, "children"> {}
/**
* @see https://seed-design.io/lynx/components/switch
*/
export const Switchmark = React.forwardRef<unknown, SwitchmarkProps>((props, ref) => {
return (
<SeedSwitch.Root ref={ref} {...props}>
<SeedSwitch.Control>
<SeedSwitch.Thumb />
</SeedSwitch.Control>
</SeedSwitch.Root>
);
});
Switchmark.displayName = "Switchmark";
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
label?React.ReactNode
defaultChecked?boolean | undefined
onCheckedChange?((checked: boolean) => void) | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
className?string | undefined
defaultChecked?boolean | undefined
onCheckedChange?((checked: boolean) => void) | undefined
style?CSSProperties | undefined
import { root } from "@lynx-js/react";
import { Switch, VStack, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
function SwitchItem({ size, label }: { size: Switch.RootProps["size"]; label: string }) {
return (
<Switch.Root size={size} defaultChecked>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>{label}</Switch.Label>
</Switch.Root>
);
}
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<VStack className="switch-preview" gap="spacingY.componentDefault">
<SwitchItem size="32" label="32 (default)" />
<SwitchItem size="24" label="24" />
<SwitchItem size="16" label="16" />
</VStack>
</page>
);
}
root.render(<Root />);
import { root } from "@lynx-js/react";
import { Switch, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="switch-preview">
<Switch.Root tone="brand" defaultChecked>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>Brand</Switch.Label>
</Switch.Root>
</view>
</page>
);
}
root.render(<Root />);
import { root } from "@lynx-js/react";
import { Switch, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="switch-preview">
<Switch.Root tone="neutral" defaultChecked>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>Neutral</Switch.Label>
</Switch.Root>
</view>
</page>
);
}
root.render(<Root />);
import { root } from "@lynx-js/react";
import { Switch, VStack, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
const label =
"Consequat ut veniam aliqua deserunt occaecat enim occaecat veniam et et cillum nulla officia incididunt incididunt. Sint laboris labore occaecat fugiat culpa voluptate ullamco in elit dolore exercitation nulla.";
function SwitchItem({ size }: { size: Switch.RootProps["size"] }) {
return (
<Switch.Root size={size}>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>{label}</Switch.Label>
</Switch.Root>
);
}
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<VStack className="switch-preview" gap="spacingY.componentDefault">
<SwitchItem size="32" />
<SwitchItem size="24" />
<SwitchItem size="16" />
</VStack>
</page>
);
}
root.render(<Root />);
import { root, useState } from "@lynx-js/react";
import { Switch, VStack, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
function SwitchItem({
disabled,
label,
tone = "brand",
defaultChecked = false,
}: {
disabled: boolean;
label: string;
tone?: Switch.RootProps["tone"];
defaultChecked?: boolean;
}) {
return (
<Switch.Root disabled={disabled} tone={tone} defaultChecked={defaultChecked}>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>{label}</Switch.Label>
</Switch.Root>
);
}
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [disabled, setDisabled] = useState(true);
return (
<page className={seedClassName}>
<VStack className="switch-preview" gap="x8">
<VStack gap="spacingY.componentDefault">
<SwitchItem disabled={disabled} label="Not Checked (Brand)" />
<SwitchItem disabled={disabled} defaultChecked label="Checked (Brand)" />
<SwitchItem disabled={disabled} tone="neutral" label="Not Checked (Neutral)" />
<SwitchItem disabled={disabled} tone="neutral" defaultChecked label="Checked (Neutral)" />
</VStack>
<Switch.Root size="16" tone="neutral" checked={disabled} onCheckedChange={setDisabled}>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>Disable switches</Switch.Label>
</Switch.Root>
</VStack>
</page>
);
}
root.render(<Root />);
onCheckedChange로 스위치의 선택 상태 변경을 감지할 수 있습니다.
import { root, useState } from "@lynx-js/react";
import { Switch, VStack, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [count, setCount] = useState(0);
const [lastValue, setLastValue] = useState<boolean | null>(null);
return (
<page className={seedClassName}>
<VStack className="switch-preview" gap="x4">
<Switch.Root
onCheckedChange={(checked) => {
setCount((previous) => previous + 1);
setLastValue(checked);
}}
>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>Click me</Switch.Label>
</Switch.Root>
<text className="switch-preview__status">
onCheckedChange called: {count} times, last value: {JSON.stringify(lastValue)}
</text>
</VStack>
</page>
);
}
root.render(<Root />);
Switch.Control과 Switch.Thumb을 직접 조합하면 레이블 배치를 자유롭게 구성할 수 있습니다.
import { root } from "@lynx-js/react";
import { HStack, Switch, VStack, useSeedClassName } from "@seed-design/lynx-react";
import "./styles";
function CustomSwitch({
label,
defaultChecked = false,
}: {
label: string;
defaultChecked?: boolean;
}) {
return (
<Switch.Root defaultChecked={defaultChecked}>
<VStack gap="x2" align="center">
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<text>{label}</text>
</VStack>
</Switch.Root>
);
}
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<HStack className="switch-preview" gap="x6">
<CustomSwitch label="regular" />
<CustomSwitch label="medium" defaultChecked />
<CustomSwitch label="bold" />
</HStack>
</page>
);
}
root.render(<Root />);
Lynx Switch는 React Switch와 다음과 같은 차이가 있습니다.
- 이벤트 핸들링:
onChange 대신 onCheckedChange만 노출합니다. tap 핸들러는 Switch 내부에서 소유합니다.
- 렌더링 요소: HTML
<label> / <input> 대신 네이티브 <view> / <text> 요소를 렌더링합니다.
- Compound 구조:
Switch.Root → Switch.Control → Switch.Thumb / Switch.Label 구성과 Context로 상태/variant를 공유하는 흐름은 동일합니다. Control에 tone/size를 직접 전달해 Root를 override 하는 것도 지원합니다.
- ref forwarding: 모든 서브 컴포넌트는
React.forwardRef를 지원합니다.
현재 Lynx 플랫폼 제약으로 다음 기능이 지원되지 않습니다.
| 기능 | 웹 대응 | 설명 |
|---|
SwitchHiddenInput | <input type="checkbox"> | Lynx에 HTML form 제출 모델 없음 |
name / value / required / invalid | form field props | Lynx에 native form 제출 모델 없음 |
focus / focusVisible | 키보드 포커스 | Lynx에 키보드 포커스 개념 없음 |
onChange (raw DOM event) | React.ChangeEvent | 의미 없음. onCheckedChange로 대체 |
| 기능 | 웹 대응 | 설명 |
|---|
active (pressed) 모디파이어 | data-active | switchmark recipe CSS에 pressed 상태가 정의되면 활성화 |