Message
One message: who sent it, when, what it says, what came with it and what happened to it. Three looks from one markup: a bubble for a chat, a row for a channel, an email in a card.
Three variants
The same message as a bubble, a row and an email. Only the variant class differs.
<div class="grid gap-8 lg:grid-cols-3">
<% [ :bubble, :row, :email ].each do |variant| %>
<div>
<p class="mb-3 text-xs font-semibold uppercase tracking-wide text-neutral-500"><%= variant %></p>
<%= message variant: variant, author: "Ana Silva", avatar: true, time: @now - 12.minutes, id: "message_variant_#{variant}" do |m| %>
<% m.meta "to Keith Pitt" if variant == :email %>
<% m.actions { |bar| bar.action "Reply", "#", icon: :reply; bar.copy "Friday at 2pm works." } %>
<% if variant == :bubble %>
Friday at 2pm works.
<% else %>
<p>Friday at 2pm works. I'll bring the sketches.</p>
<% end %>
<% end %>
</div>
<% end %>
</div>Theirs and yours
own: true puts a bubble on the right. continued: true drops the name and avatar, keeps the avatar's column and squares the joined corner.
<div class="mx-auto max-w-md">
<%= message_thread do %>
<%= message "Did the parcel arrive?", author: "Ana Silva", avatar: true, time: @now - 9.minutes %>
<%= message "The tracking says it was left with a neighbour.", author: "Ana Silva", avatar: true, time: @now - 8.minutes, continued: true %>
<%= message "Number 12, I think.", author: "Ana Silva", avatar: true, time: @now - 8.minutes, continued: true %>
<%= message "Got it, thanks!", own: true, time: @now - 2.minutes %>
<%= message "They'd put it behind the bins.", own: true, time: @now - 1.minute, continued: true %>
<% end %>
</div>Statuses
m.status with each state: a word and a glyph, coloured only for read and failed, and dimmed while sending. at: adds the moment.
<div class="mx-auto max-w-md">
<%= message_thread do %>
<% { sending: "Just sent this one.", sent: "This one left the phone.", delivered: "This one reached theirs.", read: "This one they've read.", failed: "This one never got there." }.each do |state, text| %>
<%= message text, own: true, id: "message_status_#{state}" do |m| %>
<% m.status state, at: (@now - 1.minute if state == :read) %>
<% end %>
<% end %>
<% end %>
</div>Every part
A row with a meta line, a quoted reply, attachments, reactions, an edited mark, a footer of your own and actions. Parts take a builder when their block takes an argument.
Ben ReyesCan someone review the release notes before Friday?
On it. Comments are on the migration section; the rollback steps look right to me.
<div class="mx-auto max-w-2xl">
<%= message variant: :row, id: "message_parts_row", author: "Ana Silva", avatar: true, time: @now - 40.minutes, edited: true do |m| %>
<% m.meta badge("Admin", tone: :info) %>
<% m.quote "Can someone review the release notes before Friday?", author: "Ben Reyes", href: "#" %>
<% m.attachments { |files| files.file "review-notes.md", size: 3_120, url: "#" } %>
<% m.reactions { |r| r.reaction "π", count: 3, reacted: true, names: [ "Ben", "Chloe", "You" ], url: "#"; r.reaction "π", count: 1, url: "#"; r.add popovertarget: "message_parts_row_picker" } %>
<% m.footer { link_to "2 replies", "#", class: "font-medium text-neutral-700 hover:underline dark:text-neutral-300" } %>
<% m.actions(reveal: :always) do |bar| %>
<% bar.action "Reply", "#", icon: :reply %>
<% bar.copy "On it. Comments are on the migration section." %>
<% bar.action "Edit", "#", icon: :pencil %>
<% bar.action "Delete", "#", icon: :trash_2, method: :delete, confirm: "Delete this message?" %>
<% end %>
<p>On it. Comments are on the migration section; the rollback steps look right to me.</p>
<% end %>
<div id="message_parts_row_picker" popover class="rounded-lg border border-neutral-200 bg-white p-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-900">
<% %w[π π β€οΈ π π β
].each do |emoji| %>
<button type="button" class="rounded-md px-2 py-1 text-lg hover:bg-neutral-100 dark:hover:bg-neutral-800"><%= emoji %></button>
<% end %>
</div>
</div>Collapsed and open
collapsible: true folds an email into a details whose summary is the header and a line of the body; open: false starts it shut.
Thanks for the invitation. Friday works for me. Would 2pm suit, and should I prepare anything beforehand?
<div class="mx-auto max-w-2xl">
<%= message_thread do %>
<%= message variant: :email, author: "Ana Silva", avatar: true, time: @now - 3.days, collapsible: true, open: false, id: "message_email_1" do |m| %>
<% m.meta "to Keith Pitt" %>
<p>Thanks for the invitation. Friday works for me. Would 2pm suit, and should I prepare anything beforehand?</p>
<% end %>
<%= message variant: :email, author: "Keith Pitt", avatar: true, time: @now - 2.hours, collapsible: true, id: "message_email_2" do |m| %>
<% m.meta "to Ana Silva" %>
<% m.actions(reveal: :always) { |bar| bar.control { link_to "Reply", "#", class: button_classes(size: :small) } } %>
<p>2pm is great. Nothing to prepare: it's a conversation about the work you've enjoyed most.</p>
<% end %>
<% end %>
</div>