Help Bubble
사용자에게 컴포넌트의 상태나 특정 기능에 대한 추가 정보를 제공하는 말풍선입니다.
import IconILowercaseSerifCircleFill from "@karrotmarket/lynx-monochrome-icon/IconILowercaseSerifCircleFill";
import { ActionButton, Icon } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
return (
<HelpBubbleTrigger defaultOpen title="아래 버튼이나 바깥 영역을 클릭해서 닫아보세요.">
<ActionButton variant="ghost" size="small" layout="iconOnly" accessibility-label="도움말">
<Icon icon={<IconILowercaseSerifCircleFill />} />
</ActionButton>
</HelpBubbleTrigger>
);
}Installation
npx @seed-design/cli@latest add ui:help-bubblepnpm dlx @seed-design/cli@latest add ui:help-bubbleyarn dlx @seed-design/cli@latest add ui:help-bubblebun x @seed-design/cli@latest add ui:help-bubble의존성 설치
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:help-bubble
* @requires @seed-design/lynx-react@^1.0.0
* @requires @seed-design/lynx-css@^1.0.0
* @requires @karrotmarket/lynx-monochrome-icon@>=1.20.0 <2.0.0
**/
import IconXmarkLine from "@karrotmarket/lynx-monochrome-icon/IconXmarkLine";
import * as React from "@lynx-js/react";
import { HelpBubble as SeedHelpBubble, Icon } from "@seed-design/lynx-react";
interface HelpBubbleProps extends Omit<SeedHelpBubble.RootProps, "children"> {
title: React.ReactNode;
description?: React.ReactNode;
showCloseButton?: boolean;
children?: React.ReactNode;
contentProps?: SeedHelpBubble.ContentProps;
zIndexOffset?: number;
container?: SeedHelpBubble.PositionerProps["container"];
overlayLevel?: SeedHelpBubble.PositionerProps["overlayLevel"];
}
export interface HelpBubbleTriggerProps extends HelpBubbleProps {}
/**
* 트리거와 말풍선의 기본 슬롯을 조립합니다. 자식은 native `view`로 감싸므로 `asChild`를
* 지원하지 않습니다.
*
* @see https://seed-design.io/lynx/components/help-bubble
*/
export const HelpBubbleTrigger = React.forwardRef<unknown, HelpBubbleTriggerProps>(
(
{
showCloseButton = false,
title,
description,
contentProps,
zIndexOffset,
container,
overlayLevel,
children,
"accessibility-label": accessibilityLabel,
...rootProps
},
ref,
) => {
return (
<SeedHelpBubble.Root {...rootProps}>
<SeedHelpBubble.Trigger ref={ref} accessibility-label={accessibilityLabel}>
{children}
</SeedHelpBubble.Trigger>
<HelpBubbleContent
title={title}
description={description}
showCloseButton={showCloseButton}
contentProps={contentProps}
zIndexOffset={zIndexOffset}
container={container}
overlayLevel={overlayLevel}
/>
</SeedHelpBubble.Root>
);
},
);
HelpBubbleTrigger.displayName = "HelpBubbleTrigger";
export interface HelpBubbleAnchorProps extends HelpBubbleProps {}
/**
* 위치 기준점과 말풍선의 기본 슬롯을 조립합니다. Anchor는 탭으로 열고 닫지 않습니다.
*
* @see https://seed-design.io/lynx/components/help-bubble
*/
export const HelpBubbleAnchor = React.forwardRef<unknown, HelpBubbleAnchorProps>(
(
{
showCloseButton = false,
title,
description,
contentProps,
zIndexOffset,
container,
overlayLevel,
children,
...rootProps
},
ref,
) => {
return (
<SeedHelpBubble.Root {...rootProps}>
<SeedHelpBubble.Anchor ref={ref}>
<view>{children}</view>
</SeedHelpBubble.Anchor>
<HelpBubbleContent
title={title}
description={description}
showCloseButton={showCloseButton}
contentProps={contentProps}
zIndexOffset={zIndexOffset}
container={container}
overlayLevel={overlayLevel}
/>
</SeedHelpBubble.Root>
);
},
);
HelpBubbleAnchor.displayName = "HelpBubbleAnchor";
interface HelpBubbleContentProps {
title: React.ReactNode;
description?: React.ReactNode;
showCloseButton: boolean;
contentProps?: SeedHelpBubble.ContentProps;
zIndexOffset?: number;
container?: SeedHelpBubble.PositionerProps["container"];
overlayLevel?: SeedHelpBubble.PositionerProps["overlayLevel"];
}
function HelpBubbleContent({
title,
description,
showCloseButton,
contentProps,
zIndexOffset,
container,
overlayLevel,
}: HelpBubbleContentProps) {
return (
<SeedHelpBubble.Positioner
zIndexOffset={zIndexOffset}
container={container}
overlayLevel={overlayLevel}
>
<SeedHelpBubble.Content {...contentProps}>
<SeedHelpBubble.Arrow>
<SeedHelpBubble.ArrowTip />
</SeedHelpBubble.Arrow>
<SeedHelpBubble.Body>
<SeedHelpBubble.Title>{title}</SeedHelpBubble.Title>
{description != null ? (
<SeedHelpBubble.Description>{description}</SeedHelpBubble.Description>
) : null}
</SeedHelpBubble.Body>
{showCloseButton ? (
<SeedHelpBubble.CloseButton accessibility-label="닫기">
<Icon
icon={<IconXmarkLine color="var(--seed-color-fg-on-neutral-solid)" />}
size={14}
color="fg.onNeutralSolid"
/>
</SeedHelpBubble.CloseButton>
) : null}
</SeedHelpBubble.Content>
</SeedHelpBubble.Positioner>
);
}
/**
* 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.
*/
Usage
import IconILowercaseSerifCircleFill from "@karrotmarket/lynx-monochrome-icon/IconILowercaseSerifCircleFill";
import { ActionButton, Icon } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export function App() {
return (
<HelpBubbleTrigger defaultOpen title="추가 정보를 확인하세요.">
<ActionButton variant="ghost" size="small" layout="iconOnly" accessibility-label="도움말">
<Icon icon={<IconILowercaseSerifCircleFill />} />
</ActionButton>
</HelpBubbleTrigger>
);
}HelpBubbleTrigger는 자식을 탭하면 말풍선을 열고 닫습니다.HelpBubbleAnchor는 위치만 정합니다.defaultOpen또는open과onOpenChange로 상태를 관리하세요.- 기본 배치는
"top"입니다.placement,flip,gutter,overflowPadding,arrowPadding으로 위치를 조정할 수 있습니다. showCloseButton으로 닫기 버튼을 추가할 수 있습니다.contentProps.maxWidth의 기본값은280px입니다."none"으로 제한을 없앨 수 있습니다.zIndexOffset은 같은 화면 안에서의 표시 순서를 조정합니다. native overlay 사이의 순서는overlayLevel로 정합니다.
Props
HelpBubbleTrigger
Prop
Type
titleReact.ReactNodedescription?React.ReactNodeshowCloseButton?boolean | undefinedchildren?React.ReactNodecontentProps?SeedHelpBubble.ContentProps | undefinedzIndexOffset?number | undefinedcontainer?string | undefinedoverlayLevel?1 | 2 | 3 | 4 | undefinedopen?boolean | undefineddefaultOpen?boolean | undefinedHelpBubbleAnchor
Prop
Type
titleReact.ReactNodedescription?React.ReactNodeshowCloseButton?boolean | undefinedchildren?React.ReactNodecontentProps?SeedHelpBubble.ContentProps | undefinedzIndexOffset?number | undefinedcontainer?string | undefinedoverlayLevel?1 | 2 | 3 | 4 | undefinedopen?boolean | undefineddefaultOpen?boolean | undefinedExamples
Trigger
HelpBubbleTrigger를 탭하면 말풍선이 열리고 닫힙니다. 이 예제는 처음 열린 uncontrolled Trigger와 open, onOpenChange로 상태를 제어하는 Trigger를 함께 보여줍니다. 두 경우 모두 닫기 버튼으로 닫을 수 있습니다.
import { useState } from "@lynx-js/react";
import { ActionButton, VStack } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
import { Switch } from "@/components/ui/switch";
export default function Example() {
const [isControlledHelpBubbleOpen, setIsControlledHelpBubbleOpen] = useState(true);
function handleControlledOpenChange(nextOpen: boolean) {
"background only";
setIsControlledHelpBubbleOpen(nextOpen);
}
return (
<VStack width="full" gap="x6" align="center" py="80px">
<HelpBubbleTrigger
defaultOpen
title="Trigger, uncontrolled"
description="클릭으로 열고 닫는 동작이 있는 트리거입니다."
placement="top"
showCloseButton
closeOnInteractOutside={false}
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
<VStack gap="spacingY.componentDefault" align="center">
<Switch
size="24"
tone="neutral"
label="열림"
checked={isControlledHelpBubbleOpen}
onCheckedChange={handleControlledOpenChange}
/>
<HelpBubbleTrigger
open={isControlledHelpBubbleOpen}
onOpenChange={handleControlledOpenChange}
title="Trigger, controlled"
description="클릭으로 열고 닫는 동작이 있는 트리거입니다."
placement="bottom"
showCloseButton
closeOnInteractOutside={false}
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
</VStack>
</VStack>
);
}Anchor
HelpBubbleAnchor는 아바타처럼 말풍선의 위치만 정하는 요소이며, 탭으로 열고 닫히지 않습니다. defaultOpen을 쓰는 uncontrolled Anchor와 open, onOpenChange를 쓰는 controlled Anchor의 열림 상태는 각각 닫기 버튼과 열림 Switch로 바꿉니다.
import { useState } from "@lynx-js/react";
import { Box, Text, VStack } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
import { Switch } from "@/components/ui/switch";
const AVATAR_SRC = "https://avatars.githubusercontent.com/u/54893898?v=4";
function Avatar() {
const [hasImageError, setHasImageError] = useState(false);
function handleImageError() {
"background only";
setHasImageError(true);
}
return (
<Box
width="64px"
height="64px"
style={{ display: "flex" }}
alignItems="center"
justifyContent="center"
overflowX="hidden"
overflowY="hidden"
borderRadius="full"
bg="bg.neutralWeak"
>
{hasImageError ? (
<Text textStyle="t2Bold">L</Text>
) : (
<image
src={AVATAR_SRC}
mode="aspectFill"
style={{ width: "64px", height: "64px" }}
binderror={handleImageError}
/>
)}
</Box>
);
}
export default function Example() {
const [isControlledHelpBubbleOpen, setIsControlledHelpBubbleOpen] = useState(true);
function handleControlledOpenChange(nextOpen: boolean) {
"background only";
setIsControlledHelpBubbleOpen(nextOpen);
}
return (
// 위쪽 말풍선이 들어갈 공간을 위와 두 Anchor 사이에 둡니다.
<VStack width="full" gap="160px" pt="144px" align="center">
<HelpBubbleAnchor
defaultOpen
title="Anchor, uncontrolled"
description="클릭으로 열고 닫는 동작 없이 위치만 지정합니다."
placement="top"
showCloseButton
closeOnInteractOutside={false}
>
<Avatar />
</HelpBubbleAnchor>
<VStack gap="spacingY.componentDefault" align="center">
<HelpBubbleAnchor
open={isControlledHelpBubbleOpen}
onOpenChange={handleControlledOpenChange}
title="Anchor, controlled"
description="클릭으로 열고 닫는 동작 없이 위치만 지정합니다."
placement="top"
showCloseButton
closeOnInteractOutside={false}
>
<Avatar />
</HelpBubbleAnchor>
<Switch
size="24"
tone="neutral"
label="열림"
checked={isControlledHelpBubbleOpen}
onCheckedChange={handleControlledOpenChange}
/>
</VStack>
</VStack>
);
}Close On Interact Outside
closeOnInteractOutside의 기본값은 true입니다. true이면 Trigger·Anchor와 말풍선 밖을 탭했을 때 말풍선이 닫히고, 그 탭은 아래 요소에도 그대로 전달됩니다. false이면 바깥을 탭해도 말풍선이 열린 채로 유지됩니다.
import { ActionButton, VStack } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
return (
<VStack width="full" gap="x6" align="center" py="80px">
<HelpBubbleTrigger
defaultOpen
title="This closes on interactions outside"
placement="top"
closeOnInteractOutside
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
<HelpBubbleTrigger
defaultOpen
title="This does not close on interactions outside"
placement="bottom"
closeOnInteractOutside={false}
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
</VStack>
);
}Placement
placement로 기준 요소의 12개 방향에 말풍선을 배치합니다. 이 예제는 각 배치를 열린 상태로 표시합니다. 화면 경계에서 지정한 방향의 공간이 부족하면 flip 기본값에 따라 말풍선이 반대 방향으로 뒤집힙니다.
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { Box, HStack, VStack } from "@seed-design/lynx-react";
import { HelpBubbleAnchor, type HelpBubbleAnchorProps } from "@/components/ui/help-bubble";
function PlacementAnchor({
placement,
}: {
placement: NonNullable<HelpBubbleAnchorProps["placement"]>;
}) {
return (
<Box width="200px" alignItems="center" style={{ display: "flex", flexDirection: "column" }}>
<HelpBubbleAnchor open placement={placement} title={placement} description="est tempor aute">
<IconSparkle2 />
</HelpBubbleAnchor>
</Box>
);
}
export default function Example() {
return (
<scroll-view
scroll-orientation="horizontal"
style={{ display: "flex", flexDirection: "column", width: "100%", flex: 1, minHeight: 0 }}
>
{/* 위·아래 줄 말풍선이 뒤집히지 않도록 위아래 여백을 두고, 같은 높이 안에서 줄 간격을 줄입니다. */}
<VStack width="full" minWidth="920px" gap="60px" px="80px" py="120px">
<HStack justify="center" gap="80px">
<PlacementAnchor placement="top-end" />
<PlacementAnchor placement="top" />
<PlacementAnchor placement="top-start" />
</HStack>
<HStack justify="center" gap="80px">
<PlacementAnchor placement="left-end" />
<Box width="200px" />
<PlacementAnchor placement="right-end" />
</HStack>
<HStack justify="center" gap="80px">
<PlacementAnchor placement="left" />
<Box width="200px" />
<PlacementAnchor placement="right" />
</HStack>
<HStack justify="center" gap="80px">
<PlacementAnchor placement="left-start" />
<Box width="200px" />
<PlacementAnchor placement="right-start" />
</HStack>
<HStack justify="center" gap="80px">
<PlacementAnchor placement="bottom-end" />
<PlacementAnchor placement="bottom" />
<PlacementAnchor placement="bottom-start" />
</HStack>
</VStack>
</scroll-view>
);
}Flip
flip={false}를 지정하면 화면 경계에서 공간이 부족해도 말풍선의 방향을 바꾸지 않습니다.
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
return (
<HelpBubbleAnchor
open
flip={false}
title="Flip"
description="Flip을 끄면 화면 경계에서 방향이 바뀌지 않아요."
>
<IconSparkle2 />
</HelpBubbleAnchor>
);
}Close Button
showCloseButton으로 말풍선에 닫기 버튼을 추가할 수 있습니다. 닫기 버튼을 탭하면 말풍선이 닫히고, Trigger를 다시 탭하면 다시 엽니다.
import { ActionButton } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
return (
<HelpBubbleTrigger
defaultOpen
showCloseButton
title="Close Button"
description="showCloseButton으로 닫기 버튼을 추가할 수 있어요."
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
);
}Description
description을 사용하여 title 아래에 설명을 추가할 수 있습니다.
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
return (
<HelpBubbleAnchor open title="제목" description="제목 아래에 부연 설명을 덧붙일 수 있어요.">
<IconSparkle2 />
</HelpBubbleAnchor>
);
}Title Only
description 없이 title만 전달할 수 있습니다.
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
return (
<HelpBubbleAnchor open title="Title Only">
<IconSparkle2 />
</HelpBubbleAnchor>
);
}Setting Width Manually
Content에는 기본 최대 너비가 있습니다. contentProps.maxWidth로 이 값을 덮어쓰고, "none"으로 최대 너비 제한을 없앨 수 있습니다. contentProps.style.width를 함께 지정했을 때 maxWidth가 더 좁으면 maxWidth가 우선합니다.
import { useState } from "@lynx-js/react";
import { Text, VStack } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control";
const WIDTH_OPTIONS = ["200px", "300px", "unset"] as const;
const MAX_WIDTH_OPTIONS = ["200px", "400px", "none"] as const;
type Width = (typeof WIDTH_OPTIONS)[number];
type MaxWidth = (typeof MAX_WIDTH_OPTIONS)[number];
export default function Example() {
const [width, setWidth] = useState<Width>("unset");
const [maxWidth, setMaxWidth] = useState<MaxWidth>("400px");
function handleWidthChange(nextWidth: string) {
"background only";
setWidth(nextWidth as Width);
}
function handleMaxWidthChange(nextMaxWidth: string) {
"background only";
setMaxWidth(nextMaxWidth as MaxWidth);
}
return (
<HelpBubbleAnchor
open
title="Pariatur aliqua commodo eu Lorem minim anim. Lorem ipsum voluptate eu duis eiusmod consequat."
contentProps={{ maxWidth, style: { width } }}
>
<VStack gap="x4" align="center">
<VStack gap="x1" align="center">
<Text>width</Text>
<SegmentedControl
value={width}
onValueChange={handleWidthChange}
accessibility-label="width"
>
{WIDTH_OPTIONS.map((option) => (
<SegmentedControlItem key={option} value={option}>
{option}
</SegmentedControlItem>
))}
</SegmentedControl>
</VStack>
<VStack gap="x1" align="center">
<Text>maxWidth</Text>
<SegmentedControl
value={maxWidth}
onValueChange={handleMaxWidthChange}
accessibility-label="maxWidth"
>
{MAX_WIDTH_OPTIONS.map((option) => (
<SegmentedControlItem key={option} value={option}>
{option}
</SegmentedControlItem>
))}
</SegmentedControl>
</VStack>
</VStack>
</HelpBubbleAnchor>
);
}Line Breaks
React의 <br />는 Lynx title에서 native <text> 자식과 "\n"으로 변환합니다. 문자열의 줄바꿈 문자도 title에 전달할 수 있습니다.
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HStack } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
const explicitLineBreakTitle = (
<text>
{"Breaking"}
{"\n"}
{"lines"}
{"\n"}
{"using"}
{"\n"}
{"`<br />`s"}
</text>
);
export default function Example() {
return (
<HStack gap="x16" align="center">
<HelpBubbleAnchor open title={explicitLineBreakTitle}>
<IconSparkle2 />
</HelpBubbleAnchor>
<HelpBubbleAnchor open title={"Breaking\nlines\nusing\nnewlines"}>
<IconSparkle2 />
</HelpBubbleAnchor>
</HStack>
);
}z-index Offset
zIndexOffset은 같은 Lynx 화면 안에서의 표시 순서를 조정합니다. container를 지정한 native overlay 사이의 순서는 overlayLevel로 정합니다.
import * as React from "@lynx-js/react";
import { useState } from "@lynx-js/react";
import { Box, HStack, Text, VStack } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control";
type SegmentedControlStyle = NonNullable<React.ComponentProps<typeof SegmentedControl>["style"]> & {
"--segment-min-width": string;
};
// 6개 항목이 좁은 화면에 들어가도록 Item·Indicator 공통 최소 폭을 없앤다.
const COMPACT_SEGMENTED_CONTROL_STYLE: SegmentedControlStyle = {
width: "100%",
"--segment-min-width": "0px",
};
const AVATAR_SRC = "https://avatars.githubusercontent.com/u/54893898?v=4";
const OFFSET_OPTIONS = ["0", "1", "2", "3", "4", "5"] as const;
function Avatar() {
const [hasImageError, setHasImageError] = useState(false);
function handleImageError() {
"background only";
setHasImageError(true);
}
return (
<Box
width="64px"
height="64px"
style={{ display: "flex" }}
alignItems="center"
justifyContent="center"
overflowX="hidden"
overflowY="hidden"
borderRadius="full"
bg="bg.neutralWeak"
>
{hasImageError ? (
<Text textStyle="t2Bold">L</Text>
) : (
<image
src={AVATAR_SRC}
mode="aspectFill"
style={{ width: "64px", height: "64px" }}
binderror={handleImageError}
/>
)}
</Box>
);
}
export default function Example() {
const [zIndexOffset, setZIndexOffset] = useState(5);
function handleOffsetChange(nextOffset: string) {
"background only";
setZIndexOffset(Number(nextOffset));
}
return (
<VStack width="full" gap="x8" align="center">
<HStack width="full" gap="x2" style={{ display: "flex", flexWrap: "wrap" }}>
{Array.from({ length: 5 }, (_, index) => (
<Box
key={index}
width="64px"
height="64px"
borderRadius="r2"
style={{ display: "flex" }}
alignItems="center"
justifyContent="center"
bg="bg.neutralWeak"
borderColor="stroke.neutralWeak"
borderWidth={1}
zIndex={index + 100}
>
<Text>{index + 100}</Text>
</Box>
))}
</HStack>
<HelpBubbleAnchor
defaultOpen
title={`default: 99, current: ${99 + zIndexOffset}`}
description="Et ullamco laborum voluptate ipsum labore ea nostrud sunt ipsum."
zIndexOffset={zIndexOffset}
closeOnInteractOutside={false}
>
<Avatar />
</HelpBubbleAnchor>
<VStack width="full" gap="x1" align="center">
<SegmentedControl
value={String(zIndexOffset)}
onValueChange={handleOffsetChange}
accessibility-label="zIndexOffset"
style={COMPACT_SEGMENTED_CONTROL_STYLE}
>
{OFFSET_OPTIONS.map((option) => (
<SegmentedControlItem key={option} value={option}>
{option}
</SegmentedControlItem>
))}
</SegmentedControl>
<HStack width="full" justify="spaceBetween">
<Text>0</Text>
<Text>5</Text>
</HStack>
</VStack>
</VStack>
);
}Portalled
container를 지정하지 않으면 말풍선은 Lynx view 안의 position: fixed view로 렌더링됩니다. container="window"처럼 native container 이름을 지정하면 native overlay 레이어에 렌더링되어 Lynx view 밖의 host 화면까지 덮습니다. overlayLevel은 container를 지정했을 때 overlay 레이어의 표시 순서를 정합니다.
overlay 레이어는 말풍선이 아닌 곳의 탭을 아래 화면으로 넘깁니다. closeOnInteractOutside가 true이면 같은 탭으로 말풍선도 닫힙니다.
import { useState } from "@lynx-js/react";
import { ActionButton, Text, VStack } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
const [count, setCount] = useState(0);
function handleUnderlyingTap() {
"background only";
setCount((current) => current + 1);
}
return (
<VStack width="full" gap="x16" align="center">
<HelpBubbleTrigger
defaultOpen
container="window"
overlayLevel={2}
title="바깥을 탭하면 닫혀요"
placement="right"
>
<ActionButton variant="neutralSolid">window</ActionButton>
</HelpBubbleTrigger>
<HelpBubbleTrigger
defaultOpen
container="window"
title="바깥을 탭해도 열려 있어요"
placement="right"
closeOnInteractOutside={false}
>
<ActionButton variant="neutralSolid">window</ActionButton>
</HelpBubbleTrigger>
<ActionButton variant="neutralWeak" bindtap={handleUnderlyingTap}>
{`아래 버튼 ${count}`}
</ActionButton>
<Text textStyle="t3Regular" color="fg.neutralMuted">
overlay 레이어 밖의 탭은 아래 화면으로 전달됩니다.
</Text>
</VStack>
);
}웹 버전과의 차이
- Trigger와 Anchor는 자식을 native
view로 감싸며asChild를 제공하지 않습니다. raw<text>처럼 이름을 만들 수 없는 Trigger에는accessibility-label을 지정하세요. closeOnInteractOutside는 바깥 탭을 아래 요소로 전달합니다. 스크롤처럼 탭이 아닌 상호작용으로는 닫히지 않습니다.container를 지정하면 native overlay에 렌더링됩니다. 지정하지 않으면zIndexOffset으로 같은 화면 안의 표시 순서를 조정합니다.- Help Bubble Tooltip은 hover와 키보드 focus를 사용하는 웹 전용 컴포넌트입니다.
Last updated on