# Help Bubble
URL: /lynx/components/help-bubble
Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/help-bubble.mdx
사용자에게 컴포넌트의 상태나 특정 기능에 대한 추가 정보를 제공하는 말풍선입니다.
사용 가능 버전: @seed-design/lynx-react@0.8.0, @seed-design/lynx-css@0.12.0
## Preview
```tsx
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 (
} />
);
}
```
## Installation
- npm: npx @seed-design/cli@latest add ui:help-bubble
- pnpm: pnpm dlx @seed-design/cli@latest add ui:help-bubble
- yarn: yarn dlx @seed-design/cli@latest add ui:help-bubble
- bun: bun x @seed-design/cli@latest add ui:help-bubble
## Usage
```tsx
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`는 자식을 탭하면 말풍선을 열고 닫습니다.
- `HelpBubbleAnchor`는 위치만 정합니다. `defaultOpen` 또는 `open`과 `onOpenChange`로 상태를 관리하세요.
- 기본 배치는 `"top"`입니다. `placement`, `flip`, `gutter`, `overflowPadding`, `arrowPadding`으로 위치를 조정할 수 있습니다.
- `showCloseButton`으로 닫기 버튼을 추가할 수 있습니다.
- `contentProps.maxWidth`의 기본값은 `280px`입니다. `"none"`으로 제한을 없앨 수 있습니다.
- `zIndexOffset`은 같은 화면 안에서의 표시 순서를 조정합니다. native overlay 사이의 순서는 `overlayLevel`로 정합니다.
## Props
### `HelpBubbleTrigger`
### `HelpBubbleAnchor`
## Examples
### Trigger
`HelpBubbleTrigger`를 탭하면 말풍선이 열리고 닫힙니다. 이 예제는 처음 열린 uncontrolled Trigger와 `open`, `onOpenChange`로 상태를 제어하는 Trigger를 함께 보여줍니다. 두 경우 모두 닫기 버튼으로 닫을 수 있습니다.
```tsx
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 (
토글
토글
);
}
```
### Anchor
`HelpBubbleAnchor`는 아바타처럼 말풍선의 위치만 정하는 요소이며, 탭으로 열고 닫히지 않습니다. `defaultOpen`을 쓰는 uncontrolled Anchor와 `open`, `onOpenChange`를 쓰는 controlled Anchor의 열림 상태는 각각 닫기 버튼과 `열림` Switch로 바꿉니다.
```tsx
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 (
{hasImageError ? (
L
) : (
)}
);
}
export default function Example() {
const [isControlledHelpBubbleOpen, setIsControlledHelpBubbleOpen] = useState(true);
function handleControlledOpenChange(nextOpen: boolean) {
"background only";
setIsControlledHelpBubbleOpen(nextOpen);
}
return (
// 위쪽 말풍선이 들어갈 공간을 위와 두 Anchor 사이에 둡니다.
);
}
```
### Close On Interact Outside
`closeOnInteractOutside`의 기본값은 `true`입니다. `true`이면 Trigger·Anchor와 말풍선 밖을 탭했을 때 말풍선이 닫히고, 그 탭은 아래 요소에도 그대로 전달됩니다. `false`이면 바깥을 탭해도 말풍선이 열린 채로 유지됩니다.
```tsx
import { ActionButton, VStack } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
return (
토글
토글
);
}
```
### Placement
`placement`로 기준 요소의 12개 방향에 말풍선을 배치합니다. 이 예제는 각 배치를 열린 상태로 표시합니다. 화면 경계에서 지정한 방향의 공간이 부족하면 `flip` 기본값에 따라 말풍선이 반대 방향으로 뒤집힙니다.
```tsx
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;
}) {
return (
);
}
export default function Example() {
return (
{/* 위·아래 줄 말풍선이 뒤집히지 않도록 위아래 여백을 두고, 같은 높이 안에서 줄 간격을 줄입니다. */}
);
}
```
### Flip
`flip={false}`를 지정하면 화면 경계에서 공간이 부족해도 말풍선의 방향을 바꾸지 않습니다.
```tsx
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
return (
);
}
```
### Close Button
`showCloseButton`으로 말풍선에 닫기 버튼을 추가할 수 있습니다. 닫기 버튼을 탭하면 말풍선이 닫히고, Trigger를 다시 탭하면 다시 엽니다.
```tsx
import { ActionButton } from "@seed-design/lynx-react";
import { HelpBubbleTrigger } from "@/components/ui/help-bubble";
export default function Example() {
return (
토글
);
}
```
### Description
`description`을 사용하여 `title` 아래에 설명을 추가할 수 있습니다.
```tsx
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
return (
);
}
```
### Title Only
`description` 없이 `title`만 전달할 수 있습니다.
```tsx
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
export default function Example() {
return (
);
}
```
### Setting Width Manually
Content에는 기본 최대 너비가 있습니다. `contentProps.maxWidth`로 이 값을 덮어쓰고, `"none"`으로 최대 너비 제한을 없앨 수 있습니다. `contentProps.style.width`를 함께 지정했을 때 `maxWidth`가 더 좁으면 `maxWidth`가 우선합니다.
```tsx
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("unset");
const [maxWidth, setMaxWidth] = useState("400px");
function handleWidthChange(nextWidth: string) {
"background only";
setWidth(nextWidth as Width);
}
function handleMaxWidthChange(nextMaxWidth: string) {
"background only";
setMaxWidth(nextMaxWidth as MaxWidth);
}
return (
width
{WIDTH_OPTIONS.map((option) => (
{option}
))}
maxWidth
{MAX_WIDTH_OPTIONS.map((option) => (
{option}
))}
);
}
```
### Line Breaks
React의 `
`는 Lynx title에서 native `` 자식과 `"\n"`으로 변환합니다. 문자열의 줄바꿈 문자도 title에 전달할 수 있습니다.
```tsx
import IconSparkle2 from "@karrotmarket/lynx-multicolor-icon/IconSparkle2";
import { HStack } from "@seed-design/lynx-react";
import { HelpBubbleAnchor } from "@/components/ui/help-bubble";
const explicitLineBreakTitle = (
{"Breaking"}
{"\n"}
{"lines"}
{"\n"}
{"using"}
{"\n"}
{"`
`s"}
);
export default function Example() {
return (
);
}
```
### `z-index` Offset
`zIndexOffset`은 같은 Lynx 화면 안에서의 표시 순서를 조정합니다. `container`를 지정한 native overlay 사이의 순서는 `overlayLevel`로 정합니다.
```tsx
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["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 (
{hasImageError ? (
L
) : (
)}
);
}
export default function Example() {
const [zIndexOffset, setZIndexOffset] = useState(5);
function handleOffsetChange(nextOffset: string) {
"background only";
setZIndexOffset(Number(nextOffset));
}
return (
{Array.from({ length: 5 }, (_, index) => (
{index + 100}
))}
{OFFSET_OPTIONS.map((option) => (
{option}
))}
0
5
);
}
```
### Portalled
`container`를 지정하지 않으면 말풍선은 Lynx view 안의 `position: fixed` view로 렌더링됩니다. `container="window"`처럼 native container 이름을 지정하면 native overlay 레이어에 렌더링되어 Lynx view 밖의 host 화면까지 덮습니다. `overlayLevel`은 `container`를 지정했을 때 overlay 레이어의 표시 순서를 정합니다.
overlay 레이어는 말풍선이 아닌 곳의 탭을 아래 화면으로 넘깁니다. `closeOnInteractOutside`가 `true`이면 같은 탭으로 말풍선도 닫힙니다.
```tsx
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 (
window
window
{`아래 버튼 ${count}`}
overlay 레이어 밖의 탭은 아래 화면으로 전달됩니다.
);
}
```
## 웹 버전과의 차이
- Trigger와 Anchor는 자식을 native `view`로 감싸며 `asChild`를 제공하지 않습니다. raw ``처럼 이름을 만들 수 없는 Trigger에는 `accessibility-label`을 지정하세요.
- `closeOnInteractOutside`는 바깥 탭을 아래 요소로 전달합니다. 스크롤처럼 탭이 아닌 상호작용으로는 닫히지 않습니다.
- `container`를 지정하면 native overlay에 렌더링됩니다. 지정하지 않으면 `zIndexOffset`으로 같은 화면 안의 표시 순서를 조정합니다.
- [Help Bubble Tooltip](/react/components/help-bubble-tooltip)은 hover와 키보드 focus를 사용하는 웹 전용 컴포넌트입니다.