Skip to content

Commit 75db4cf

Browse files
committed
[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.
1 parent a9564c7 commit 75db4cf

17 files changed

Lines changed: 542 additions & 0 deletions

File tree

docs/app/components/shared/components_list.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def components
3535
{name: "Input", path: docs_input_path},
3636
{name: "Link", path: docs_link_path},
3737
{name: "Masked Input", path: masked_input_path},
38+
{name: "Message", path: docs_message_path},
3839
{name: "Pagination", path: docs_pagination_path},
3940
{name: "Popover", path: docs_popover_path},
4041
{name: "Progress", path: docs_progress_path},

docs/app/controllers/docs_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ def masked_input
166166
render Views::Docs::MaskedInput.new
167167
end
168168

169+
def message
170+
render Views::Docs::Message.new
171+
end
172+
169173
def pagination
170174
render Views::Docs::Pagination.new
171175
end

docs/app/lib/site_files.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class SiteFiles
108108
{title: "Input", path: "/docs/input", description: "Styled input field primitive."},
109109
{title: "Link", path: "/docs/link", description: "Link component with button-like and underline variants."},
110110
{title: "Masked Input", path: "/docs/masked_input", description: "Form input with an applied mask."},
111+
{title: "Message", path: "/docs/message", description: "Chat message layout pairing an avatar with bubbles, headers, and footers."},
111112
{title: "Pagination", path: "/docs/pagination", description: "Page navigation with next and previous links."},
112113
{title: "Popover", path: "/docs/popover", description: "Triggered rich content panel."},
113114
{title: "Progress", path: "/docs/progress", description: "Progress bar for task completion state."},

docs/app/views/docs/message.rb

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# frozen_string_literal: true
2+
3+
class Views::Docs::Message < Views::Base
4+
def view_template
5+
component = "Message"
6+
7+
div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
8+
render Docs::Header.new(title: "Message", description: "A chat message layout that pairs an avatar with bubbles, headers, and footers. Built on top of Avatar and Bubble.")
9+
10+
Heading(level: 2) { "Usage" }
11+
12+
render Docs::VisualCodeExample.new(title: "Conversation", context: self) do
13+
<<~RUBY
14+
div(class: "flex flex-col gap-6") do
15+
Message(align: :end) do
16+
MessageAvatar do
17+
Avatar(size: :sm) do
18+
AvatarImage(src: "https://github.com/joeldrapper.png", alt: "@me")
19+
AvatarFallback { "ME" }
20+
end
21+
end
22+
MessageContent do
23+
Bubble do
24+
BubbleContent { "Deploying to prod real quick." }
25+
end
26+
end
27+
end
28+
29+
Message do
30+
MessageAvatar do
31+
Avatar(size: :sm) do
32+
AvatarImage(src: "https://github.com/shadcn.png", alt: "@rabbit")
33+
AvatarFallback { "R" }
34+
end
35+
end
36+
MessageContent do
37+
Bubble(variant: :muted) do
38+
BubbleContent { "It's 4:55 PM. On a Friday." }
39+
end
40+
end
41+
end
42+
43+
Message(align: :end) do
44+
MessageAvatar do
45+
Avatar(size: :sm) do
46+
AvatarImage(src: "https://github.com/joeldrapper.png", alt: "@me")
47+
AvatarFallback { "ME" }
48+
end
49+
end
50+
MessageContent do
51+
Bubble do
52+
BubbleContent { "It's a one-line change." }
53+
end
54+
MessageFooter { "Delivered" }
55+
end
56+
end
57+
58+
Message do
59+
MessageAvatar do
60+
Avatar(size: :sm) do
61+
AvatarImage(src: "https://github.com/shadcn.png", alt: "@rabbit")
62+
AvatarFallback { "R" }
63+
end
64+
end
65+
MessageContent do
66+
BubbleGroup do
67+
Bubble(variant: :muted) do
68+
BubbleContent { "It's always a one-line change 😭." }
69+
end
70+
Bubble(variant: :muted) do
71+
BubbleContent { "Alright, let me take a look." }
72+
BubbleReactions(role: "img", aria_label: "Reaction: thumbs up") do
73+
span { "👍" }
74+
end
75+
end
76+
end
77+
end
78+
end
79+
end
80+
RUBY
81+
end
82+
83+
Heading(level: 2) { "With header" }
84+
85+
render Docs::VisualCodeExample.new(title: "Header and footer", context: self) do
86+
<<~RUBY
87+
Message do
88+
MessageAvatar do
89+
Avatar(size: :sm) do
90+
AvatarImage(src: "https://github.com/shadcn.png", alt: "@oliver")
91+
AvatarFallback { "OL" }
92+
end
93+
end
94+
MessageContent do
95+
MessageHeader { "Oliver" }
96+
Bubble(variant: :muted) do
97+
BubbleContent { "Pushed the fix, can you review?" }
98+
end
99+
MessageFooter { "9:41 AM" }
100+
end
101+
end
102+
RUBY
103+
end
104+
105+
Heading(level: 2) { "Alignment" }
106+
107+
render Docs::VisualCodeExample.new(title: "Sender and receiver", context: self) do
108+
<<~RUBY
109+
div(class: "flex flex-col gap-6") do
110+
Message do
111+
MessageAvatar do
112+
Avatar(size: :sm) do
113+
AvatarImage(src: "https://github.com/shadcn.png", alt: "@rabbit")
114+
AvatarFallback { "R" }
115+
end
116+
end
117+
MessageContent do
118+
Bubble(variant: :muted) { BubbleContent { "Aligned to the start." } }
119+
end
120+
end
121+
Message(align: :end) do
122+
MessageAvatar do
123+
Avatar(size: :sm) do
124+
AvatarImage(src: "https://github.com/joeldrapper.png", alt: "@me")
125+
AvatarFallback { "ME" }
126+
end
127+
end
128+
MessageContent do
129+
Bubble { BubbleContent { "Aligned to the end." } }
130+
end
131+
end
132+
end
133+
RUBY
134+
end
135+
136+
Heading(level: 2) { "Group" }
137+
138+
render Docs::VisualCodeExample.new(title: "Message group", context: self) do
139+
<<~RUBY
140+
MessageGroup do
141+
Message do
142+
MessageAvatar do
143+
Avatar(size: :sm) do
144+
AvatarImage(src: "https://github.com/shadcn.png", alt: "@rabbit")
145+
AvatarFallback { "R" }
146+
end
147+
end
148+
MessageContent do
149+
Bubble(variant: :muted) { BubbleContent { "First message." } }
150+
end
151+
end
152+
Message do
153+
MessageAvatar do
154+
Avatar(size: :sm) do
155+
AvatarImage(src: "https://github.com/shadcn.png", alt: "@rabbit")
156+
AvatarFallback { "R" }
157+
end
158+
end
159+
MessageContent do
160+
Bubble(variant: :muted) { BubbleContent { "Second, tighter spacing." } }
161+
end
162+
end
163+
end
164+
RUBY
165+
end
166+
167+
render Components::ComponentSetup::Tabs.new(component_name: component)
168+
169+
# components
170+
render Docs::ComponentsTable.new(component_files(component))
171+
end
172+
end
173+
end

docs/config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
get "input", to: "docs#input", as: :docs_input
5353
get "link", to: "docs#link", as: :docs_link
5454
get "masked_input", to: "docs#masked_input", as: :masked_input
55+
get "message", to: "docs#message", as: :docs_message
5556
get "pagination", to: "docs#pagination", as: :docs_pagination
5657
get "popover", to: "docs#popover", as: :docs_popover
5758
get "progress", to: "docs#progress", as: :docs_progress

docs/public/llms-full.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ This file expands the curated /llms.txt map into a compact reference that can be
228228
- URL: https://rubyui.com/docs/masked_input
229229
- Summary: Form input with an applied mask.
230230

231+
### Message
232+
233+
- URL: https://rubyui.com/docs/message
234+
- Summary: Chat message layout pairing an avatar with bubbles, headers, and footers.
235+
231236
### Pagination
232237

233238
- URL: https://rubyui.com/docs/pagination

docs/public/llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Use the core docs first for installation, theming, dark mode, and customization
5050
- [Input](https://rubyui.com/docs/input): Styled input field primitive.
5151
- [Link](https://rubyui.com/docs/link): Link component with button-like and underline variants.
5252
- [Masked Input](https://rubyui.com/docs/masked_input): Form input with an applied mask.
53+
- [Message](https://rubyui.com/docs/message): Chat message layout pairing an avatar with bubbles, headers, and footers.
5354
- [Pagination](https://rubyui.com/docs/pagination): Page navigation with next and previous links.
5455
- [Popover](https://rubyui.com/docs/popover): Triggered rich content panel.
5556
- [Progress](https://rubyui.com/docs/progress): Progress bar for task completion state.

docs/public/sitemap.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@
205205
<changefreq>monthly</changefreq>
206206
<priority>0.7</priority>
207207
</url>
208+
<url>
209+
<loc>https://rubyui.com/docs/message</loc>
210+
<changefreq>monthly</changefreq>
211+
<priority>0.7</priority>
212+
</url>
208213
<url>
209214
<loc>https://rubyui.com/docs/pagination</loc>
210215
<changefreq>monthly</changefreq>

gem/lib/generators/ruby_ui/dependencies.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ masked_input:
7373
js_packages:
7474
- "maska"
7575

76+
message:
77+
components:
78+
- "Avatar"
79+
- "Bubble"
80+
7681
pagination:
7782
components:
7883
- "Button"

gem/lib/ruby_ui/message/message.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
module RubyUI
4+
class Message < Base
5+
def initialize(align: :start, **attrs)
6+
@align = align
7+
super(**attrs)
8+
end
9+
10+
def view_template(&)
11+
div(**attrs, &)
12+
end
13+
14+
private
15+
16+
def default_attrs
17+
{
18+
data: {slot: "message", align: @align},
19+
class: "group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse"
20+
}
21+
end
22+
end
23+
end

0 commit comments

Comments
 (0)