Skip to content

[render preview] [Feature] Add Bubble component#445

Merged
cirdes merged 3 commits into
mainfrom
feat/bubble-component
Jun 30, 2026
Merged

[render preview] [Feature] Add Bubble component#445
cirdes merged 3 commits into
mainfrom
feat/bubble-component

Conversation

@djalmaaraujo

@djalmaaraujo djalmaaraujo commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

Ports the shadcn Bubble component to RubyUI — a chat bubble surface for conversational content.

Reference:

What's included

  • Bubble — root, variant: (default, secondary, muted, tinted, outline, ghost, destructive) and align: (start/end)
  • BubbleContent — content wrapper; polymorphic via as: (:div default, :a, :button) for link/button bubbles — the idiomatic Phlex equivalent of shadcn's asChild
  • BubbleReactions — edge-anchored reactions pill, side: (top/bottom) + align: (start/end)
  • BubbleGroup — stacks bubbles with tight spacing

Notes

  • shadcn ships these styles as a custom cn-bubble-* CSS layer. This port translates every rule to real Tailwind v4 utilities + RubyUI theme tokens, so no custom CSS is shipped.
  • No JS (no Stimulus controller).
  • Composes with the existing Tooltip and Popover components — examples included in the docs page.
  • No dependencies.yml entry (Bubble has no gem/JS/component deps, like Avatar/Card).

Testing

  • cd gem && bundle exec rake → tests + StandardRB green.
  • Docs page rendered locally at /docs/bubble: all 7 variants, alignment, reactions (top/bottom × start/end), group, link/button hover states, and Tooltip/Popover integration verified visually.

Screenshots

Add before/after screenshots of /docs/bubble.


Summary by cubic

Add a chat Bubble component to RubyUI for conversational UIs with 7 variants, alignment, grouping, reactions, docs, tests, and mcp registry entries. Built with Tailwind v4 utilities and RubyUI tokens—no custom CSS or JS.

  • New Features

    • RubyUI::Bubble with variants: default, secondary, muted, tinted, outline, ghost, destructive; align: :start|:end.
    • RubyUI::BubbleContent polymorphic via as: :div|:a|:button for link/button bubbles.
    • RubyUI::BubbleReactions pill anchored to the bubble edge; side: :top|:bottom, align: :start|:end.
    • RubyUI::BubbleGroup to stack bubbles with tight spacing.
    • Docs at /docs/bubble with examples and Tooltip/Popover integration; routes, menu, sitemap, and LLMs maps updated.
    • Rebuilt mcp registry to include Bubble metadata and examples.
  • Bug Fixes

    • Simplified BubbleContent interactive selectors to &:is(button,a) to match variant hover rules and reduce class noise without changing behavior.

Written for commit 1ae9145. Summary will update on new commits.

Review in cubic

Port the shadcn Bubble component: a chat bubble surface with 7 variants
(default, secondary, muted, tinted, outline, ghost, destructive),
start/end alignment, grouping, and edge-anchored reactions.

- Translates shadcn's cn-bubble-* CSS layer to real Tailwind v4 utilities
  and RubyUI theme tokens (no custom CSS shipped).
- BubbleContent is polymorphic via as: (:div/:a/:button) for link/button
  bubbles — the idiomatic Phlex equivalent of shadcn's asChild.
- No JS. Composes with Tooltip/Popover (docs examples included).

Docs page, route, controller, menu entry and site_files updated.
@djalmaaraujo djalmaaraujo requested a review from cirdes as a code owner June 30, 2026 15:16

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 14 files

Re-trigger cubic

