Skip to content

Commit 9e0d56b

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 9e0d56b

17 files changed

Lines changed: 508 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: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
AvatarFallback { "ME" }
19+
end
20+
end
21+
MessageContent do
22+
Bubble do
23+
BubbleContent { "Deploying to prod real quick." }
24+
end
25+
end
26+
end
27+
28+
Message do
29+
MessageAvatar do
30+
Avatar(size: :sm) do
31+
AvatarFallback { "R" }
32+
end
33+
end
34+
MessageContent do
35+
Bubble(variant: :muted) do
36+
BubbleContent { "It's 4:55 PM. On a Friday." }
37+
end
38+
end
39+
end
40+
41+
Message(align: :end) do
42+
MessageAvatar do
43+
Avatar(size: :sm) do
44+
AvatarFallback { "ME" }
45+
end
46+
end
47+
MessageContent do
48+
Bubble do
49+
BubbleContent { "It's a one-line change." }
50+
end
51+
MessageFooter { "Delivered" }
52+
end
53+
end
54+
55+
Message do
56+
MessageAvatar do
57+
Avatar(size: :sm) do
58+
AvatarFallback { "R" }
59+
end
60+
end
61+
MessageContent do
62+
BubbleGroup do
63+
Bubble(variant: :muted) do
64+
BubbleContent { "It's always a one-line change 😭." }
65+
end
66+
Bubble(variant: :muted) do
67+
BubbleContent { "Alright, let me take a look." }
68+
BubbleReactions(role: "img", aria_label: "Reaction: thumbs up") do
69+
span { "👍" }
70+
end
71+
end
72+
end
73+
end
74+
end
75+
end
76+
RUBY
77+
end
78+
79+
Heading(level: 2) { "With header" }
80+
81+
render Docs::VisualCodeExample.new(title: "Header and footer", context: self) do
82+
<<~RUBY
83+
Message do
84+
MessageAvatar do
85+
Avatar(size: :sm) do
86+
AvatarFallback { "OL" }
87+
end
88+
end
89+
MessageContent do
90+
MessageHeader { "Oliver" }
91+
Bubble(variant: :muted) do
92+
BubbleContent { "Pushed the fix, can you review?" }
93+
end
94+
MessageFooter { "9:41 AM" }
95+
end
96+
end
97+
RUBY
98+
end
99+
100+
Heading(level: 2) { "Alignment" }
101+
102+
render Docs::VisualCodeExample.new(title: "Sender and receiver", context: self) do
103+
<<~RUBY
104+
div(class: "flex flex-col gap-6") do
105+
Message do
106+
MessageAvatar do
107+
Avatar(size: :sm) { AvatarFallback { "R" } }
108+
end
109+
MessageContent do
110+
Bubble(variant: :muted) { BubbleContent { "Aligned to the start." } }
111+
end
112+
end
113+
Message(align: :end) do
114+
MessageAvatar do
115+
Avatar(size: :sm) { AvatarFallback { "ME" } }
116+
end
117+
MessageContent do
118+
Bubble { BubbleContent { "Aligned to the end." } }
119+
end
120+
end
121+
end
122+
RUBY
123+
end
124+
125+
Heading(level: 2) { "Group" }
126+
127+
render Docs::VisualCodeExample.new(title: "Message group", context: self) do
128+
<<~RUBY
129+
MessageGroup do
130+
Message do
131+
MessageAvatar do
132+
Avatar(size: :sm) { AvatarFallback { "R" } }
133+
end
134+
MessageContent do
135+
Bubble(variant: :muted) { BubbleContent { "First message." } }
136+
end
137+
end
138+
Message do
139+
MessageAvatar do
140+
Avatar(size: :sm) { AvatarFallback { "R" } }
141+
end
142+
MessageContent do
143+
Bubble(variant: :muted) { BubbleContent { "Second, tighter spacing." } }
144+
end
145+
end
146+
end
147+
RUBY
148+
end
149+
150+
render Components::ComponentSetup::Tabs.new(component_name: component)
151+
152+
# components
153+
render Docs::ComponentsTable.new(component_files(component))
154+
end
155+
end
156+
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)