Skip to content

PrezelTooltip 구현#131

Merged
moondev03 merged 7 commits intodevelopfrom
feat/#126-ds-tooltip
May 10, 2026
Merged

PrezelTooltip 구현#131
moondev03 merged 7 commits intodevelopfrom
feat/#126-ds-tooltip

Conversation

@moondev03
Copy link
Copy Markdown
Member

@moondev03 moondev03 commented May 9, 2026

📌 작업 내용

  • 디자인 시스템에 PrezelTooltipBox 컴포넌트를 추가했습니다.
  • balloon-compose 의존성을 도입해 툴팁 UI를 구성했습니다.
  • anchor의 window 내 가시성을 기준으로 tooltip 노출/복원 로직을 구현했습니다.
    • 외부 입력으로 dismiss 되지 않는다.라는 요구 사항으로 인해 스크롤 시 화면을 벗어나면 숨김 처리
  • Arrow 위치를 자동으로 계산해서 Anchor 중앙에 위치하도록 했습니다.
  • 위의 내용들은 프리뷰를 통해 확인이 가능합니다.

🧩 관련 이슈


📸 스크린샷

Screen_recording_20260510_004200.webm

📢 논의하고 싶은 내용

Material 3 TooltipBox를 사용하려고 했으나 현재 Caret(Arrow) 부분이 늦게 그려지는 문제가 있었습니다.
해결하기 위해 여러 방법을 사용해봤지만 해결하지 못해서 skydoves/Balloon 라이브러리를 활용하여 구현하였습니다.
해당 이슈는 알려진 문제로 수정이 진행중인 것으로 보입니다.

Summary by CodeRabbit

  • 새로운 기능

    • 설정 가능한 텍스트, 선택적 화살표 및 닫기 아이콘을 갖춘 툴팁 컴포넌트를 추가했습니다. 탭/복원 동작에 따라 동적으로 표시 및 숨김됩니다.
  • 작업(Chores)

    • 툴팁 구현에 필요한 외부 라이브러리 참조를 추가하고 버전 카탈로그를 업데이트했습니다.
  • 문구

    • 툴팁 닫기 버튼의 접근성용 문자열 리소스를 추가했습니다.

Review Change Stack

moondev03 added 4 commits May 10, 2026 00:19
* **feat: `PrezelTooltipBox` 컴포넌트 구현**
    * Skydoves의 Balloon 라이브러리를 활용하여 커스텀 툴팁 컴포넌트를 구현했습니다.
    * 디자인 가이드에 맞춰 배경색(`bgMedium`), 화살표 크기, 코너 라운드, 패딩 등 `Balloon.Builder` 설정을 적용했습니다.
    * 툴팁 내부 콘텐츠 구성을 위한 `TooltipContent`를 분리하고, 선택적인 닫기 아이콘 표시 기능을 추가했습니다.
    * 클릭 시 툴팁이 노출되도록 `Modifier.balloon` 및 `rememberBalloonState`를 연동했습니다.

* **build: Balloon 라이브러리 의존성 추가**
    * 툴팁 기능 구현을 위해 `core:designsystem` 모듈에 `balloon-compose` 의존성을 추가했습니다.
* **style: PrezelTooltipBox의 Balloon 화살표 배치 설정 수정**
    * 툴팁 화살표가 기준이 되는 요소(Anchor)의 중앙에 정확히 위치하도록 `setArrowPosition(0.5f)` 및 `setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR)` 설정을 추가했습니다.
* **feat: `PrezelTooltipBox` 외부 터치 시 닫힘 방지 및 클릭 동작 수정**
    * Balloon 설정에 `setDismissWhenTouchOutside(false)`를 추가하여 외부 영역 터치 시 툴팁이 자동으로 닫히지 않도록 변경했습니다.
    * 툴팁 컨텐츠를 클릭했을 때 툴팁이 닫히도록 `TooltipContent`에 클릭 이벤트를 추가했습니다.

* **refactor: `Modifier.noRippleClick` 확장 함수 추가 및 적용**
    * 리플 효과 없이 클릭 이벤트를 처리하기 위한 `noRippleClick` 확장 함수를 정의했습니다.
    * `PrezelTooltipBox`의 표시(show) 및 닫기(dismiss) 로직에 해당 함수를 적용하여 불필요한 인디케이션을 제거했습니다.

* **refactor: `TooltipContent` 컴포저블 구조 개선**
    * 상위 수준에서 레이아웃 및 이벤트를 제어할 수 있도록 `modifier` 파라미터를 추가하고 내부 `Row`에 적용했습니다.
