Help Bubble
사용자에게 컴포넌트의 상태나 특정 기능에 대한 추가 정보를 제공하는 말풍선입니다.
import "./styles";
import IconILowercaseSerifCircleFill from "@karrotmarket/lynx-monochrome-icon/IconILowercaseSerifCircleFill";
import { ActionButton, Icon, VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" align="center" justify="center">
<HelpBubbleTrigger defaultOpen title="아래 버튼이나 바깥 영역을 클릭해서 닫아보세요.">
<ActionButton variant="ghost" size="small" layout="iconOnly" accessibility-label="도움말">
<Icon icon={<IconILowercaseSerifCircleFill />} />
</ActionButton>
</HelpBubbleTrigger>
</VStack>
</view>
);
}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@>=0.8.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.12.0 <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;
}
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,
children,
...rootProps
},
ref,
) => {
return (
<SeedHelpBubble.Root {...rootProps}>
<SeedHelpBubble.Trigger ref={ref}>
<view>{children}</view>
</SeedHelpBubble.Trigger>
<HelpBubbleContent
title={title}
description={description}
showCloseButton={showCloseButton}
contentProps={contentProps}
zIndexOffset={zIndexOffset}
/>
</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,
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}
/>
</SeedHelpBubble.Root>
);
},
);
HelpBubbleAnchor.displayName = "HelpBubbleAnchor";
interface HelpBubbleContentProps {
title: React.ReactNode;
description?: React.ReactNode;
showCloseButton: boolean;
contentProps?: SeedHelpBubble.ContentProps;
zIndexOffset?: number;
}
function HelpBubbleContent({
title,
description,
showCloseButton,
contentProps,
zIndexOffset,
}: HelpBubbleContentProps) {
return (
<SeedHelpBubble.Positioner zIndexOffset={zIndexOffset}>
<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-neutral-inverted)" />}
size={14}
color="fg.neutralInverted"
/>
</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
설치한 스니펫은 HelpBubbleTrigger와 HelpBubbleAnchor로 위치 기준점, 말풍선 콘텐츠, 화살표, 선택적인 닫기 버튼을 함께 조립합니다.
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"으로 최대 너비 제한을 없앨 수 있습니다.contentProps.style.width를 함께 지정했을 때는maxWidth가 더 좁으면maxWidth가 적용됩니다.zIndexOffset은 Positioner의 기본 z-index99에 더합니다.
Props
HelpBubbleTrigger
Prop
Type
titleReact.ReactNodedescription?React.ReactNodeshowCloseButton?boolean | undefinedchildren?React.ReactNodecontentProps?SeedHelpBubble.ContentProps | undefinedzIndexOffset?number | undefinedopen?boolean | undefineddefaultOpen?boolean | undefinedonOpenChange?((open: boolean) => void) | undefinedstyle?CSSProperties | undefinedclassName?string | undefinedHelpBubbleAnchor
Prop
Type
titleReact.ReactNodedescription?React.ReactNodeshowCloseButton?boolean | undefinedchildren?React.ReactNodecontentProps?SeedHelpBubble.ContentProps | undefinedzIndexOffset?number | undefinedopen?boolean | undefineddefaultOpen?boolean | undefinedonOpenChange?((open: boolean) => void) | undefinedstyle?CSSProperties | undefinedclassName?string | undefinedExamples
Trigger
HelpBubbleTrigger를 탭하면 말풍선이 열리고 닫힙니다. 이 예제는 처음 열린 uncontrolled Trigger와 open, onOpenChange로 상태를 제어하는 Trigger를 함께 보여줍니다. 두 경우 모두 닫기 버튼으로 닫을 수 있습니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { ActionButton, VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
import { Switch } from "@/components/ui/switch";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [isControlledHelpBubbleOpen, setIsControlledHelpBubbleOpen] = useState(true);
function handleControlledOpenChange(nextOpen: boolean) {
"background only";
setIsControlledHelpBubbleOpen(nextOpen);
}
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" gap="x16" align="center" justify="center">
<HelpBubbleTrigger
defaultOpen
title="Trigger, uncontrolled"
description="클릭으로 열고 닫는 동작이 있는 트리거입니다."
placement="right"
showCloseButton
closeOnInteractOutside={false}
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
<VStack gap="spacingY.componentDefault" align="center">
<HelpBubbleTrigger
open={isControlledHelpBubbleOpen}
onOpenChange={handleControlledOpenChange}
title="Trigger, controlled"
description="클릭으로 열고 닫는 동작이 있는 트리거입니다."
placement="right"
showCloseButton
closeOnInteractOutside={false}
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
<Switch
size="24"
tone="neutral"
label="열림"
checked={isControlledHelpBubbleOpen}
onCheckedChange={handleControlledOpenChange}
/>
</VStack>
</VStack>
</view>
);
}Anchor
HelpBubbleAnchor는 아바타처럼 말풍선의 위치만 정하는 요소이며, 탭으로 열고 닫히지 않습니다. defaultOpen을 쓰는 uncontrolled Anchor와 open, onOpenChange를 쓰는 controlled Anchor의 열림 상태는 각각 닫기 버튼과 열림 Switch로 바꿉니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { Box, Text, VStack, useSeedClassName } 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"
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 seedClassName = useSeedClassName({ colorMode: "system" });
const [isControlledHelpBubbleOpen, setIsControlledHelpBubbleOpen] = useState(true);
function handleControlledOpenChange(nextOpen: boolean) {
"background only";
setIsControlledHelpBubbleOpen(nextOpen);
}
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" gap="x16" align="center" justify="center">
<HelpBubbleAnchor
defaultOpen
title="Anchor, uncontrolled"
description="클릭으로 열고 닫는 동작 없이 위치만 지정합니다."
placement="right"
showCloseButton
closeOnInteractOutside={false}
>
<Avatar />
</HelpBubbleAnchor>
<VStack gap="spacingY.componentDefault" align="center">
<HelpBubbleAnchor
open={isControlledHelpBubbleOpen}
onOpenChange={handleControlledOpenChange}
title="Anchor, controlled"
description="클릭으로 열고 닫는 동작 없이 위치만 지정합니다."
placement="right"
showCloseButton
closeOnInteractOutside={false}
>
<Avatar />
</HelpBubbleAnchor>
<Switch
size="24"
tone="neutral"
label="열림"
checked={isControlledHelpBubbleOpen}
onCheckedChange={handleControlledOpenChange}
/>
</VStack>
</VStack>
</view>
);
}Close On Interact Outside
closeOnInteractOutside의 기본값은 true입니다. native에서 true인 말풍선의 첫 바깥 탭은 말풍선만 닫고 아래 요소에는 전달되지 않습니다. false이면 말풍선은 열린 채로 유지되고, 바깥 탭은 아래 요소에 그대로 전달됩니다.
import "./styles";
import { ActionButton, VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" gap="x16" align="center" justify="center">
<HelpBubbleTrigger
defaultOpen
title="This closes on interactions outside"
placement="right"
closeOnInteractOutside
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
<HelpBubbleTrigger
defaultOpen
title="This does not close on interactions outside"
placement="right"
closeOnInteractOutside={false}
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
</VStack>
</view>
);
}Placement
placement로 기준 요소의 12개 방향에 말풍선을 배치합니다. 이 예제는 각 배치를 열린 상태로 표시하며, flip={false}로 지정한 방향을 유지합니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { Box, HStack, VStack, useSeedClassName } 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">
<HelpBubbleAnchor
open
flip={false}
placement={placement}
title={placement}
description="est tempor aute"
>
<IconSparkle2 />
</HelpBubbleAnchor>
</Box>
);
}
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<scroll-view scroll-orientation="horizontal" style={{ width: "100%", height: "600px" }}>
<VStack width="full" minWidth="920px" gap="80px" px="80px" py="80px">
<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>
</view>
);
}Flip
flip={false}를 지정하면 화면 경계에서 공간이 부족해도 말풍선의 방향을 바꾸지 않습니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" align="center" justify="center">
<HelpBubbleAnchor
open
flip={false}
title="Flip"
description="Flip을 끄면 화면 경계에서 방향이 바뀌지 않아요."
>
<IconSparkle2 />
</HelpBubbleAnchor>
</VStack>
</view>
);
}Close Button
showCloseButton으로 말풍선에 닫기 버튼을 추가할 수 있습니다. 닫기 버튼을 탭하면 말풍선이 닫히고, Trigger를 다시 탭하면 다시 엽니다.
import "./styles";
import { ActionButton, VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" align="center" justify="center">
<HelpBubbleTrigger
defaultOpen
showCloseButton
title="Close Button"
description="showCloseButton으로 닫기 버튼을 추가할 수 있어요."
>
<ActionButton variant="neutralSolid">토글</ActionButton>
</HelpBubbleTrigger>
</VStack>
</view>
);
}Description
description을 사용하여 title 아래에 설명을 추가할 수 있습니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" align="center" justify="center">
<HelpBubbleAnchor open title="제목" description="제목 아래에 부연 설명을 덧붙일 수 있어요.">
<IconSparkle2 />
</HelpBubbleAnchor>
</VStack>
</view>
);
}Title Only
description 없이 title만 전달할 수 있습니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="320px" align="center" justify="center">
<HelpBubbleAnchor open title="Title Only">
<IconSparkle2 />
</HelpBubbleAnchor>
</VStack>
</view>
);
}Setting Width Manually
Content에는 기본 최대 너비가 있습니다. contentProps.maxWidth로 이 값을 덮어쓰고, "none"으로 최대 너비 제한을 없앨 수 있습니다. contentProps.style.width를 함께 지정했을 때 maxWidth가 더 좁으면 maxWidth가 우선합니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { Text, VStack, useSeedClassName } 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 seedClassName = useSeedClassName({ colorMode: "system" });
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 (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="400px" p="x10" align="center" justify="center">
<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>
</VStack>
</view>
);
}Line Breaks
React의 <br />는 Lynx title에서 native <text> 자식과 "\n"으로 변환합니다. 문자열의 줄바꿈 문자도 title에 전달할 수 있습니다.
import "./styles";
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HStack, useSeedClassName } 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() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<HStack width="full" height="320px" gap="x16" align="center" justify="center">
<HelpBubbleAnchor open title={explicitLineBreakTitle}>
<IconSparkle2 />
</HelpBubbleAnchor>
<HelpBubbleAnchor open title={"Breaking\nlines\nusing\nnewlines"}>
<IconSparkle2 />
</HelpBubbleAnchor>
</HStack>
</view>
);
}z-index Offset
zIndexOffset으로 Positioner의 기본 z-index 99에 값을 더합니다. 이 예제는 SegmentedControl로 offset을 바꾸며 말풍선의 현재 z-index를 확인합니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { Box, HStack, Text, VStack, useSeedClassName } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control";
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"
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 seedClassName = useSeedClassName({ colorMode: "system" });
const [zIndexOffset, setZIndexOffset] = useState(5);
function handleOffsetChange(nextOffset: string) {
"background only";
setZIndexOffset(Number(nextOffset));
}
return (
<view className={`${seedClassName} docs-lynx-help-bubble-root`}>
<VStack width="full" height="480px" p="x5" gap="x8" align="center" justify="center">
<HStack gap="x2">
{Array.from({ length: 5 }, (_, index) => (
<Box
key={index}
width="64px"
height="64px"
borderRadius="r2"
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 gap="x1" align="center">
<SegmentedControl
value={String(zIndexOffset)}
onValueChange={handleOffsetChange}
accessibility-label="zIndexOffset"
>
{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>
</view>
);
}웹 버전과의 차이
- Lynx의 Trigger와 Anchor는 자식을 native
view로 감쌉니다. DOMasChild, HTML ARIA 속성, 키보드 포커스·ESC 닫힘, portal은 제공하지 않습니다. 필요한 접근성은 Lynx의accessibility-*prop과 host의 native 접근성 흐름으로 확인하세요. closeOnInteractOutside가true이면 첫 번째 바깥 탭은 말풍선만 닫고 아래 요소로 전달되지 않습니다.false이면 말풍선은 열린 채로 바깥 탭이 아래 요소로 전달됩니다.- Positioner는 portal이나 fullscreen overlay가 아닌 고정 native
view입니다.zIndexOffset으로 같은 화면의 형제 요소와의 z-index 순서를 조정합니다.
Last updated on