Page Banner
페이지 상단에서 전체 상태나 중요한 메시지를 전달하는 상위 레벨 메시지 컴포넌트입니다.
import "./styles";
import { root, useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
} from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [open, setOpen] = useState(true);
function handleTap() {
"background only";
}
function handleDismiss() {
"background only";
setOpen(false);
}
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner description="새로운 소식을 확인해 보세요." />
<ActionablePageBanner
title="알림"
description="전체 배너를 탭할 수 있어요."
bindtap={handleTap}
accessibility-label="새로운 소식 확인"
/>
<DismissiblePageBanner
open={open}
description="닫을 수 있는 안내 메시지예요."
onDismiss={handleDismiss}
/>
</view>
</page>
);
}
root.render(<Root />);Installation
npx @seed-design/cli@latest add ui:page-bannerpnpm dlx @seed-design/cli@latest add ui:page-banneryarn dlx @seed-design/cli@latest add ui:page-bannerbun x @seed-design/cli@latest add ui:page-banner의존성 설치
npm install @karrotmarket/lynx-monochrome-icon @seed-design/lynx-reactyarn add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-reactpnpm add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-reactbun add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:page-banner
* @requires @seed-design/lynx-react@>=0.6.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.10.0 <1.0.0
**/
import IconChevronRightLine from "@karrotmarket/lynx-monochrome-icon/IconChevronRightLine";
import IconXmarkLine from "@karrotmarket/lynx-monochrome-icon/IconXmarkLine";
import * as React from "@lynx-js/react";
import {
PageBanner as SeedPageBanner,
PrefixIcon,
SuffixIcon,
type PrefixIconProps,
} from "@seed-design/lynx-react";
export interface PageBannerProps
extends Omit<SeedPageBanner.RootProps, "children" | "open" | "defaultOpen" | "onDismiss"> {
prefixIcon?: PrefixIconProps["icon"];
title?: React.ReactNode;
description: React.ReactNode;
suffix?: React.ReactNode;
}
/**
* @see https://seed-design.io/lynx/components/page-banner
*/
export const PageBanner = React.forwardRef<unknown, PageBannerProps>(
({ prefixIcon, title, description, suffix, ...otherProps }, ref) => {
return (
<SeedPageBanner.Root ref={ref} {...otherProps}>
{prefixIcon ? <PrefixIcon icon={prefixIcon} /> : null}
<SeedPageBanner.Content>
<SeedPageBanner.Body>
{title ? <SeedPageBanner.Title>{title}</SeedPageBanner.Title> : null}
<SeedPageBanner.Description>{description}</SeedPageBanner.Description>
</SeedPageBanner.Body>
{suffix}
</SeedPageBanner.Content>
</SeedPageBanner.Root>
);
},
);
PageBanner.displayName = "PageBanner";
export interface PageBannerButtonProps extends SeedPageBanner.ButtonProps {}
export const PageBannerButton = SeedPageBanner.Button;
export interface ActionablePageBannerProps
extends Omit<SeedPageBanner.RootProps, "children" | "open" | "defaultOpen" | "onDismiss"> {
prefixIcon?: PrefixIconProps["icon"];
title?: React.ReactNode;
description: React.ReactNode;
}
/**
* @see https://seed-design.io/lynx/components/page-banner
*/
export const ActionablePageBanner = React.forwardRef<unknown, ActionablePageBannerProps>(
({ prefixIcon, title, description, ...otherProps }, ref) => {
return (
<SeedPageBanner.Root ref={ref} {...otherProps}>
{prefixIcon ? <PrefixIcon icon={prefixIcon} /> : null}
<SeedPageBanner.Content>
<SeedPageBanner.Body>
{title ? <SeedPageBanner.Title>{title}</SeedPageBanner.Title> : null}
<SeedPageBanner.Description>{description}</SeedPageBanner.Description>
</SeedPageBanner.Body>
</SeedPageBanner.Content>
<SuffixIcon icon={<IconChevronRightLine />} />
</SeedPageBanner.Root>
);
},
);
ActionablePageBanner.displayName = "ActionablePageBanner";
export interface DismissiblePageBannerProps extends Omit<SeedPageBanner.RootProps, "children"> {
prefixIcon?: PrefixIconProps["icon"];
title?: React.ReactNode;
description: React.ReactNode;
dismissLabel?: string;
}
/**
* @see https://seed-design.io/lynx/components/page-banner
*/
export const DismissiblePageBanner = React.forwardRef<unknown, DismissiblePageBannerProps>(
({ prefixIcon, title, description, dismissLabel = "닫기", ...otherProps }, ref) => {
return (
<SeedPageBanner.Root ref={ref} {...otherProps}>
{prefixIcon ? <PrefixIcon icon={prefixIcon} /> : null}
<SeedPageBanner.Content>
<SeedPageBanner.Body>
{title ? <SeedPageBanner.Title>{title}</SeedPageBanner.Title> : null}
<SeedPageBanner.Description>{description}</SeedPageBanner.Description>
</SeedPageBanner.Body>
</SeedPageBanner.Content>
<SeedPageBanner.CloseButton accessibility-label={dismissLabel}>
<SuffixIcon icon={<IconXmarkLine />} />
</SeedPageBanner.CloseButton>
</SeedPageBanner.Root>
);
},
);
DismissiblePageBanner.displayName = "DismissiblePageBanner";
/**
* 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.
*/
Props
PageBanner
Prop
Type
prefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedtitle?React.ReactNodedescriptionReact.ReactNodesuffix?React.ReactNodebindtap?EventHandler<BaseTouchEvent<Target>> | undefinedmain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedstyle?CSSProperties | undefinedclassName?string | undefinedPageBannerButton
Prop
Type
style?CSSProperties | undefinedchildren?React.ReactNodeclassName?string | undefinedbindtap?EventHandler<BaseTouchEvent<Target>> | undefinedmain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedActionablePageBanner
Prop
Type
prefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedtitle?React.ReactNodedescriptionReact.ReactNodebindtap?EventHandler<BaseTouchEvent<Target>> | undefinedmain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedstyle?CSSProperties | undefinedclassName?string | undefinedDismissiblePageBanner
Prop
Type
prefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedtitle?React.ReactNodedescriptionReact.ReactNodedismissLabel?string | undefinedopen?boolean | undefineddefaultOpen?boolean | undefinedonDismiss?(() => void) | undefinedbindtap?EventHandler<BaseTouchEvent<Target>> | undefinedmain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedstyle?CSSProperties | undefinedclassName?string | undefinedUsage
설치한 snippet은 기본 배너와 전체 영역을 탭할 수 있는 배너, 닫을 수 있는 배너를 제공합니다.
import { PageBanner } from "@/components/ui/page-banner";
export function App() {
return <PageBanner title="알림" description="새로운 소식을 확인해 보세요." />;
}저수준 조합이 필요하면 @seed-design/lynx-react의 compound API를 직접 사용할 수 있습니다.
import { PageBanner } from "@seed-design/lynx-react";
export function App() {
return (
<PageBanner.Root tone="informative">
<PageBanner.Content>
<PageBanner.Body>
<PageBanner.Title>알림</PageBanner.Title>
<PageBanner.Description>새로운 소식을 확인해 보세요.</PageBanner.Description>
</PageBanner.Body>
</PageBanner.Content>
</PageBanner.Root>
);
}Examples
With Button
PageBanner의 suffix에 PageBannerButton을 전달하면 메시지 옆에 버튼을 배치할 수 있습니다.
import "./styles";
import { root } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner
description="사업자 정보를 등록해 주세요."
suffix={<PageBannerButton bindtap={handleTap}>자세히 보기</PageBannerButton>}
/>
</view>
</page>
);
}
root.render(<Root />);Tones and Variants
tone은 neutral, informative, positive, warning, critical, magic을 지원합니다. variant는 weak과 solid를 지원합니다. 기본 조합은 neutral과 weak입니다.
Neutral
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { root } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner
tone="neutral"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해 주세요."
suffix={<PageBannerButton>등록하기</PageBannerButton>}
/>
<PageBanner
tone="neutral"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해 주세요."
suffix={<PageBannerButton>등록하기</PageBannerButton>}
/>
</view>
</page>
);
}
root.render(<Root />);Informative
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { root } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner
tone="informative"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="안내"
description="새로운 정보를 확인해 주세요."
suffix={<PageBannerButton>확인하기</PageBannerButton>}
/>
<PageBanner
tone="informative"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="안내"
description="새로운 정보를 확인해 주세요."
suffix={<PageBannerButton>확인하기</PageBannerButton>}
/>
</view>
</page>
);
}
root.render(<Root />);Positive
import "./styles";
import IconCheckmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconCheckmarkCircleFill";
import { root } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner
tone="positive"
variant="weak"
prefixIcon={<IconCheckmarkCircleFill />}
title="완료"
description="설정이 저장되었어요."
suffix={<PageBannerButton>확인</PageBannerButton>}
/>
<PageBanner
tone="positive"
variant="solid"
prefixIcon={<IconCheckmarkCircleFill />}
title="완료"
description="설정이 저장되었어요."
suffix={<PageBannerButton>확인</PageBannerButton>}
/>
</view>
</page>
);
}
root.render(<Root />);Warning
import "./styles";
import IconExclamationmarkTriangleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkTriangleFill";
import { root } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner
tone="warning"
variant="weak"
prefixIcon={<IconExclamationmarkTriangleFill />}
title="주의"
description="입력한 내용을 다시 확인해 주세요."
suffix={<PageBannerButton>확인</PageBannerButton>}
/>
<PageBanner
tone="warning"
variant="solid"
prefixIcon={<IconExclamationmarkTriangleFill />}
title="주의"
description="입력한 내용을 다시 확인해 주세요."
suffix={<PageBannerButton>확인</PageBannerButton>}
/>
</view>
</page>
);
}
root.render(<Root />);Critical
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { root } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner
tone="critical"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="오류"
description="요청을 처리하지 못했어요."
suffix={<PageBannerButton>다시 시도</PageBannerButton>}
/>
<PageBanner
tone="critical"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="오류"
description="요청을 처리하지 못했어요."
suffix={<PageBannerButton>다시 시도</PageBannerButton>}
/>
</view>
</page>
);
}
root.render(<Root />);Magic
tone="magic"은 variant="weak"과만 함께 사용할 수 있습니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { root } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<view className="page-banner-preview">
<PageBanner
tone="magic"
variant="weak"
prefixIcon={<IconSparkle2 />}
title="새로운 기능"
description="마법 같은 소식이 도착했어요!"
suffix={<PageBannerButton>둘러보기</PageBannerButton>}
/>
</view>
</page>
);
}
root.render(<Root />);Action and Dismiss
전체 배너를 액션으로 사용하려면 ActionablePageBanner에 bindtap과 accessibility-label을 전달하세요. 닫기 상태를 직접 관리할 때는 DismissiblePageBanner의 open과 onDismiss를 사용합니다.
Web Version Differences
| 영역 | React Web | Lynx |
|---|---|---|
| Native element | div, span, button | view, text |
| Event | onClick | bindtap, main-thread:bindtap |
| Accessibility | HTML semantics, ARIA | accessibility-* |
| Polymorphism | asChild 지원 | 미지원 |
| Interaction feedback | focus ring, scale feedback | pressed 배경색, Button·CloseButton Scale Feedback |
Lynx의 actionable Root와 CloseButton은 터치되는 동안 pressed Recipe 상태를 적용합니다. PageBannerButton과 CloseButton은 Scale Feedback도 적용하며, child의 기존 Main Thread 이벤트와 ref를 보존합니다. PageBannerButton, actionable Root, CloseButton은 기본적으로 button trait를 사용합니다.
Unsupported Lynx Features
asChild: Lynx에는 DOM Slot 기반 다형 렌더링이 없습니다.PageBannerButton은 Lynxtext요소로 렌더링됩니다.- 웹 focus ring: Lynx는 웹 키보드 포커스 모델을 사용하지 않습니다.
- HTML attribute와 ARIA prop: 대응하는 Lynx native prop과
accessibility-*를 사용하세요.
Last updated on