* **feat: 툴팁 노출 상태 및 화면 내 가시성 처리 로직 추가**
    * `onGloballyPositioned`와 `getWindowVisibleDisplayFrame`을 사용하여 툴팁의 앵커가 현재 화면(Window) 내에 위치하는지 감지하는 로직을 구현했습니다.
    * `shouldRestoreTooltip` 상태를 추가하여 사용자가 명시적으로 툴팁을 닫았는지 여부를 추적하고, 앵커가 화면에 보일 때만 툴팁이 유지되도록 `LaunchedEffect`로 제어합니다.
    * 툴팁 내부 클릭 시 `shouldRestoreTooltip`을 `false`로 설정하여 의도치 않게 다시 뜨는 현상을 방지했습니다.

* **refactor: 툴팁 컴포넌트 구조 및 설정 최적화**
    * `TooltipContent`의 가시성을 `private`으로 제한하여 캡슐화를 강화했습니다.
    * `BalloonAnimation.NONE`을 적용하여 툴팁 표시 시 불필요한 애니메이션을 제거했습니다.
    * 툴팁의 닫기 아이콘에 접근성을 위한 `contentDescription`을 추가하고 관련 문자열 리소스를 정의했습니다.
    * 앵커 위치 계산을 위해 `Rect.intersects` 확장 함수를 추가했습니다.

* **style: 프리뷰 코드 정리**
    * `PrezelTooltipBoxPreview`에서 불필요한 `innerPadding` 참조를 제거하고 레이아웃을 정돈했습니다.
@moondev03 moondev03 self-assigned this May 9, 2026
@moondev03 moondev03 added the ✨ feat 새로운 기능 추가 또는 기존 기능 확장 label May 9, 2026
@moondev03 moondev03 requested a review from HamBeomJoon as a code owner May 9, 2026 15:53
@moondev03 moondev03 linked an issue May 9, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf40d686-544e-4274-aeaa-128c301a221a

📥 Commits

Reviewing files that changed from the base of the PR and between f4dcb4e and 5414cbe.

📒 Files selected for processing (3)
  • Prezel/core/designsystem/build.gradle.kts
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt
  • Prezel/gradle/libs.versions.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • Prezel/core/designsystem/build.gradle.kts
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt

📝 Walkthrough

워크스루

이 PR은 Skydoves Balloon 라이브러리를 기반으로 한 새로운 Jetpack Compose 툴팁 컴포넌트 PrezelTooltipBox를 추가합니다. 컴포넌트는 앵커의 윈도우 가시성을 추적하고 사용자 상호작용(탭)에 따라 툴팁을 표시하거나 복원합니다.

변경 사항

PrezelTooltip 기능 구현

Layer / File(s) Summary
의존성 추가
Prezel/core/designsystem/build.gradle.kts, Prezel/gradle/libs.versions.toml
Skydoves Balloon Compose 라이브러리(balloon-compose, version ref balloonCompose)를 버전 카탈로그 및 모듈 의존성에 추가합니다.
핵심 컴포넌트 및 상태 관리
Prezel/core/designsystem/src/main/java/.../PrezelTooltipBox.kt
PrezelTooltipBox 컴포저블은 호출자 콘텐츠를 래핑하고 Balloon 툴팁을 첨부합니다. 앵커의 윈도우 가시성에 따라 툴팁 표시/복원/닫기 상태를 관리하며, 콘텐츠 탭으로 닫기, 앵커 탭으로 복원하는 동작을 처리합니다.
Balloon 구성 및 유틸리티 헬퍼
Prezel/core/designsystem/src/main/java/.../PrezelTooltipBox.kt
rememberBalloonBuilder는 화살표 표시, 패딩, 모서리 반경, 배경색, 터치 외부 닫기 및 애니메이션 비활성화 등을 설정합니다. Rect.intersectsModifier.noRippleClick 유틸리티를 추가합니다.
툴팁 UI 렌더링
Prezel/core/designsystem/src/main/java/.../PrezelTooltipBox.kt
TooltipContent는 제공된 텍스트와 선택적 닫기 아이콘을 포함하는 행을 렌더링하며 테마 타이포그래피/색상을 적용합니다.
미리보기 및 문자열 리소스
Prezel/core/designsystem/src/main/java/.../PrezelTooltipBox.kt, Prezel/core/designsystem/src/main/res/values/strings.xml
PrezelTooltipBoxPreview는 스크롤 가능한 스캐폴드에서 여러 툴팁 인스턴스를 보여줍니다. 문자열 리소스 core_designsystem_tooltip_cancel_btn_content_desc"툴팁 닫기"로 추가합니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 변경의 핵심인 PrezelTooltipBox 컴포넌트 구현을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명은 작업 내용, 관련 이슈, 스크린샷, 논의 내용 등 필수 섹션을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed PR의 모든 구현(PrezelTooltipBox 컴포넌트, balloon-compose 의존성, 가시성 로직)이 #126의 목표를 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 PrezelTooltipBox 구현의 범위 내에 있으며 불필요한 수정은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt (1)

72-74: ⚡ Quick win

onGloballyPositioned 내부 Rect 할당은 재사용으로 줄이는 편이 좋습니다.

현재 콜백 호출마다 android.graphics.Rect()를 생성하므로, 스크롤 중 불필요한 객체 할당이 누적될 수 있습니다.

♻️ 제안 diff
 fun PrezelTooltipBox(
     text: String,
     modifier: Modifier = Modifier,
     showDismissIcon: Boolean = true,
     showArrow: Boolean = true,
     content: `@Composable` BoxScope.() -> Unit,
 ) {
     val builder = rememberBalloonBuilder(showArrow)
     val state = rememberBalloonState(builder)
     val view = LocalView.current
+    val visibleFrame = remember { android.graphics.Rect() }
     var shouldRestoreTooltip by remember { mutableStateOf(false) }
     var isAnchorVisibleInWindow by remember { mutableStateOf(true) }

     LaunchedEffect(shouldRestoreTooltip, isAnchorVisibleInWindow) {
         if (shouldRestoreTooltip && isAnchorVisibleInWindow) state.showAlignTop() else state.dismiss()
@@
         modifier = modifier
             .onGloballyPositioned { coordinates ->
-                val visibleFrame = android.graphics.Rect()
                 view.getWindowVisibleDisplayFrame(visibleFrame)
                 isAnchorVisibleInWindow = coordinates.boundsInWindow().intersects(visibleFrame.toComposeRect())
             }.noRippleClick { shouldRestoreTooltip = true }
             .balloon(state) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt`
around lines 72 - 74, The callback currently allocates a new
android.graphics.Rect on every onGloballyPositioned call which causes churn;
instead create a single reusable Rect and reuse it inside the callback (e.g.
remember { android.graphics.Rect() } or a private field) and pass that instance
to view.getWindowVisibleDisplayFrame(...) before computing
isAnchorVisibleInWindow via
coordinates.boundsInWindow().intersects(visibleFrame.toComposeRect()); ensure
you call getWindowVisibleDisplayFrame on the same Rect each invocation rather
than creating a new one.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt`:
- Around line 72-74: The callback currently allocates a new
android.graphics.Rect on every onGloballyPositioned call which causes churn;
instead create a single reusable Rect and reuse it inside the callback (e.g.
remember { android.graphics.Rect() } or a private field) and pass that instance
to view.getWindowVisibleDisplayFrame(...) before computing
isAnchorVisibleInWindow via
coordinates.boundsInWindow().intersects(visibleFrame.toComposeRect()); ensure
you call getWindowVisibleDisplayFrame on the same Rect each invocation rather
than creating a new one.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dc9677fa-7692-4b61-aae9-09f2b9062501

📥 Commits

Reviewing files that changed from the base of the PR and between 5f473d7 and f4dcb4e.

📒 Files selected for processing (3)
  • Prezel/core/designsystem/build.gradle.kts
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt
  • Prezel/core/designsystem/src/main/res/values/strings.xml

moondev03 added 2 commits May 10, 2026 01:26
* **refactor: `onGloballyPositioned` 내 객체 할당 최적화**
    * 뷰의 가시 영역을 계산할 때 사용되는 `android.graphics.Rect` 객체를 `remember`를 사용하여 재사용하도록 변경함으로써, 레이아웃 계산 시 발생하는 불필요한 객체 생성을 방지했습니다.
    * `modifier` 내 `noRippleClick`과 `onGloballyPositioned`의 호출 순서를 변경하여 코드를 정리했습니다.
* **build: Balloon Compose 라이브러리 정의 및 참조 방식 변경**
    * `libs.versions.toml` 파일에 `balloonCompose` 버전 정보와 라이브러리 선언을 추가했습니다.
    * `core:designsystem` 모듈의 `build.gradle.kts`에서 하드코딩되어 있던 `balloon-compose` 의존성을 Version Catalog 참조(`libs.balloon.compose`) 방식으로 수정했습니다.
Copy link
Copy Markdown
Contributor

@HamBeomJoon HamBeomJoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 👍

@moondev03 moondev03 merged commit b0dcd9f into develop May 10, 2026
2 checks passed
@moondev03 moondev03 deleted the feat/#126-ds-tooltip branch May 10, 2026 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat 새로운 기능 추가 또는 기존 기능 확장

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PrezelTooltip 구현

2 participants