Action Button
명확한 액션을 쉽게 수행할 수 있도록 돕는 기본 인터랙션 컴포넌트입니다.
@seed-design/lynx-react@0.1.0, @seed-design/lynx-css@0.1.0
npm install @seed-design/lynx-react @seed-design/lynx-css
pnpm add @seed-design/lynx-react @seed-design/lynx-css
yarn add @seed-design/lynx-react @seed-design/lynx-css
bun add @seed-design/lynx-react @seed-design/lynx-css
import "./styles";
import { root, useState } from "@lynx-js/react";
import { ActionButton, useSeedClassName, VStack } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [count, setCount] = useState(0);
function handleTap() {
"background only";
setCount((value) => value + 1);
}
return (
<page className={seedClassName}>
<VStack className="action-button-preview" gap="x3">
<text className="action-button-preview__description">버튼을 탭하면 상태가 바뀝니다.</text>
<ActionButton variant="brandSolid" size="medium" bindtap={handleTap}>
{count === 0 ? "탭해 보세요" : `${count}번 탭했어요`}
</ActionButton>
<ActionButton variant="neutralOutline" disabled={count === 0}>
{count === 0 ? "먼저 위 버튼을 탭하세요" : "활성화됨"}
</ActionButton>
</VStack>
</page>
);
}
root.render(<Root />);
icon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefined
prefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefined
suffixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefined
flexGrow?boolean | Property.FlexGrow | undefined
children?React.ReactNode
className?string | undefined
bindtap?EventHandler<BaseTouchEvent<Target>> | undefined
main-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefined
accessibility-label?string | undefined
accessibility-element?boolean | undefined
accessibility-traits?"disabled" | "text" | "image" | "button" | "link" | "header" | "search" | "selected" | "playable" | "keyboard" | "summary" | "updating" | "adjustable" | "tabbar" | "none" | undefined
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton variant="brandSolid">라벨</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton variant="neutralSolid">라벨</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton variant="neutralWeak">라벨</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton variant="criticalSolid">라벨</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton variant="brandOutline">라벨</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton variant="neutralOutline">라벨</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
Lynx에서는 기본 Ghost 스타일을 지원합니다. 웹의 color, fontWeight 사용자 지정은 지원하지 않습니다.
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton variant="ghost">Ghost</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import IconPlusFill from "@karrotmarket/lynx-monochrome-icon/IconPlusFill";
import { root } from "@lynx-js/react";
import { ActionButton, Icon, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton layout="iconOnly" accessibility-label="추가">
<Icon icon={<IconPlusFill />} />
</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import IconPlusFill from "@karrotmarket/lynx-monochrome-icon/IconPlusFill";
import { root } from "@lynx-js/react";
import { ActionButton, PrefixIcon, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton>
<PrefixIcon icon={<IconPlusFill />} />
라벨
</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import IconChevronRightFill from "@karrotmarket/lynx-monochrome-icon/IconChevronRightFill";
import { root } from "@lynx-js/react";
import { ActionButton, SuffixIcon, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton>
라벨
<SuffixIcon icon={<IconChevronRightFill />} />
</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import { root } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton disabled>라벨</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
import "./styles";
import { root, useState } from "@lynx-js/react";
import { ActionButton, useSeedClassName } from "@seed-design/lynx-react";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [loading, setLoading] = useState(false);
function handleTap() {
"background only";
setLoading(true);
setTimeout(() => setLoading(false), 2000);
}
return (
<page className={seedClassName}>
<view className="action-button-preview">
<ActionButton loading={loading} bindtap={handleTap}>
시간이 걸리는 액션
</ActionButton>
</view>
</page>
);
}
root.render(<Root />);
Lynx ActionButton은 React ActionButton과 다음과 같은 차이가 있습니다.
- 이벤트 핸들링:
onClick 대신 bindtap을 사용합니다.
- 렌더링 요소: HTML
<button> 대신 네이티브 <view>/<text> 요소를 렌더링합니다.
- 접근성 라벨링:
aria-label 대신 Lynx native accessibility-label을 사용합니다.
- ref forwarding:
React.forwardRef로 ref를 전달할 수 있습니다.
- 아이콘 렌더링 방식: 웹의 SVG
currentColor 대신 Lynx <image>의 tint-color를 사용합니다. Icon / PrefixIcon / SuffixIcon이 recipe color를 native tint로 동기화합니다.
- 호환 아이콘 prop: 기존
prefixIcon / suffixIcon / icon prop도 계속 지원하지만, 기본 예시는 child slot API를 사용합니다.
- Loading 상태: 웹과 동일하게 spinner를 absolute로 얹고 기존 content를 숨겨 원래 버튼 폭을 유지합니다.
현재 Lynx 플랫폼 제약으로 다음 기능이 지원되지 않습니다.
| 기능 | 웹 대응 | 설명 |
|---|
color | StyleProps | CSS variable 동적 주입 제한 |
fontWeight | StyleProps | CSS variable 동적 주입 제한 |
bleedX / bleedY | StyleProps | CSS variable 동적 주입 제한 |
React 문서의 Bleed 예제는 bleedX와 bleedY가 필요하므로 Lynx Examples에서 제외했습니다.