Component
FormSurface
페이지형 폼을 PC 카드와 모바일 full-bleed + 고정 footer로 표현합니다.
Playground
옵션을 조절하며 화면과 사용 코드를 함께 확인하세요.
PC 1280px
Controls
Footer: 모바일 화면 하단 고정 / 본문 흐름
PC 카드 표면 · 모바일은 평평한 배경
PC 본문과 footer 여백 · 모바일 본문은 0
FormSurfaceFooter 표시
긴 폼에서 스크롤과 마지막 필드 가림 확인
Code
import { useState } from 'react'
import { FormSurface, FormSurfaceContent, FormStack, TextField, NumberField, TextareaField, FormSurfaceFooter, FormActions } from '@amineslab/ui'
function FormLayoutExample({ compact = false, gap = 'md', columns = '1', }: {
compact?: boolean;
gap?: string;
columns?: string;
}) {
return (<FormStack gap={compact ? 'sm' : gap === 'sm' || gap === 'lg' ? gap : 'md'} columns={columns === '2' ? 2 : 1}>
<TextField label="프로젝트 이름" placeholder="Amineslab UI" required description="팀에서 식별할 이름입니다."/>
<NumberField label="예산" placeholder="금액을 입력해 주세요"/>
<TextareaField label="설명" placeholder="프로젝트를 소개해 주세요" rows={3}/>
</FormStack>);
}
function FormSurfaceExample({ footer, fixedOnMobile = true, variant = 'soft', padding = 'md', longContent = false, }: {
footer: boolean;
fixedOnMobile?: boolean;
variant?: string;
padding?: string;
longContent?: boolean;
}) {
const [saved, setSaved] = useState(false);
return (<form className="w-full" onSubmit={(event) => {
event.preventDefault();
setSaved(true);
}}>
<FormSurface variant={variant === 'raised' || variant === 'flat' || variant === 'muted' ? variant : 'soft'} padding={padding === 'none' || padding === 'sm' || padding === 'lg' ? padding : 'md'}>
<FormSurfaceContent>
<FormStack gap="lg">
<FormLayoutExample compact={false}/>
{longContent && (<>
<TextareaField rows={12} label="운영 정책" placeholder="운영 정책을 입력해 주세요"/>
<TextareaField label="마지막 메모" rows={12} placeholder="하단 액션에 가려지지 않는지 확인해 주세요"/>
</>)}
</FormStack>
</FormSurfaceContent>
{footer && (<FormSurfaceFooter fixedOnMobile={fixedOnMobile}>
<FormActions cancelOptions={{ onClick: () => setSaved(false) }} submitOptions={{ children: saved ? '저장됨' : '저장' }}/>
</FormSurfaceFooter>)}
</FormSurface>
</form>);
}
export default function ExamplePreview() {
return (<FormSurfaceExample {...({"footer":true,"fixedOnMobile":true,"variant":"soft","padding":"md","longContent":false} as const)}/>)
}Props
import {
FormSurface,
FormSurfaceContent,
FormSurfaceFooter,
} from '@amineslab/ui'FormSurface
Card 표면을 화면 폭에 맞춰 조정합니다.
| Name | Type | Default | Description |
|---|---|---|---|
variant | 'raised' | 'flat' | 'muted' | 'soft' | 'soft' | PC 카드 배경·테두리·그림자입니다. 모바일은 평평한 표면을 사용합니다. |
padding | 'none' | 'sm' | 'md' | 'lg' | 'md' | PC 본문 여백과 footer 좌우 여백을 함께 변경합니다. 모바일 본문 여백은 0입니다. |
className | string | - | 공개 surface wrapper 클래스입니다. |
FormSurfaceContent
폼 본문 padding을 PC·모바일 규칙으로 관리합니다.
| Name | Type | Default | Description |
|---|---|---|---|
children* | ReactNode | - | 스크롤 대상 폼 본문입니다. |
FormSurfaceFooter
모바일 하단 액션과 safe area를 관리하며 높이만큼 자리를 확보합니다.
| Name | Type | Default | Description |
|---|---|---|---|
fixedOnMobile | boolean | true | 모바일에서 화면 하단 고정입니다. false면 본문 흐름에 배치합니다. PC는 항상 카드 내부입니다. |
children* | ReactNode | - | FormActions 같은 액션 콘텐츠입니다. |
className | string | - | 공개 footer wrapper 클래스입니다. |
Examples
자주 쓰는 조합을 살펴보고, 필요한 예시의 코드를 펼쳐 확인하세요.
페이지 폼 표면
PC에서는 카드 안의 footer, 모바일에서는 safe-area를 포함한 고정 footer로 바뀝니다.
import { useState } from 'react'
import { FormSurface, FormSurfaceContent, FormStack, TextField, NumberField, TextareaField, FormSurfaceFooter, FormActions } from '@amineslab/ui'
function FormLayoutExample({ compact = false, gap = 'md', columns = '1', }: {
compact?: boolean;
gap?: string;
columns?: string;
}) {
return (<FormStack gap={compact ? 'sm' : gap === 'sm' || gap === 'lg' ? gap : 'md'} columns={columns === '2' ? 2 : 1}>
<TextField label="프로젝트 이름" placeholder="Amineslab UI" required description="팀에서 식별할 이름입니다."/>
<NumberField label="예산" placeholder="금액을 입력해 주세요"/>
<TextareaField label="설명" placeholder="프로젝트를 소개해 주세요" rows={3}/>
</FormStack>);
}
function FormSurfaceExample({ footer, fixedOnMobile = true, variant = 'soft', padding = 'md', longContent = false, }: {
footer: boolean;
fixedOnMobile?: boolean;
variant?: string;
padding?: string;
longContent?: boolean;
}) {
const [saved, setSaved] = useState(false);
return (<form className="w-full" onSubmit={(event) => {
event.preventDefault();
setSaved(true);
}}>
<FormSurface variant={variant === 'raised' || variant === 'flat' || variant === 'muted' ? variant : 'soft'} padding={padding === 'none' || padding === 'sm' || padding === 'lg' ? padding : 'md'}>
<FormSurfaceContent>
<FormStack gap="lg">
<FormLayoutExample compact={false}/>
{longContent && (<>
<TextareaField rows={12} label="운영 정책" placeholder="운영 정책을 입력해 주세요"/>
<TextareaField label="마지막 메모" rows={12} placeholder="하단 액션에 가려지지 않는지 확인해 주세요"/>
</>)}
</FormStack>
</FormSurfaceContent>
{footer && (<FormSurfaceFooter fixedOnMobile={fixedOnMobile}>
<FormActions cancelOptions={{ onClick: () => setSaved(false) }} submitOptions={{ children: saved ? '저장됨' : '저장' }}/>
</FormSurfaceFooter>)}
</FormSurface>
</form>);
}
export default function ExamplePreview() {
return (<FormSurfaceExample footer/>)
}사용 기준
권장하는 사용
- FormSurface는 페이지 폼의 바깥 표면입니다.
- PC는 카드와 내부 여백, 모바일은 평평한 본문과 하단 액션을 제공합니다.
- Footer가 자기 높이만큼 본문 자리를 확보하므로 특정 main 구조가 필요하지 않습니다.
- native form으로 감싸 제출을 연결하세요.
- Dialog·Drawer에서는 해당 컴포넌트의 Footer를 사용합니다.
피해야 할 사용
- 겉모양만으로 사용을 결정하지 마세요.
- 의미와 키보드 동작, 모바일에서의 흐름을 함께 확인하세요.
접근성
화면에 맞는 이름을 제공하고, 키보드만으로 작업을 마칠 수 있는지 확인하세요.
Tab: 본문과 고정 footer 사이에서 순서가 끊기지 않습니다.