Lynx
Page Banner
페이지 상단에서 전체 상태나 중요한 메시지를 전달하는 상위 레벨 메시지 컴포넌트입니다.
사용 가능 버전@seed-design/[email protected], @seed-design/[email protected]
Lynx 예제를 불러오는 중입니다.
import "./styles";
import { useState } from "@lynx-js/react";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
} from "@/components/ui/page-banner";
export default function Example() {
const [open, setOpen] = useState(true);
function handleTap() {
"background only";
}
function handleDismiss() {
"background only";
setOpen(false);
}
return (
<view className="page-banner-preview">
<PageBanner description="새로운 소식을 확인해 보세요." />
<ActionablePageBanner
title="알림"
description="전체 배너를 탭할 수 있어요."
bindtap={handleTap}
accessibility-label="새로운 소식 확인"
/>
<DismissiblePageBanner
open={open}
description="닫을 수 있는 안내 메시지예요."
onDismiss={handleDismiss}
/>
</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@^1.0.0
* @requires @seed-design/lynx-css@^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;
}
// Lynx `PageBanner.Root`는 tap handler가 있어야 눌림 상태·Content Scale·button 접근성을 연결합니다.
// React의 `<button>`처럼 handler 없이도 탭할 수 있는 PageBanner로 표시합니다.
const handleEmptyTap: NonNullable<SeedPageBanner.RootProps["bindtap"]> = () => {};
/**
* 전체 영역을 탭할 수 있는 PageBanner입니다. `bindtap`이 없어도 눌림 상태와 Content Scale을 표시합니다.
*
* @see https://seed-design.io/lynx/components/page-banner
*/
export const ActionablePageBanner = React.forwardRef<unknown, ActionablePageBannerProps>(
({ prefixIcon, title, description, bindtap = handleEmptyTap, ...otherProps }, ref) => {
return (
<SeedPageBanner.Root ref={ref} bindtap={bindtap} {...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.ReactNodemain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedPageBannerButton
고유 prop이 없습니다. className, style, children, bindtap, main-thread:bindtap을 받습니다.
ActionablePageBanner
Prop
Type
prefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedtitle?React.ReactNodedescriptionReact.ReactNodemain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedDismissiblePageBanner
Prop
Type
prefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedtitle?React.ReactNodedescriptionReact.ReactNodedismissLabel?string | undefinedmain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedUsage
import { PageBanner } from "@/components/ui/page-banner";
export function App() {
return <PageBanner title="알림" description="새로운 소식을 확인해 보세요." />;
}Examples
With Button
PageBanner의 suffix에 PageBannerButton을 전달하면 메시지 옆에 버튼을 배치할 수 있습니다.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import { PageBanner, PageBannerButton } from "@/components/ui/page-banner";
export default function Example() {
function handleTap() {
"background only";
}
return (
<view className="page-banner-preview">
<PageBanner
description="사업자 정보를 등록해 주세요."
suffix={<PageBannerButton bindtap={handleTap}>자세히 보기</PageBannerButton>}
/>
</view>
);
}Tones and Variants
tone은 neutral, positive, informative, warning, critical, magic을 지원합니다. variant는 weak과 solid를 지원합니다. 기본 조합은 neutral과 weak입니다.
Neutral
Lynx 예제를 불러오는 중입니다.
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
function handleTap() {
"background only";
}
return (
<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>
);
}Positive
Lynx 예제를 불러오는 중입니다.
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
function handleTap() {
"background only";
}
return (
<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>
);
}Informative
Lynx 예제를 불러오는 중입니다.
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
function handleTap() {
"background only";
}
return (
<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>
);
}Warning
Lynx 예제를 불러오는 중입니다.
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
function handleTap() {
"background only";
}
return (
<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>
);
}Critical
Lynx 예제를 불러오는 중입니다.
import "./styles";
import IconExclamationmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconExclamationmarkCircleFill";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
function handleTap() {
"background only";
}
return (
<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>
);
}Magic
tone="magic"은 variant="weak"과만 함께 사용할 수 있습니다.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import {
ActionablePageBanner,
DismissiblePageBanner,
PageBanner,
PageBannerButton,
} from "@/components/ui/page-banner";
export default function Example() {
function handleTap() {
"background only";
}
return (
<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>
);
}Action and Dismiss
전체 배너를 액션으로 사용하려면 ActionablePageBanner에 bindtap과 accessibility-label을 전달하세요. 닫기 상태를 직접 관리하려면 DismissiblePageBanner의 open과 onDismiss를 사용합니다.
Web Version Differences
- 탭 이벤트는
onClick대신bindtap또는main-thread:bindtap으로 받습니다. - 접근성은 HTML 의미 요소와 ARIA 대신 Lynx의
accessibility-*속성을 사용합니다. - CloseButton은 Root의 직접 자식이나 Fragment 안에 두세요. 다른 컴포넌트로 감싸면 배너 본문과 함께 눌림 피드백이 적용됩니다.
Unsupported Lynx Features
asChild를 지원하지 않습니다.- 웹의 focus ring과 HTML 속성은 지원하지 않습니다. 대응하는 Lynx native 속성을 사용하세요.
Last updated on