Radio Group
사용자가 여러 옵션 중 하나만 선택할 수 있게 하는 라디오 그룹 컴포넌트입니다.
import "./styles";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
return (
<VStack className="radio-group-preview">
<RadioGroup
defaultValue="apple"
label="좋아하는 과일"
description="좋아하는 과일을 선택해 주세요."
indicator="선택"
>
<RadioGroupItem value="apple" label="Apple" tone="neutral" size="large" />
<RadioGroupItem value="banana" label="Banana" tone="neutral" size="large" />
<RadioGroupItem value="orange" label="Orange" tone="neutral" size="large" />
</RadioGroup>
</VStack>
);
}Installation
npx @seed-design/cli add ui:radio-grouppnpm dlx @seed-design/cli add ui:radio-groupyarn dlx @seed-design/cli add ui:radio-groupbun x @seed-design/cli add ui:radio-group의존성 설치
npm install @seed-design/lynx-reactyarn add @seed-design/lynx-reactpnpm add @seed-design/lynx-reactbun add @seed-design/lynx-react아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:radio-group
* @requires @seed-design/lynx-react@^1.0.0
* @requires @seed-design/lynx-css@^1.0.0
**/
import * as React from "@lynx-js/react";
import {
RadioGroup as SeedRadioGroup,
RadioGroupField as SeedRadioGroupField,
} from "@seed-design/lynx-react";
export interface RadioGroupProps extends SeedRadioGroupField.RootProps {
label?: React.ReactNode;
/**
* @default "medium"
*/
labelWeight?: SeedRadioGroupField.LabelProps["weight"];
indicator?: React.ReactNode;
showRequiredIndicator?: boolean;
description?: React.ReactNode;
errorMessage?: React.ReactNode;
}
/**
* @see https://seed-design.io/lynx/components/radio-group
*/
export const RadioGroup = React.forwardRef<unknown, RadioGroupProps>(
(
{
label,
labelWeight,
indicator,
showRequiredIndicator,
description,
errorMessage,
children,
"accessibility-label": accessibilityLabel,
...props
},
ref,
) => {
const renderHeader = label != null || indicator != null;
const renderErrorMessage = props.invalid && errorMessage != null;
const renderDescription = description != null && !renderErrorMessage;
const renderFooter = renderDescription || renderErrorMessage;
const defaultAccessibilityLabel = typeof label === "string" ? label : undefined;
return (
<SeedRadioGroupField.Root
ref={ref}
accessibility-label={accessibilityLabel ?? defaultAccessibilityLabel}
{...props}
>
{renderHeader ? (
<SeedRadioGroupField.Header>
<SeedRadioGroupField.Label weight={labelWeight}>
{label}
{showRequiredIndicator ? <SeedRadioGroupField.RequiredIndicator /> : null}
{indicator != null ? (
<SeedRadioGroupField.IndicatorText>{indicator}</SeedRadioGroupField.IndicatorText>
) : null}
</SeedRadioGroupField.Label>
</SeedRadioGroupField.Header>
) : null}
<SeedRadioGroup.Root>{children}</SeedRadioGroup.Root>
{renderFooter ? (
<SeedRadioGroupField.Footer>
{renderDescription ? (
<SeedRadioGroupField.Description>{description}</SeedRadioGroupField.Description>
) : null}
{renderErrorMessage ? (
<SeedRadioGroupField.ErrorMessage>{errorMessage}</SeedRadioGroupField.ErrorMessage>
) : null}
</SeedRadioGroupField.Footer>
) : null}
</SeedRadioGroupField.Root>
);
},
);
RadioGroup.displayName = "RadioGroup";
export interface RadioGroupItemProps extends SeedRadioGroup.ItemProps {
label?: React.ReactNode;
}
/**
* @see https://seed-design.io/lynx/components/radio-group
*/
export const RadioGroupItem = React.forwardRef<unknown, RadioGroupItemProps>(
({ label, children, "accessibility-label": accessibilityLabel, ...otherProps }, ref) => {
const defaultAccessibilityLabel = typeof label === "string" ? label : undefined;
return (
<SeedRadioGroup.Item
ref={ref}
accessibility-label={accessibilityLabel ?? defaultAccessibilityLabel}
{...otherProps}
>
<SeedRadioGroup.ItemControl>
<SeedRadioGroup.ItemIndicator />
</SeedRadioGroup.ItemControl>
{label != null ? <SeedRadioGroup.ItemLabel>{label}</SeedRadioGroup.ItemLabel> : null}
{children}
</SeedRadioGroup.Item>
);
},
);
RadioGroupItem.displayName = "RadioGroupItem";
export interface RadiomarkProps extends Omit<SeedRadioGroup.ItemControlProps, "children"> {}
/**
* @see https://seed-design.io/lynx/components/radio-group
*/
export const Radiomark = React.forwardRef<unknown, RadiomarkProps>((props, ref) => {
return (
<SeedRadioGroup.ItemControl ref={ref} {...props}>
<SeedRadioGroup.ItemIndicator />
</SeedRadioGroup.ItemControl>
);
});
Radiomark.displayName = "Radiomark";
/**
* 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.
*/
Usage
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export function ContactMethod() {
return (
<RadioGroup
label="선호하는 연락 방법"
indicator="필수"
description="연락받을 방법을 선택해 주세요."
defaultValue="chat"
>
<RadioGroupItem value="chat" label="채팅" />
<RadioGroupItem value="phone" label="전화" />
</RadioGroup>
);
}invalid가 true이면 description 대신 errorMessage를 표시합니다. weight, size, tone은 RadioGroupItem에 지정하며, 이전 버전에서 그룹에 지정한 값도 각 항목으로 옮겨야 합니다. Registry RadioGroup은 React와 같이 required와 readOnly를 받지 않습니다.
Props
RadioGroup
Prop
Type
label?React.ReactNodeindicator?React.ReactNodeshowRequiredIndicator?boolean | undefineddescription?React.ReactNodeerrorMessage?React.ReactNodeonValueChange?((value: string) => void) | undefinedRadioGroupItem
Prop
Type
label?React.ReactNodevaluestringRadiomark
Prop
Type
접근성
RadioGroup의 문자열 label은 그룹의 접근성 이름으로도 사용됩니다. RadioGroupItem의 문자열 label은 항목 이름으로 연결되며 선택 상태와 비활성 상태도 전달됩니다.
raw <text>를 사용하거나 항목에 텍스트가 없으면 accessibility-label을 지정하세요. raw <text>의 정적 텍스트는 접근성 이름을 만들 수 없습니다.
Examples
Sizes
import "./styles";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
return (
<VStack className="radio-group-preview" gap="x5">
<RadioGroup accessibility-label="과일 선택" defaultValue="apple">
<RadioGroupItem value="apple" label="사과" size="medium" tone="neutral" />
<RadioGroupItem value="banana" label="바나나" size="medium" tone="neutral" />
<RadioGroupItem value="orange" label="오렌지" size="medium" tone="neutral" />
</RadioGroup>
<RadioGroup accessibility-label="색상 선택" defaultValue="red">
<RadioGroupItem value="red" label="빨간색" size="large" tone="neutral" />
<RadioGroupItem value="blue" label="파란색" size="large" tone="neutral" />
<RadioGroupItem value="green" label="초록색" size="large" tone="neutral" />
</RadioGroup>
</VStack>
);
}Tones
Brand
import "./styles";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
return (
<VStack className="radio-group-preview">
<RadioGroup accessibility-label="과일 선택" defaultValue="apple">
<RadioGroupItem value="apple" label="사과" tone="brand" size="large" />
<RadioGroupItem value="banana" label="바나나" tone="brand" size="large" />
<RadioGroupItem value="orange" label="오렌지" tone="brand" size="large" />
</RadioGroup>
</VStack>
);
}Neutral
import "./styles";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
return (
<VStack className="radio-group-preview">
<RadioGroup accessibility-label="과일 선택" defaultValue="apple">
<RadioGroupItem value="apple" label="사과" tone="neutral" size="large" />
<RadioGroupItem value="banana" label="바나나" tone="neutral" size="large" />
<RadioGroupItem value="orange" label="오렌지" tone="neutral" size="large" />
</RadioGroup>
</VStack>
);
}Weights
import "./styles";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
return (
<VStack className="radio-group-preview">
<RadioGroup accessibility-label="글꼴 굵기 선택" defaultValue="regular">
<RadioGroupItem
value="regular"
label="Regular"
weight="regular"
tone="neutral"
size="large"
/>
<RadioGroupItem value="bold" label="Bold" weight="bold" tone="neutral" size="large" />
</RadioGroup>
</VStack>
);
}Long Label
import "./styles";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
const longLabel =
"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.";
export default function Example() {
return (
<VStack className="radio-group-preview">
<RadioGroup accessibility-label="Long label options" defaultValue="medium">
<RadioGroupItem value="medium" label={longLabel} size="medium" tone="neutral" />
<RadioGroupItem value="large" label={longLabel} size="large" tone="neutral" />
</RadioGroup>
</VStack>
);
}Disabled
import "./styles";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
return (
<VStack className="radio-group-preview">
<RadioGroup accessibility-label="Options with disabled" defaultValue="option1">
<RadioGroupItem value="option1" label="Active option" tone="neutral" size="large" />
<RadioGroupItem
value="option2"
label="Disabled option"
tone="neutral"
size="large"
disabled
/>
<RadioGroupItem value="option3" label="Another active option" tone="neutral" size="large" />
</RadioGroup>
</VStack>
);
}Listening to Value Changes
RadioGroup의 onValueChange로 선택 값 변경을 감지할 수 있습니다. Controlled 모드에서는 value와 onValueChange를 함께 사용합니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
const [count, setCount] = useState(0);
const [lastValue, setLastValue] = useState<string | null>(null);
return (
<VStack className="radio-group-preview" gap="x4">
<RadioGroup
accessibility-label="Fruit selection"
defaultValue="apple"
onValueChange={(value) => {
setCount((previous) => previous + 1);
setLastValue(value);
}}
>
<RadioGroupItem value="apple" label="Apple" tone="neutral" size="large" />
<RadioGroupItem value="banana" label="Banana" tone="neutral" size="large" />
<RadioGroupItem value="orange" label="Orange" tone="neutral" size="large" />
</RadioGroup>
<text className="radio-group-preview__status">
onValueChange called: {count} times, last value: {lastValue ?? "-"}
</text>
</VStack>
);
}Use Cases
Using Radiomark
Radiomark는 RadioGroupItem 안에서만 사용할 수 있습니다.
import "./styles";
import { HStack, RadioGroup as RadioGroupPrimitive, VStack } from "@seed-design/lynx-react";
import { RadioGroup, Radiomark } from "@/components/ui/radio-group";
export default function Example() {
return (
<VStack className="radio-group-preview">
<RadioGroup accessibility-label="Weight selection" defaultValue="medium">
<HStack gap="x6">
<RadioGroupPrimitive.Item
accessibility-label="regular"
value="regular"
size="large"
tone="neutral"
>
<VStack gap="x2" align="center">
<Radiomark tone="neutral" size="large" />
<RadioGroupPrimitive.ItemLabel>regular</RadioGroupPrimitive.ItemLabel>
</VStack>
</RadioGroupPrimitive.Item>
<RadioGroupPrimitive.Item
accessibility-label="medium"
value="medium"
size="large"
tone="neutral"
>
<VStack gap="x2" align="center">
<Radiomark tone="neutral" size="large" />
<RadioGroupPrimitive.ItemLabel>medium</RadioGroupPrimitive.ItemLabel>
</VStack>
</RadioGroupPrimitive.Item>
<RadioGroupPrimitive.Item
accessibility-label="bold"
value="bold"
size="large"
tone="neutral"
>
<VStack gap="x2" align="center">
<Radiomark tone="neutral" size="large" />
<RadioGroupPrimitive.ItemLabel>bold</RadioGroupPrimitive.ItemLabel>
</VStack>
</RadioGroupPrimitive.Item>
</HStack>
</RadioGroup>
</VStack>
);
}RadioGroupField Integration
import "./styles";
import { useState } from "@lynx-js/react";
import { ActionButton, HStack, VStack } from "@seed-design/lynx-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function Example() {
const [contact, setContact] = useState("email");
const [firstErrorMessage, setFirstErrorMessage] = useState<string | undefined>();
const [option, setOption] = useState("option1");
const [secondErrorMessage, setSecondErrorMessage] = useState<string | undefined>();
const handleFirstSubmit = () => {
setFirstErrorMessage(contact === "email" ? "이메일은 선택할 수 없습니다." : undefined);
};
const handleSecondSubmit = () => {
setSecondErrorMessage(option === "option1" ? "옵션 1은 선택할 수 없습니다." : undefined);
};
return (
<VStack className="radio-group-preview" maxWidth="640px">
<HStack width="full" gap="x8" align="flex-start" wrap="wrap" justify="center">
<VStack
grow
shrink
minWidth="240px"
style={{ flexBasis: 0 }}
gap="spacingY.componentDefault"
>
<RadioGroup
label="선호하는 연락 방법"
indicator="필수"
description="이메일을 선택하고 제출해보세요."
value={contact}
onValueChange={setContact}
invalid={firstErrorMessage != null}
errorMessage={firstErrorMessage}
>
<RadioGroupItem value="email" label="이메일" tone="neutral" size="large" />
<RadioGroupItem value="phone" label="전화" tone="neutral" size="large" />
<RadioGroupItem value="sms" label="문자" tone="neutral" size="large" />
</RadioGroup>
<ActionButton variant="neutralSolid" bindtap={handleFirstSubmit}>
제출
</ActionButton>
</VStack>
<VStack
grow
shrink
minWidth="240px"
style={{ flexBasis: 0 }}
gap="spacingY.componentDefault"
>
<RadioGroup
label="필수 선택"
labelWeight="bold"
showRequiredIndicator
description="옵션 1을 선택하고 제출해보세요."
value={option}
onValueChange={setOption}
invalid={secondErrorMessage != null}
errorMessage={secondErrorMessage}
>
<RadioGroupItem value="option1" label="옵션 1" tone="neutral" size="large" />
<RadioGroupItem value="option2" label="옵션 2" tone="neutral" size="large" disabled />
<RadioGroupItem value="option3" label="옵션 3" tone="neutral" size="large" />
</RadioGroup>
<ActionButton variant="neutralSolid" bindtap={handleSecondSubmit}>
제출
</ActionButton>
</VStack>
</HStack>
</VStack>
);
}Scale Feedback
Lynx RadioGroupItem은 누르는 동안 Radiomark를 줄입니다. 콘텐츠를 축소하는 SEED 컴포넌트 안에서는 이 효과를 생략합니다. React의 --seed-radiomark-feedback-scale CSS 변수는 제공하지 않습니다.
웹 버전과의 차이
invalid상태에서는description을 제외하고errorMessage만 화면과 접근성 트리에 표시합니다.- 사용자 지정 아이콘은 Lynx 단색 아이콘 컴포넌트를 사용해야 합니다. raw
<svg>아이콘은 지원하지 않습니다. - 값 변경은
onChange가 아닌onValueChange(value: string)로 받습니다. - HTML
<label>과<input type="radio">대신 네이티브<view>와<text>를 렌더링합니다. RadioGroupItem은RadioGroup안에서,Radiomark는 해당 항목 안에서 사용해야 합니다.
사용자 지정 아이콘 패키지는 Registry 기본 설치에 포함되지 않습니다.
bun add @karrotmarket/lynx-monochrome-icon@^1.20.0Lynx 미지원 기능
현재 Lynx 플랫폼 제약으로 다음 기능이 지원되지 않습니다.
런타임 모델 차이로 제외
| 기능 | 웹 대응 | 설명 |
|---|---|---|
| React Hook Form | HTML form 라이브러리 | value와 onValueChange 및 앱 수준 제출 로직으로 대체 |
RadioGroupItemHiddenInput | <input type="radio"> | Lynx에 HTML form 제출 모델 없음 |
name | native form field name | Lynx에 native form 제출 모델 없음 |
focus / focusVisible | 키보드 포커스 | Lynx에 키보드 포커스 개념 없음 |
onChange (raw DOM event) | React.ChangeEvent | 의미 없음. onValueChange로 대체 |
Last updated on