Component
FormActions
기본 취소·제출 버튼 또는 커스텀 액션을 PC·모바일에 맞게 배치합니다.
Playground
옵션을 조절하며 화면과 사용 코드를 함께 확인하세요.
actions: 기본 버튼 / 커스텀 목록 / 빈 목록
cancelOptions.show
cancelOptions.children
cancelOptions.variant
cancelOptions.color
submitOptions.show
submitOptions.children
submitOptions.variant
submitOptions.color
두 버튼의 size
submitOptions.loading
cancelOptions.disabled
submitOptions.disabled
destructiveOptions 지정 여부
hintOptions.show
hintShow를 켜면 안내 문구에 적용됩니다.
hintShow를 켜면 안내의 ReactNode 표현에 적용됩니다.
import { useState } from 'react'
import { type FormActionsProps, Button, TextField, FormActions } from '@amineslab/ui'
const formActionsInitial = {
cancelShow: true,
submitShow: true,
cancelText: '취소',
submitText: '저장',
cancelVariant: 'ghost',
submitVariant: 'primary',
cancelColor: 'inverse',
submitColor: 'inverse',
size: 'md',
loading: false,
cancelDisabled: false,
submitDisabled: false,
hintShow: false,
hintText: '변경 사항을 확인한 뒤 저장해 주세요.',
richHint: false,
actionsMode: 'builtin',
destructiveShow: false,
};
function FormActionsExample(options: Partial<typeof formActionsInitial>) {
const state = { ...formActionsInitial, ...options };
const [saved, setSaved] = useState(false);
const [name, setName] = useState('Amineslab UI');
const reset = () => {
setName('Amineslab UI');
setSaved(false);
};
const actions: FormActionsProps['actions'] = state.actionsMode === 'empty'
? []
: state.actionsMode === 'custom'
? [
{ children: '임시 저장', variant: 'ghost', onClick: () => setSaved(true) },
<Button key="preview" type="button" variant="outline" onClick={() => setName('미리보기 프로젝트')}>
미리보기
</Button>,
{ children: saved ? '저장됨' : '게시', type: 'submit', color: 'brand' },
]
: undefined;
return (<form className="grid w-full gap-6" onSubmit={(event) => {
event.preventDefault();
setSaved(true);
}}>
<TextField label="프로젝트 이름" value={name} onValueChange={(value) => {
setName(value);
setSaved(false);
}}/>
<FormActions cancelOptions={{
show: state.cancelShow,
children: state.cancelText,
onClick: reset,
disabled: state.cancelDisabled,
variant: state.cancelVariant as 'ghost' | 'outline' | 'secondary',
color: state.cancelColor as 'inverse' | 'brand' | 'danger',
size: state.size as 'xs' | 'sm' | 'md' | 'lg' | 'xl',
}} destructiveOptions={state.destructiveShow ? { children: '삭제', onClick: reset } : undefined} submitOptions={{
show: state.submitShow,
children: saved ? '저장됨' : state.submitText,
disabled: state.submitDisabled,
loading: state.loading,
loadingLabel: '저장 중…',
variant: state.submitVariant as 'primary' | 'outline' | 'secondary',
color: state.submitColor as 'inverse' | 'brand' | 'danger',
size: state.size as 'xs' | 'sm' | 'md' | 'lg' | 'xl',
}} actions={actions} hintOptions={{
show: state.hintShow,
children: state.richHint ? (<span>
<strong>안내: </strong>
{state.hintText}
</span>) : (state.hintText),
}}/>
</form>);
}
export default function ExamplePreview() {
return (<FormActionsExample {...({"cancelShow":true,"submitShow":true,"cancelText":"취소","submitText":"저장","cancelVariant":"ghost","submitVariant":"primary","cancelColor":"inverse","submitColor":"inverse","size":"md","loading":false,"cancelDisabled":false,"submitDisabled":false,"hintShow":false,"hintText":"변경 사항을 확인한 뒤 저장해 주세요.","richHint":false,"actionsMode":"builtin","destructiveShow":false} as const)}/>)
}Props
@amineslab/ui에서 FormActions를 가져옵니다.
import { FormActions } from '@amineslab/ui'FormActions
버튼 텍스트는 children을 사용하며 모든 ButtonProps를 전달할 수 있습니다.
| Name | Type | Default | Description |
|---|---|---|---|
cancelOptions | ActionButtonProps | - | 기본값: show true, type button, variant ghost, color inverse, size lg, children 취소. onClick으로 취소 동작을 지정합니다. |
submitOptions | ActionButtonProps | - | 기본값: show true, type submit, variant primary, color inverse, size lg, children 저장. loading·loadingLabel·disabled·form·아이콘 등을 ButtonProps로 설정합니다. |
actions | (ActionButtonProps | ReactNode)[] | - | 지정하면 기본 버튼 대신 이 목록만 표시합니다. []는 버튼 없음입니다. 옵션 버튼은 type button·size md이 기본이며 show false 항목은 숨깁니다. |
destructiveOptions | ActionButtonProps | - | 기본값: type button, variant outline, color danger, size lg. actions와 별개로 버튼 그룹 밖에 렌더합니다. |
hintOptions | { show?: boolean; children?: ReactNode; id?: string } | - | show 기본값은 true입니다. 문자열·JSX를 표시하며 false 또는 빈 콘텐츠는 영역과 ARIA 연결을 제거합니다. 기본 제출 버튼과 옵션형 actions에 연결하고 직접 전달한 ReactNode의 ARIA 속성은 변경하지 않습니다. |
className | string | - | 외곽 배치를 조정합니다. |
ActionButtonProps
ButtonProps & { show?: boolean }. 버튼별 설정은 독립적입니다.
| Name | Type | Default | Description |
|---|---|---|---|
show | boolean | true | false면 렌더링하지 않습니다. |
children | ReactNode | - | 버튼 텍스트 또는 JSX입니다. |
loading / loadingLabel / disabled | ButtonProps | - | 제출 로딩이 취소를 자동으로 비활성화하지 않습니다. 필요하면 cancelOptions.disabled도 명시합니다. |
Examples
자주 쓰는 조합을 살펴보고, 필요한 예시의 코드를 펼쳐 확인하세요.
힌트와 저장 중 상태
힌트는 데스크톱에서 버튼을 밀어내고 모바일에서는 액션 위에 쌓입니다. pending 중에는 중복 실행을 막습니다.
import { useState } from 'react'
import { type FormActionsProps, Button, TextField, FormActions } from '@amineslab/ui'
const formActionsInitial = {
cancelShow: true,
submitShow: true,
cancelText: '취소',
submitText: '저장',
cancelVariant: 'ghost',
submitVariant: 'primary',
cancelColor: 'inverse',
submitColor: 'inverse',
size: 'md',
loading: false,
cancelDisabled: false,
submitDisabled: false,
hintShow: false,
hintText: '변경 사항을 확인한 뒤 저장해 주세요.',
richHint: false,
actionsMode: 'builtin',
destructiveShow: false,
};
function FormActionsExample(options: Partial<typeof formActionsInitial>) {
const state = { ...formActionsInitial, ...options };
const [saved, setSaved] = useState(false);
const [name, setName] = useState('Amineslab UI');
const reset = () => {
setName('Amineslab UI');
setSaved(false);
};
const actions: FormActionsProps['actions'] = state.actionsMode === 'empty'
? []
: state.actionsMode === 'custom'
? [
{ children: '임시 저장', variant: 'ghost', onClick: () => setSaved(true) },
<Button key="preview" type="button" variant="outline" onClick={() => setName('미리보기 프로젝트')}>
미리보기
</Button>,
{ children: saved ? '저장됨' : '게시', type: 'submit', color: 'brand' },
]
: undefined;
return (<form className="grid w-full gap-6" onSubmit={(event) => {
event.preventDefault();
setSaved(true);
}}>
<TextField label="프로젝트 이름" value={name} onValueChange={(value) => {
setName(value);
setSaved(false);
}}/>
<FormActions cancelOptions={{
show: state.cancelShow,
children: state.cancelText,
onClick: reset,
disabled: state.cancelDisabled,
variant: state.cancelVariant as 'ghost' | 'outline' | 'secondary',
color: state.cancelColor as 'inverse' | 'brand' | 'danger',
size: state.size as 'xs' | 'sm' | 'md' | 'lg' | 'xl',
}} destructiveOptions={state.destructiveShow ? { children: '삭제', onClick: reset } : undefined} submitOptions={{
show: state.submitShow,
children: saved ? '저장됨' : state.submitText,
disabled: state.submitDisabled,
loading: state.loading,
loadingLabel: '저장 중…',
variant: state.submitVariant as 'primary' | 'outline' | 'secondary',
color: state.submitColor as 'inverse' | 'brand' | 'danger',
size: state.size as 'xs' | 'sm' | 'md' | 'lg' | 'xl',
}} actions={actions} hintOptions={{
show: state.hintShow,
children: state.richHint ? (<span>
<strong>안내: </strong>
{state.hintText}
</span>) : (state.hintText),
}}/>
</form>);
}
export default function ExamplePreview() {
return (<FormActionsExample hintShow loading cancelDisabled/>)
}옵션과 커스텀 버튼 혼합
actions를 지정하면 기본 취소·제출 옵션은 무시합니다. 빈 배열은 버튼을 숨깁니다.
import { useState } from 'react'
import { type FormActionsProps, Button, TextField, FormActions } from '@amineslab/ui'
const formActionsInitial = {
cancelShow: true,
submitShow: true,
cancelText: '취소',
submitText: '저장',
cancelVariant: 'ghost',
submitVariant: 'primary',
cancelColor: 'inverse',
submitColor: 'inverse',
size: 'md',
loading: false,
cancelDisabled: false,
submitDisabled: false,
hintShow: false,
hintText: '변경 사항을 확인한 뒤 저장해 주세요.',
richHint: false,
actionsMode: 'builtin',
destructiveShow: false,
};
function FormActionsExample(options: Partial<typeof formActionsInitial>) {
const state = { ...formActionsInitial, ...options };
const [saved, setSaved] = useState(false);
const [name, setName] = useState('Amineslab UI');
const reset = () => {
setName('Amineslab UI');
setSaved(false);
};
const actions: FormActionsProps['actions'] = state.actionsMode === 'empty'
? []
: state.actionsMode === 'custom'
? [
{ children: '임시 저장', variant: 'ghost', onClick: () => setSaved(true) },
<Button key="preview" type="button" variant="outline" onClick={() => setName('미리보기 프로젝트')}>
미리보기
</Button>,
{ children: saved ? '저장됨' : '게시', type: 'submit', color: 'brand' },
]
: undefined;
return (<form className="grid w-full gap-6" onSubmit={(event) => {
event.preventDefault();
setSaved(true);
}}>
<TextField label="프로젝트 이름" value={name} onValueChange={(value) => {
setName(value);
setSaved(false);
}}/>
<FormActions cancelOptions={{
show: state.cancelShow,
children: state.cancelText,
onClick: reset,
disabled: state.cancelDisabled,
variant: state.cancelVariant as 'ghost' | 'outline' | 'secondary',
color: state.cancelColor as 'inverse' | 'brand' | 'danger',
size: state.size as 'xs' | 'sm' | 'md' | 'lg' | 'xl',
}} destructiveOptions={state.destructiveShow ? { children: '삭제', onClick: reset } : undefined} submitOptions={{
show: state.submitShow,
children: saved ? '저장됨' : state.submitText,
disabled: state.submitDisabled,
loading: state.loading,
loadingLabel: '저장 중…',
variant: state.submitVariant as 'primary' | 'outline' | 'secondary',
color: state.submitColor as 'inverse' | 'brand' | 'danger',
size: state.size as 'xs' | 'sm' | 'md' | 'lg' | 'xl',
}} actions={actions} hintOptions={{
show: state.hintShow,
children: state.richHint ? (<span>
<strong>안내: </strong>
{state.hintText}
</span>) : (state.hintText),
}}/>
</form>);
}
export default function ExamplePreview() {
return (<FormActionsExample actionsMode="custom" hintShow richHint/>)
}사용 기준
- PC에서는 안내를 왼쪽, 버튼을 오른쪽에 배치하고 세로 중앙을 맞춥니다.
- 모바일에서는 안내를 버튼 위에 가운데 정렬합니다. 버튼은 같은 폭으로 배치하고, 공간이 부족하면 세로로 쌓습니다.
- 저장은 type="submit"이며 부모 form의 onSubmit으로 처리합니다.
- 취소는 type="button"입니다. cancelOptions.onClick에 취소 동작을 지정하세요.
- actions를 지정하면 cancelOptions와 submitOptions는 무시합니다. 빈 배열을 지정하면 버튼을 표시하지 않습니다.
- 커스텀 ReactNode의 스타일과 이벤트는 그대로 유지합니다.
- destructiveOptions는 삭제처럼 되돌리기 어려운 작업입니다. PC에서는 행의 시작 쪽, 모바일에서는 맨 위에 두어 주 작업과 떨어뜨립니다.
- 링크 이동 액션은 actions에 Button asChild로 감싼 링크 노드를 넣습니다.
- 겉모양만으로 사용을 결정하지 마세요.
- 의미와 키보드 동작, 모바일에서의 흐름을 함께 확인하세요.
접근성
화면에 맞는 이름을 제공하고, 키보드만으로 작업을 마칠 수 있는지 확인하세요.
Enter: submit 버튼이 속한 폼을 제출합니다.