Lynx
Page Banner
페이지 상단에서 전체 상태나 중요한 메시지를 전달하는 상위 레벨 메시지 컴포넌트입니다.
사용 가능 버전@seed-design/lynx-react@0.6.0, @seed-design/lynx-css@0.10.0
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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을 전달하면 메시지 옆에 버튼을 배치할 수 있습니다.
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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"과만 함께 사용할 수 있습니다.
Lynx 예제를 불러오는 중입니다.
URL을 준비하는 중입니다.
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 배경색 |
Lynx의 actionable Root는 터치되는 동안 pressed Recipe 상태를 적용합니다. PageBannerButton, actionable Root, CloseButton은 기본적으로 button trait를 사용합니다.
Unsupported Lynx Features
asChild: Lynx에는 DOM Slot 기반 다형 렌더링이 없습니다.PageBannerButton은 Lynxtext요소로 렌더링됩니다.- 웹 focus ring과 동적 scale feedback: Lynx는 웹 키보드 포커스 모델을 사용하지 않습니다.
- HTML attribute와 ARIA prop: 대응하는 Lynx native prop과
accessibility-*를 사용하세요.
Last updated on