Result Section
작업 결과를 안내하고 다음 액션을 제공하는 템플릿입니다.
import IconDiamond from "@karrotmarket/lynx-multicolor-icon/IconDiamond";
import { useState } from "@lynx-js/react";
import { Box, Icon, VStack } from "@seed-design/lynx-react";
import { ResultSection } from "@/components/ui/result-section";
export default function Example() {
const [actionResult, setActionResult] = useState<string | null>(null);
function handlePrimaryAction() {
"background only";
setActionResult("Primary Action Clicked");
}
function handleSecondaryAction() {
"background only";
setActionResult("Secondary Action Clicked");
}
return (
<VStack width="full" maxWidth="320px" alignSelf="center" grow minHeight="0">
<ResultSection
asset={
<Box pb="x4">
<Icon icon={<IconDiamond />} size="x10" multicolor />
</Box>
}
title="결과 타이틀"
description={actionResult ?? "부가 설명을 적어주세요"}
primaryActionProps={{ children: "Primary Action", bindtap: handlePrimaryAction }}
secondaryActionProps={{
children: "Secondary Action",
bindtap: handleSecondaryAction,
}}
/>
</VStack>
);
}
npx @seed-design/cli@latest add ui:result-section
pnpm dlx @seed-design/cli@latest add ui:result-section
yarn dlx @seed-design/cli@latest add ui:result-section
bun x @seed-design/cli@latest add ui:result-section
의존성 설치
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:result-section
* @requires @seed-design/lynx-react@^1.0.0
* @requires @seed-design/lynx-css@^1.0.0
**/
import * as React from "@lynx-js/react";
import {
ActionButton,
Text,
VStack,
type ActionButtonProps,
type TextProps,
type VStackProps,
} from "@seed-design/lynx-react";
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
export type ResultSectionProps = DistributiveOmit<VStackProps, "children"> & {
/**
* @default "large"
*/
size?: "large" | "medium";
asset?: React.ReactNode;
title?: React.ReactNode;
description?: React.ReactNode;
primaryActionProps?: ActionButtonProps;
secondaryActionProps?: ActionButtonProps;
};
const textStyles = {
title: {
large: "t8Bold",
medium: "t5Bold",
},
description: {
large: "t5Regular",
medium: "t4Regular",
},
} as const satisfies Record<
string,
Record<NonNullable<ResultSectionProps["size"]>, NonNullable<TextProps["textStyle"]>>
>;
const textContainerProperties = {
large: {
gap: "x3",
pb: "x7",
},
medium: {
gap: "x2",
pb: "x6",
},
} as const satisfies Record<NonNullable<ResultSectionProps["size"]>, VStackProps>;
/**
* @see https://seed-design.io/lynx/components/result-section
*/
export const ResultSection = React.forwardRef<unknown, ResultSectionProps>((props, ref) => {
const {
size = "large",
asset,
title,
description,
primaryActionProps,
secondaryActionProps,
...otherProps
} = props;
return (
<VStack
{...(ref ? { ref } : {})}
justify="center"
align="center"
px="x12"
py="x4"
grow
{...otherProps}
>
{asset}
<VStack {...textContainerProperties[size]}>
<Text align="center" color="fg.neutral" textStyle={textStyles.title[size]}>
{title}
</Text>
<Text align="center" color="fg.neutralMuted" textStyle={textStyles.description[size]}>
{description}
</Text>
</VStack>
{primaryActionProps || secondaryActionProps ? (
<VStack align="center" gap="x5">
{primaryActionProps ? (
<ActionButton variant="neutralWeak" size="medium" {...primaryActionProps} />
) : null}
{secondaryActionProps ? (
<ActionButton variant="ghost" size="small" {...secondaryActionProps} />
) : null}
</VStack>
) : null}
</VStack>
);
});
ResultSection.displayName = "ResultSection";
/**
* 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.
*/
asset?React.ReactNode
title?React.ReactNode
description?React.ReactNode
primaryActionProps?ActionButtonProps | undefined
secondaryActionProps?ActionButtonProps | undefined
ResultSection은 VStack의 레이아웃·스타일 prop도 받습니다.
전체 화면을 차지하는 상태를 표현할 때 사용합니다.
import { VStack } from "@seed-design/lynx-react";
import { ResultSection } from "@/components/ui/result-section";
export default function Example() {
return (
<VStack width="full" maxWidth="320px" alignSelf="center" grow minHeight="0">
<ResultSection
size="large"
title="cupidatat ad consequat"
description="Lorem ipsum dolor sit amet consectetur adipisicing elit."
primaryActionProps={{ children: "Primary Action" }}
secondaryActionProps={{ children: "Secondary Action" }}
/>
</VStack>
);
}
카드나 섹션처럼 화면의 일부 영역에서 상태를 표현할 때 사용합니다.
import { VStack } from "@seed-design/lynx-react";
import { ResultSection } from "@/components/ui/result-section";
export default function Example() {
return (
<VStack width="full" maxWidth="320px" alignSelf="center" grow minHeight="0">
<ResultSection
size="medium"
title="cupidatat ad consequat"
description="Lorem ipsum dolor sit amet consectetur adipisicing elit."
primaryActionProps={{ children: "Primary Action" }}
secondaryActionProps={{ children: "Secondary Action" }}
/>
</VStack>
);
}
주요 액션을 화면 하단에 둘 때는 Result Section 밖에 Action Button을 배치할 수 있습니다.
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { useEffect, useState } from "@lynx-js/react";
import { ActionButton, Box, Icon, VStack } from "@seed-design/lynx-react";
import { AppBar, AppBarMain } from "@/components/ui/app-bar";
import { ProgressCircle } from "@/components/ui/progress-circle";
import { ResultSection } from "@/components/ui/result-section";
export default function Example() {
const [loading, setLoading] = useState(true);
useEffect(() => {
const timer = setTimeout(() => setLoading(false), 3000);
return () => clearTimeout(timer);
}, []);
function handleRetry() {
"background only";
setLoading(true);
setTimeout(() => setLoading(false), 3000);
}
return (
<VStack width="full" maxWidth="360px" alignSelf="center" grow minHeight="0">
<AppBar>
<AppBarMain title="환불 요청" />
</AppBar>
<VStack grow gap="x4" pb="safeArea">
<ResultSection
title={loading ? "환불을 요청하고 있어요" : "다시 시도해주세요"}
description={loading ? "잠시만 기다려주세요" : "환불 요청에 실패했어요"}
asset={
<Box pb="x4">
{loading ? (
<ProgressCircle />
) : (
<Icon icon={<IconExclamationmarkCircleFill />} size="x10" color="fg.critical" />
)}
</Box>
}
/>
<Box px="spacingX.globalGutter" pt="x3" pb="x2">
<ActionButton
flexGrow
size="large"
variant="neutralSolid"
disabled={loading}
loading={loading}
bindtap={handleRetry}
>
다시 시도
</ActionButton>
</Box>
</VStack>
</VStack>
);
}
SEED는 Lynx용 Lottie 컴포넌트를 제공하지 않아 Success Example을 실행 예제로 제공하지 않습니다. 앱에서 사용하는 Lottie 노드를 asset에 전달하고, 확인 액션은 primaryActionProps로 연결합니다.
| 영역 | React Web | Lynx |
|---|
| 네이티브 요소 | div, span, button | view, text |
| 이벤트 | onClick | bindtap, main-thread:bindtap |
| 루트 속성 | HTML 속성 | Lynx 네이티브 요소 속성과 accessibility-* |
| 보조 액션 기본값 | ghost, small, color="fg.neutral", fontWeight="bold", bleedX/bleedY="asPadding" | ghost, small; 기본 bleed 없음 |
| 보조 액션 bleed 값 | 토큰과 "asPadding" 지원 | px 길이, 0, "safeArea" 지원 |
버튼의 접근 가능한 이름과 탭 이벤트는 primaryActionProps와 secondaryActionProps로 전달합니다.