# 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를 사용하는 웹 전용 컴포넌트입니다.