djalmaaraujo added a commit that referenced this pull request Jun 30, 2026
Port the shadcn Message component: the higher-level chat layout that pairs
an avatar with bubbles, headers, and footers. Built on top of Bubble (#445).

- 6 parts: Message (align start/end), MessageGroup, MessageAvatar,
  MessageContent, MessageHeader, MessageFooter.
- Reuses the existing Avatar and the new Bubble components — no new
  primitives. Bubble alignment inside a message is driven by Message via
  group-data-[align=end]/message; Bubble itself is unchanged.
- Translates shadcn's cn-message-* CSS layer to Tailwind v4 utilities.
- dependencies.yml: message depends on Avatar + Bubble.

Closes #444.

Docs page, route, controller, menu entry and site_files updated.
Collapse the duplicated [&_button]/[&_a] pairs into [&:is(button,a)]
selectors targeting the content element itself (the polymorphic as:
:button/:a case), matching the variant hover selectors. Halves the
class string with identical behavior.
djalmaaraujo added a commit that referenced this pull request Jun 30, 2026
Port the shadcn Message component: the higher-level chat layout that pairs
an avatar with bubbles, headers, and footers. Built on top of Bubble (#445).

- 6 parts: Message (align start/end), MessageGroup, MessageAvatar,
  MessageContent, MessageHeader, MessageFooter.
- Reuses the existing Avatar and the new Bubble components — no new
  primitives. Bubble alignment inside a message is driven by Message via
  group-data-[align=end]/message; Bubble itself is unchanged.
- Translates shadcn's cn-message-* CSS layer to Tailwind v4 utilities.
- dependencies.yml: message depends on Avatar + Bubble.

Closes #444.

Docs page, route, controller, menu entry and site_files updated.
@djalmaaraujo djalmaaraujo changed the title [Feature] Add Bubble component [render preview] [Feature] Add Bubble component Jun 30, 2026
@djalmaaraujo djalmaaraujo temporarily deployed to feat/bubble-component - web-1 PR #445 June 30, 2026 15:45 — with Render Destroyed
djalmaaraujo added a commit that referenced this pull request Jun 30, 2026
Port the shadcn Message component: the higher-level chat layout that pairs
an avatar with bubbles, headers, and footers. Built on top of Bubble (#445).

- 6 parts: Message (align start/end), MessageGroup, MessageAvatar,
  MessageContent, MessageHeader, MessageFooter.
- Reuses the existing Avatar and the new Bubble components — no new
  primitives. Bubble alignment inside a message is driven by Message via
  group-data-[align=end]/message; Bubble itself is unchanged.
- Translates shadcn's cn-message-* CSS layer to Tailwind v4 utilities.
- dependencies.yml: message depends on Avatar + Bubble.

Closes #444.

Docs page, route, controller, menu entry and site_files updated.
@cirdes cirdes merged commit d95814f into main Jun 30, 2026
8 checks passed
@cirdes cirdes deleted the feat/bubble-component branch June 30, 2026 16:53
djalmaaraujo added a commit that referenced this pull request Jun 30, 2026
* [Feature] Add Bubble component

Port the shadcn Bubble component: a chat bubble surface with 7 variants
(default, secondary, muted, tinted, outline, ghost, destructive),
start/end alignment, grouping, and edge-anchored reactions.

- Translates shadcn's cn-bubble-* CSS layer to real Tailwind v4 utilities
  and RubyUI theme tokens (no custom CSS shipped).
- BubbleContent is polymorphic via as: (:div/:a/:button) for link/button
  bubbles — the idiomatic Phlex equivalent of shadcn's asChild.
- No JS. Composes with Tooltip/Popover (docs examples included).

Docs page, route, controller, menu entry and site_files updated.

* [Bug Fix] Bubble: simplify BubbleContent interactive styles

Collapse the duplicated [&_button]/[&_a] pairs into [&:is(button,a)]
selectors targeting the content element itself (the polymorphic as:
:button/:a case), matching the variant hover selectors. Halves the
class string with identical behavior.

* [Feature] Bubble: rebuild MCP registry

* [Feature] Add Message component

Port the shadcn Message component: the higher-level chat layout that pairs
an avatar with bubbles, headers, and footers. Built on top of Bubble (#445).

- 6 parts: Message (align start/end), MessageGroup, MessageAvatar,
  MessageContent, MessageHeader, MessageFooter.
- Reuses the existing Avatar and the new Bubble components — no new
  primitives. Bubble alignment inside a message is driven by Message via
  group-data-[align=end]/message; Bubble itself is unchanged.
- Translates shadcn's cn-message-* CSS layer to Tailwind v4 utilities.
- dependencies.yml: message depends on Avatar + Bubble.

Closes #444.

Docs page, route, controller, menu entry and site_files updated.

* [Feature] Message: rebuild MCP registry
djalmaaraujo added a commit that referenced this pull request Jun 30, 2026
Port the shadcn Message Scroller: a chat transcript scroller that follows
the live edge, anchors new turns near the top, and jumps to the latest
message. Built on top of Message (#446) and Bubble (#445).

shadcn delegates to a closed React primitive (@shadcn/react); this is a
from-scratch Stimulus controller (ruby-ui--message-scroller) — our own
code, no external lib:

- autoScroll follow-edge: pins to the bottom while the reader is there,
  releases on wheel/touch/keyboard/scrollbar away, re-engages on jump.
- scrollAnchor: settles an appended turn near the top keeping a peek of
  the previous item (previous_item_peek).
- defaultPosition: open at end / start / last-anchor.
- preserveOnPrepend: hold the visible row when history loads in above.
- Public API for streaming/ActionCable: scrollToEnd/scrollToStart/
  scrollToMessage; new rows are picked up via MutationObserver.
- rAF-based smooth scrolling (native smooth is unreliable on a contained
  viewport), honors prefers-reduced-motion.
- a11y: content role=log + aria-relevant, button sr-only label, button
  removed from tab order while inert.

Parts: MessageScrollerProvider, MessageScroller, MessageScrollerViewport,
MessageScrollerContent, MessageScrollerItem, MessageScrollerButton.

dependencies.yml: message_scroller depends on Message + Bubble. MCP
registry, docs page, route, controller, menu and site_files updated.
djalmaaraujo added a commit that referenced this pull request Jun 30, 2026
Port the shadcn Message Scroller: a chat transcript scroller that follows
the live edge, anchors new turns near the top, and jumps to the latest
message. Built on top of Message (#446) and Bubble (#445).

shadcn delegates to a closed React primitive (@shadcn/react); this is a
from-scratch Stimulus controller (ruby-ui--message-scroller) — our own
code, no external lib:

- autoScroll follow-edge: pins to the bottom while the reader is there,
  releases on wheel/touch/keyboard/scrollbar away, re-engages on jump.
- scrollAnchor: settles an appended turn near the top keeping a peek of
  the previous item (previous_item_peek).
- defaultPosition: open at end / start / last-anchor.
- preserveOnPrepend: hold the visible row when history loads in above.
- Public API for streaming/ActionCable: scrollToEnd/scrollToStart/
  scrollToMessage; new rows are picked up via MutationObserver.
- rAF-based smooth scrolling (native smooth is unreliable on a contained
  viewport), honors prefers-reduced-motion.
- a11y: content role=log + aria-relevant, button sr-only label, button
  removed from tab order while inert.

Parts: MessageScrollerProvider, MessageScroller, MessageScrollerViewport,
MessageScrollerContent, MessageScrollerItem, MessageScrollerButton.

dependencies.yml: message_scroller depends on Message + Bubble. MCP
registry, docs page, route, controller, menu and site_files updated.
cirdes pushed a commit that referenced this pull request Jun 30, 2026
* [Feature] Add Empty component

Port the shadcn Empty component: a centered empty-state surface for when
there is no data or content. Parts: Empty, EmptyHeader, EmptyMedia
(default/icon variants), EmptyTitle, EmptyDescription, EmptyContent.

Translates shadcn's cn-empty-* CSS layer to Tailwind v4 utilities. No JS.

Docs page, route, controller, menu, site_files and MCP registry updated.

* [Feature] Add Message Scroller component

Port the shadcn Message Scroller: a chat transcript scroller that follows
the live edge, anchors new turns near the top, and jumps to the latest
message. Built on top of Message (#446) and Bubble (#445).

shadcn delegates to a closed React primitive (@shadcn/react); this is a
from-scratch Stimulus controller (ruby-ui--message-scroller) — our own
code, no external lib:

- autoScroll follow-edge: pins to the bottom while the reader is there,
  releases on wheel/touch/keyboard/scrollbar away, re-engages on jump.
- scrollAnchor: settles an appended turn near the top keeping a peek of
  the previous item (previous_item_peek).
- defaultPosition: open at end / start / last-anchor.
- preserveOnPrepend: hold the visible row when history loads in above.
- Public API for streaming/ActionCable: scrollToEnd/scrollToStart/
  scrollToMessage; new rows are picked up via MutationObserver.
- rAF-based smooth scrolling (native smooth is unreliable on a contained
  viewport), honors prefers-reduced-motion.
- a11y: content role=log + aria-relevant, button sr-only label, button
  removed from tab order while inert.

Parts: MessageScrollerProvider, MessageScroller, MessageScrollerViewport,
MessageScrollerContent, MessageScrollerItem, MessageScrollerButton.

dependencies.yml: message_scroller depends on Message + Bubble. MCP
registry, docs page, route, controller, menu and site_files updated.

* [Bug Fix] Message Scroller: address review feedback

- Gate anchored-turn scrolling behind autoScroll/following, so a new turn
  never yanks a reader who scrolled up to older content (P1).
- Scroll button honors data-direction: a start-direction button now jumps
  to the start instead of the end (renamed action jumpToEnd → jump) (P2).
- Guard last-anchor opening position with hasContentTarget; the Stimulus
  target getter throws rather than returning undefined (P2).
- Include the flex row gap in prepend preservation so the visible row no
  longer drifts down by one gap per history insertion (P2).
- Only treat direct content children as transcript rows; markup mutated
  inside a message (subtree) is handled as streaming, not history (P2).

Rebuilt MCP registry.

* [Documentation] Message Scroller: faithful chat-window demo with Empty state

Add the shadcn-style chat window as the hero example: a Card with an Empty
state until the first message, then a scrolling transcript that follows the
live edge, plus an input footer. A docs-only Stimulus demo harness
(message-scroller-chat) clones server-rendered user/assistant templates on
send so the scroller's autoscroll/anchoring is demonstrated live — standing
in for a real ActionCable/streaming source. Uses the new Empty component.

* [Bug Fix] Message Scroller: direction-aware scroll button visibility

updateButton now derives each button's active state from its own
data-direction — an end button activates when away from the bottom, a
start button when away from the top — and iterates all button targets.
Previously a start-direction button used end logic, so it was inert at
the live edge and showed at the wrong end. (cubic P2)
@djalmaaraujo djalmaaraujo mentioned this pull request Jul 1, 2026
3 tasks
djalmaaraujo added a commit that referenced this pull request Jul 1, 2026
Bump RubyUI to 1.5.0 (minor: new components since v1.4.0).

- gem/lib/ruby_ui.rb → 1.5.0; regenerate gem/ and docs/ Gemfile.lock
- docs home hero badge → headline features (Bubble, Message, Empty)
- rebuild mcp/data/registry.json

Highlights since v1.4.0:
- New components: Bubble (#445), Message (#446), Message Scroller (#447), Empty (#448)
- Port hover_card & context_menu to Floating UI, drop Popper.js (#438)
- Bug fixes: Dialog closed-state + docs controller (#458), DropdownMenu z-index (#440)
djalmaaraujo added a commit that referenced this pull request Jul 1, 2026
Bump RubyUI to 1.5.0 (minor: new components since v1.4.0).

- gem/lib/ruby_ui.rb → 1.5.0; regenerate gem/ and docs/ Gemfile.lock
- docs home hero badge → headline features (Bubble, Message, Empty)
- rebuild mcp/data/registry.json

Highlights since v1.4.0:
- New components: Bubble (#445), Message (#446), Message Scroller (#447), Empty (#448)
- Port hover_card & context_menu to Floating UI, drop Popper.js (#438)
- Bug fixes: Dialog closed-state + docs controller (#458), DropdownMenu z-index (#440)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants