Skip to content

[JEWEL-1150] Create Got It Tooltip Component#3451

Open
DanielSouzaBertoldi wants to merge 2 commits intoJetBrains:masterfrom
DanielSouzaBertoldi:dsb/JEWEL-1150
Open

[JEWEL-1150] Create Got It Tooltip Component#3451
DanielSouzaBertoldi wants to merge 2 commits intoJetBrains:masterfrom
DanielSouzaBertoldi:dsb/JEWEL-1150

Conversation

@DanielSouzaBertoldi
Copy link
Collaborator

@DanielSouzaBertoldi DanielSouzaBertoldi commented Mar 6, 2026

Important

This PR is a direct continuation of #3449! While that PR is not merged, just ignore these three files changed: JBPopupRenderer.kt, Popup.kt and JDialogRenderer.kt

Context

This PR adds the greatest component EVER to Jewel's arsenal. Don't let the "tooltip" part in its name fool you. This does NOT act like a tooltip at all, since a tooltip by definition should only be active while your cursor is hovering a component, and this weird guy loves to behave like your plain 'ol popup. It won't disappear unless you click its main button (actually there are other ways to close it which you'll find out during code review, yes this your encouragement to go through the code carefully and find out all its features. Or just check the screen recordings below if you feel like cheating and don't mind a guilty conscience), and hangs around basically begging for the user's attention.

The cool thing though is that you are free to choose where it's going to show up below, on top or at the start/end of another component, and it has a very cool custom shape that resembles a balloon. Everybody loves balloons!

Current limitations

  1. This PR does not add support for keyboard shortcuts.
  2. This PR does not add support for Lottie animations (since they don't provide support for CfD AFAIK)
  3. The main button in IJP's version can be colored with a vertical linear gradient. Ours, for now, does not.
  4. We do not support contrast colors.

Changes

  • Added GotItTooltip composable with balloon popup, auto-positioning, and configurable GotItBody DSL
  • Added BalloonShape for drawing the balloon outline with directional arrow tip
  • Added BalloonPopupPositionProvider for positioning the balloon relative to its anchor
  • Extracted calculateBalloonPosition and createBalloonOutline as @InternalJewelApi top-level functions in BalloonInternals
  • Added bridge theming via readGotItTooltipStyle / readGotItButtonStyle in IntUiBridgeGotItTooltip
  • Added standalone (Int UI) theme defaults

Demo

Themes

Light Dark
IJP image image
Jewel image image

Appearance

Main Options

IJP Jewel
Header image image
Image w/ Borders image image
Image w/o Borders image image
Icon image image
Step (smaller than 10) image image
Step (greater than 9) image image
Max Width (150) image image
Max Width w/ Image image image

Buttons

IJP Jewel
Without Buttons image image
Only Default Primary Button image image
W/ Secondary Button image image
Primary Button Hover
Screen.Recording.2026-03-09.at.14.57.54.mov
Screen.Recording.2026-03-09.at.14.59.25.mov
Primary Button Click
Screen.Recording.2026-03-09.at.15.01.12.mov
Screen.Recording.2026-03-09.at.15.00.29.mov
Secondary Button Hover
Screen.Recording.2026-03-09.at.15.04.34.mov
Screen.Recording.2026-03-09.at.15.03.51.mov
Secondary Button Click
Screen.Recording.2026-03-09.at.15.05.20.mov
Screen.Recording.2026-03-09.at.15.05.41.mov
Link image image
Browser Link image image

Note: Yes, on click shouldn't show a blue outline. However, that would take a bit of refactoring on the Button composable side which is not part of this PR!

Body text

IJP Jewel
Plain Text image image
Code image image
Icon image image
Bold text image image
Link image image
External Link image image
Hovering Inline Links
Screen.Recording.2026-03-09.at.15.56.04.mov
Screen.Recording.2026-03-09.at.15.53.36.mov

Behavioral

IJP Jewel
Link (Browser)
Screen.Recording.2026-03-09.at.16.21.36.mov
Screen.Recording.2026-03-09.at.16.18.22.mov
Link (Internal Action)
Screen.Recording.2026-03-09.at.16.22.38.mov
Screen.Recording.2026-03-09.at.16.23.18.mov
Timeout (3 seconds)
Screen.Recording.2026-03-09.at.16.26.55.mov
Screen.Recording.2026-03-09.at.16.26.08.mov
Max Width w/ Image image
Screen.Recording.2026-03-09.at.16.29.09.mov
Text < 5 lines w/o image/max width image image
Text < 5 lines w image image image
Text < 5 lines w max width image
Screen.Recording.2026-03-09.at.16.36.31.mov
Text >=5 lines w/o image/max width image
Screen.Recording.2026-03-09.at.16.55.19.mov
Text >=5 lines w image image image
Text >=5 lines w max width image image
onEscapePress defined DISCUSSION PENDING. CHECK COMMENTS DISCUSSION PENDING. CHECK COMMENTS
onEscapePress undefined DISCUSSION PENDING. CHECK COMMENTS DISCUSSION PENDING. CHECK COMMENTS

Got It in its full glory (aka all possible customizations at once)

Light Dark
IJP image image
Jewel image image

Popup Renderer

Compose Jewel w/o Compose Interop Blending Jewel w/ Compose Interop Blending
Light
Screen.Recording.2026-03-10.at.09.04.26.mov
Screen.Recording.2026-03-10.at.09.07.41.mov
Screen.Recording.2026-03-10.at.09.09.43.mov
Dark
Screen.Recording.2026-03-10.at.09.05.06.mov
Screen.Recording.2026-03-10.at.09.08.28.mov
Screen.Recording.2026-03-10.at.09.10.19.mov

Release notes

New features

  • Added new GotItTooltip component with a balloon popup, rich body DSL (text, link, shortcut segments), optional header, primary/secondary buttons, step indicator, timeout auto-dismiss, and keyboard escape support. Check the showcase for all possible customizations.

Comment on lines +253 to +254
val shouldListenForEscapeKeyPress by
rememberUpdatedState(visible && currentOnEscapePress != null || buttons.hasNoButtons())
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So, we have a decision to make here. I humbly ask for your input :)

In GotItComponentBuilder from IJP, there's this function:

/**
 * Sets the action to be executed when the escape key is pressed.
 * Note that the popup will be closed after the action execution.
 * If escape action is not set, the popup won't be closed on the escape key.
 */
fun onEscapePressed(action: () -> Unit): GotItComponentBuilder {
  this.escapeAction = action
  return this
}

The last line in the comment states pretty convincingly that, if users don't set a escape action, then the popup will not be closed on the escape key.

This however, is false. It doesn't matter if you set up a escape action or not, the popup will always be dismissed when users press escape.

The funny thing is, it doesn't say anything about how the component can be closed if there's no buttons, so that's why I added a "escape hatch" for such cases. If there are no buttons, then users can (and should, unless developers externally set visible to false on some other condition) close the popup when pressing the escape key.

So, I decided on follow what's actually written in the code instead of its actual behavior.

What do you think? Stick to the code intention or the actual behavior?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant