Page Banner
페이지 상단에서 전체 상태나 중요한 메시지를 전달하는 상위 레벨 메시지 컴포넌트입니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
} from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [open, setOpen] = useState(true);
function handleTap() {
"background only";
}
function handleDismiss() {
"background only";
setOpen(false);
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<view className="page-banner-preview">
<PageBanner description="새로운 소식을 확인해 보세요." />
<ActionablePageBanner
title="알림"
description="전체 배너를 탭할 수 있어요."
bindtap={handleTap}
accessibility-label="새로운 소식 확인"
/>
<DismissiblePageBanner
open={open}
description="닫을 수 있는 안내 메시지예요."
onDismiss={handleDismiss}
/>
</view>
</view>
);
}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 { useSeedClassName } from "@seed-design/lynx-react";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<view className="page-banner-preview">
<PageBanner
description="사업자 정보를 등록해 주세요."
suffix={<PageBannerButton bindtap={handleTap}>자세히 보기</PageBannerButton>}
/>
</view>
</view>
);
}Tones and Variants
tone은 neutral, positive, informative, warning, critical, magic을 지원합니다. variant는 weak과 solid를 지원합니다. 기본 조합은 neutral과 weak입니다.
Neutral
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<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>}
/>
<ActionablePageBanner
tone="neutral"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<ActionablePageBanner
tone="neutral"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="neutral"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="neutral"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
</view>
</view>
);
}Positive
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<view className="page-banner-preview">
<PageBanner
tone="positive"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
suffix={<PageBannerButton>등록하기</PageBannerButton>}
/>
<PageBanner
tone="positive"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
suffix={<PageBannerButton>등록하기</PageBannerButton>}
/>
<ActionablePageBanner
tone="positive"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<ActionablePageBanner
tone="positive"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="positive"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="positive"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
</view>
</view>
);
}Informative
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<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>}
/>
<ActionablePageBanner
tone="informative"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<ActionablePageBanner
tone="informative"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="informative"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="informative"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
</view>
</view>
);
}Warning
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<view className="page-banner-preview">
<PageBanner
tone="warning"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
suffix={<PageBannerButton>등록하기</PageBannerButton>}
/>
<PageBanner
tone="warning"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
suffix={<PageBannerButton>등록하기</PageBannerButton>}
/>
<ActionablePageBanner
tone="warning"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<ActionablePageBanner
tone="warning"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="warning"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="warning"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
</view>
</view>
);
}Critical
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<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>}
/>
<ActionablePageBanner
tone="critical"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<ActionablePageBanner
tone="critical"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
bindtap={handleTap}
accessibility-label="미노출, 사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="critical"
variant="weak"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
<DismissiblePageBanner
tone="critical"
variant="solid"
prefixIcon={<IconExclamationmarkCircleFill />}
title="미노출"
description="사업자 정보를 등록해주세요."
/>
</view>
</view>
);
}Magic
tone="magic"은 variant="weak"과만 함께 사용할 수 있습니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { useSeedClassName } from "@seed-design/lynx-react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
function handleTap() {
"background only";
}
return (
<view className={`${seedClassName} docs-lynx-page-banner-root`}>
<view className="page-banner-preview">
<PageBanner
tone="magic"
variant="weak"
prefixIcon={<IconSparkle2 />}
title="새로운 기능"
description="마법 같은 소식이 도착했어요!"
suffix={<PageBannerButton>둘러보기</PageBannerButton>}
/>
<ActionablePageBanner
tone="magic"
variant="weak"
prefixIcon={<IconSparkle2 />}
title="새로운 기능"
description="마법 같은 소식이 도착했어요!"
bindtap={handleTap}
accessibility-label="새로운 기능, 마법 같은 소식이 도착했어요!"
/>
<DismissiblePageBanner
tone="magic"
variant="weak"
prefixIcon={<IconSparkle2 />}
title="새로운 기능"
description="마법 같은 소식이 도착했어요!"
/>
</view>
</view>
);
}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 배경색, 본문 Content Scale, Button·CloseButton Root Scale |
Actionable Root는 배경을 유지하고 내부 콘텐츠만 축소하는 Content Scale을 적용합니다. PageBannerButton과 CloseButton은 각각 자신의 영역을 축소하는 Root Scale을 적용합니다. 개별 버튼을 누르면 본문 액션이 함께 실행되거나 중복 축소되지 않습니다. PageBannerButton, actionable Root, CloseButton은 기본적으로 button trait를 사용합니다.
CloseButton은 Root의 직접 자식 또는 Fragment 안에 배치해야 본문 축소 영역에서 분리됩니다. 사용자 정의 컴포넌트로 감싸면 본문 축소 영역에 포함됩니다.
Unsupported Lynx Features
asChild: Lynx에는 DOM Slot 기반 다형 렌더링이 없습니다.PageBannerButton은 Lynxtext요소로 렌더링됩니다.- 웹 focus ring: Lynx는 웹 키보드 포커스 모델을 사용하지 않습니다.
- HTML attribute와 ARIA prop: 대응하는 Lynx native prop과
accessibility-*를 사용하세요.
Last updated on