diff --git a/lib/petal_components.ex b/lib/petal_components.ex
index e02633a8..d185e8e8 100644
--- a/lib/petal_components.ex
+++ b/lib/petal_components.ex
@@ -10,6 +10,7 @@ defmodule PetalComponents do
Button,
ButtonGroup,
Card,
+ ChatBubble,
Container,
Dropdown,
Field,
diff --git a/lib/petal_components/chat_bubble.ex b/lib/petal_components/chat_bubble.ex
new file mode 100644
index 00000000..d4a7f77f
--- /dev/null
+++ b/lib/petal_components/chat_bubble.ex
@@ -0,0 +1,1123 @@
+defmodule PetalComponents.ChatBubble do
+ use Phoenix.Component
+
+ # Base chat bubble component
+ attr(:author, :string, default: "Sarah Hill", doc: "author name for the chat message")
+ attr(:message, :string, default: "That's awesome. I think our users will really appreciate the improvements.", doc: "main message content")
+ attr(:time, :string, default: "10:13", doc: "timestamp for the message")
+ attr(:avatar_src, :string, default: "https://res.cloudinary.com/wickedsites/image/upload/v1604268092/unnamed_sagz0l.jpg", doc: "hosted avatar URL")
+ attr(:avatar_alt, :string, default: nil, doc: "alt text for avatar image")
+ attr(:kind, :atom, default: :default_chat_bubble,
+ values: [
+ :default_chat_bubble, :voicenote, :file_attachment,
+ :image_attachment, :image_gallery, :url_preview_sharing,
+ :outline_chat_bubble, :outline_voicenote, :outline_file_attachment,
+ :outline_image_attachment, :outline_image_gallery, :outline_url_preview_sharing,
+ :clean_chat_bubble, :clean_voicenote, :clean_file_attachment,
+ :clean_image_attachment, :clean_image_gallery, :clean_url_preview_sharing
+ ],
+ doc: "determines the type of chat bubble to render"
+ )
+ attr(:class, :any, default: nil, doc: "additional css classes")
+ attr(:rest, :global)
+
+ # File attributes
+ attr(:file_name, :string, default: "Petal Components Terms & Conditions", doc: "name of the attached file")
+ attr(:file_size, :string, default: "18 MB", doc: "size of the attached file")
+ attr(:file_pages, :string, default: "12 Pages", doc: "number of pages in document")
+ attr(:file_type, :string, default: "PDF", doc: "type of file")
+
+ # Voice note attributes
+ attr(:duration, :string, default: "3:42", doc: "duration of voice note")
+
+ # Image attributes
+ attr(:image_src, :string, default: "https://images.unsplash.com/photo-1562664377-709f2c337eb2", doc: "URL of the attached image")
+ attr(:image_alt, :string, default: "Image attachment", doc: "alt text for attached image")
+
+ # Gallery attributes
+ attr(:images, :list,
+ default: [
+ "https://images.unsplash.com/photo-1552664730-d307ca884978",
+ "https://images.unsplash.com/photo-1551434678-e076c223a692",
+ "https://images.unsplash.com/photo-1562664377-709f2c337eb2",
+ "https://petal.build/images/logo_dark.svg"
+ ],
+ doc: "list of gallery image URLs"
+ )
+ attr(:extra_images_count, :integer, default: 7, doc: "number of additional images not shown")
+
+ # URL Preview attributes
+ attr(:url, :string, default: "https://petal.build/components", doc: "URL to preview")
+ attr(:url_title, :string, default: "Welcome to Petal Components", doc: "title of the URL preview")
+ attr(:url_description, :string, default: "A versatile set of beautifully styled components", doc: "description for the URL preview")
+ attr(:url_image, :string, default: "https://petal.build/images/favicon.png", doc: "image for the URL preview")
+ attr(:url_domain, :string, default: "github.com", doc: "domain name for the URL")
+
+ def chat_bubble(assigns) do
+ case assigns.kind do
+ :default_chat_bubble -> render_default_chat_bubble(assigns)
+ :voicenote -> render_voicenote(assigns)
+ :file_attachment -> render_file_attachment(assigns)
+ :image_attachment -> render_image_attachment(assigns)
+ :image_gallery -> render_image_gallery(assigns)
+ :url_preview_sharing -> render_url_preview_sharing(assigns)
+
+ # Outline variants
+ :outline_chat_bubble -> render_outline_chat_bubble(assigns)
+ :outline_voicenote -> render_outline_voicenote(assigns)
+ :outline_file_attachment -> render_outline_file_attachment(assigns)
+ :outline_image_attachment -> render_outline_image_attachment(assigns)
+ :outline_image_gallery -> render_outline_image_gallery(assigns)
+ :outline_url_preview_sharing -> render_outline_url_preview_sharing(assigns)
+
+ # Clean variants
+ :clean_chat_bubble -> render_clean_chat_bubble(assigns)
+ :clean_voicenote -> render_clean_voicenote(assigns)
+ :clean_file_attachment -> render_clean_file_attachment(assigns)
+ :clean_image_attachment -> render_clean_image_attachment(assigns)
+ :clean_image_gallery -> render_clean_image_gallery(assigns)
+ :clean_url_preview_sharing -> render_clean_url_preview_sharing(assigns)
+
+ # Default fallback
+ _ -> render_default_chat_bubble(assigns)
+ end
+ end
+
+ defp render_header(assigns) do
+ ~H"""
+
+ <%= @author %>
+ <%= @time %>
+
+ """
+end
+
+defp render_default_chat_bubble(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
<%= @message %>
+
Delivered
+
+
+
+ """
+end
+
+defp render_waveform(assigns) do
+ ~H"""
+
+
+
+
<%= assigns[:duration] %>
+
+ """
+end
+
+defp render_voicenote(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:class, assigns[:class])
+ |> assign(:duration, assigns[:duration])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+ <%= render_waveform(assigns) %>
+ Delivered
+
+
+
+ """
+end
+
+defp render_file_attachment(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:class, assigns[:class])
+ |> assign(:file_name, assigns[:file_name])
+ |> assign(:file_size, assigns[:file_size])
+ |> assign(:file_pages, assigns[:file_pages])
+ |> assign(:file_type, assigns[:file_type])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+
+
+ <%= @author %>
+ <%= @time %>
+
+
+
+
+
+ <%= @file_name %>
+
+
+ <%= @file_pages %>
+
+ <%= @file_size %>
+
+ <%= @file_type %>
+
+
+
+
+
+
+
+ Download file
+
+
+
+
Delivered
+
+
+
+
+ """
+end
+
+defp render_image_attachment(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:image_src, assigns[:image_src])
+ |> assign(:image_alt, assigns[:image_alt])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+
+
+ <%= @author %>
+ <%= @time %>
+
+
<%= @message %>
+
+
+
+
+
+
+ Download image
+
+
+

+
+
Delivered
+
+
+
+
+ """
+end
+
+defp render_image_gallery(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:images, assigns[:images] || [])
+ |> assign(:extra_images_count, assigns[:extra_images_count] || 0)
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+
+
+ <%= @author %>
+ <%= @time %>
+
+
<%= @message %>
+
+
+
+
+ <%= for {img, _index} <- Enum.with_index(Enum.take(@images, 3)) do %>
+
+
+
+
+
+ Download image
+
+
+

+
+ <% end %>
+
+
+
+
+
+ View more
+
+ <%= if length(@images) > 3 do %>
+

+ <% else %>
+
+ <% end %>
+
+
+
+
+
Delivered
+
+
+
+
+
+
+ """
+end
+
+defp render_url_preview_sharing(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:url, assigns[:url])
+ |> assign(:url_title, assigns[:url_title])
+ |> assign(:url_description, assigns[:url_description])
+ |> assign(:url_image, assigns[:url_image])
+ |> assign(:url_domain, assigns[:url_domain])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+
+
+ """
+end
+
+defp render_outline_chat_bubble(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_outline_voicenote(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
+ <%= render_waveform(assigns) %>
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_outline_file_attachment(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:file_name, assigns[:file_name])
+ |> assign(:file_size, assigns[:file_size])
+ |> assign(:file_pages, assigns[:file_pages])
+ |> assign(:file_type, assigns[:file_type])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
+
+
+
+
+ <%= @file_name %>
+
+
+ <%= @file_pages %>
+
+ <%= @file_size %>
+
+ <%= @file_type %>
+
+
+
+
+
+
+ Download file
+
+
+
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_outline_image_attachment(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:image_src, assigns[:image_src])
+ |> assign(:image_alt, assigns[:image_alt])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
<%= @message %>
+
+
+
+
+
+ Download image
+
+
+

+
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_outline_image_gallery(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:images, assigns[:images] || [])
+ |> assign(:extra_images_count, assigns[:extra_images_count] || 0)
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
<%= @message %>
+
+
+
+ <%= for {img, _index} <- Enum.with_index(Enum.take(@images, 3)) do %>
+
+
+
+
+ Download image
+
+
+

+
+ <% end %>
+
+
+
+
+ View more
+
+ <%= if length(@images) > 3 do %>
+

+ <% else %>
+
+ <% end %>
+
+
+
+
+
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_outline_url_preview_sharing(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:url, assigns[:url])
+ |> assign(:url_title, assigns[:url_title])
+ |> assign(:url_description, assigns[:url_description])
+ |> assign(:url_image, assigns[:url_image])
+ |> assign(:url_domain, assigns[:url_domain])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
<%= @message %>
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_clean_chat_bubble(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
<%= @message %>
+
Delivered
+
+
+
+ """
+end
+
+defp render_clean_voicenote(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+ <%= render_waveform(assigns) %>
+ Delivered
+
+
+
+ """
+end
+
+defp render_clean_file_attachment(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:file_name, assigns[:file_name])
+ |> assign(:file_size, assigns[:file_size])
+ |> assign(:file_pages, assigns[:file_pages])
+ |> assign(:file_type, assigns[:file_type])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
+
+
+
+ <%= @file_name %>
+
+
+ <%= @file_pages %>
+
+ <%= @file_size %>
+
+ <%= @file_type %>
+
+
+
+
+
+ Download file
+
+
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_clean_image_attachment(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:image_src, assigns[:image_src])
+ |> assign(:image_alt, assigns[:image_alt])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
+
+
+
+
+
+
+ Download image
+
+
+

+
+
+
Delivered
+
+
+
+ """
+end
+
+defp render_clean_image_gallery(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:images, assigns[:images] || [])
+ |> assign(:extra_images_count, assigns[:extra_images_count] || 0)
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+ <%= render_header(assigns) %>
+
<%= @message %>
+
+
+
+
+ <%= for {img, _index} <- Enum.with_index(Enum.take(@images, 3)) do %>
+
+
+
+
+
+ Download image
+
+
+

+
+ <% end %>
+
+
+
+
+
+ View more
+
+ <%= if length(@images) > 3 do %>
+

+ <% else %>
+
+ <% end %>
+
+
+
+
Delivered
+
+
+
+
+
+
+
+ """
+end
+
+defp render_clean_url_preview_sharing(assigns) do
+ assigns =
+ assigns
+ |> assign(:author, assigns[:author])
+ |> assign(:message, assigns[:message])
+ |> assign(:time, assigns[:time])
+ |> assign(:avatar_src, assigns[:avatar_src])
+ |> assign(:avatar_alt, assigns[:avatar_alt] || "#{assigns[:author]} image")
+ |> assign(:url, assigns[:url])
+ |> assign(:url_title, assigns[:url_title])
+ |> assign(:url_description, assigns[:url_description])
+ |> assign(:url_image, assigns[:url_image])
+ |> assign(:url_domain, assigns[:url_domain])
+ |> assign(:class, assigns[:class])
+ |> assign(:rest, assigns[:rest])
+
+ ~H"""
+
+
+

+
+
+
+ """
+end
+
+end
diff --git a/test/petal/chat_bubble_test.exs b/test/petal/chat_bubble_test.exs
new file mode 100644
index 00000000..e0a05850
--- /dev/null
+++ b/test/petal/chat_bubble_test.exs
@@ -0,0 +1,426 @@
+defmodule PetalComponents.ChatBubbleTest do
+ use ComponentCase
+ import PetalComponents.ChatBubble
+
+test "default chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ author="Test User"
+ message="Hello world"
+ time="12:45"
+ />
+ """)
+
+ assert html =~ "Test User"
+ assert html =~ "Hello world"
+ assert html =~ "12:45"
+ assert html =~ "Delivered"
+ assert html =~ "pc-chat-bubble--default"
+end
+
+test "voice note chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:voicenote}
+ author="Voice User"
+ time="15:20"
+ duration="2:15"
+ />
+ """)
+
+ assert html =~ "Voice User"
+ assert html =~ "15:20"
+ assert html =~ "2:15"
+ assert html =~ "pc-chat-bubble--voice-note"
+ assert html =~ ~s(
+ """)
+
+ assert html =~ "File User"
+ assert html =~ "16:45"
+ assert html =~ "Petal Components Terms & Conditions"
+ assert html =~ "18 MB"
+ assert html =~ "12 Pages"
+ assert html =~ "PDF"
+ assert html =~ "pc-chat-bubble--file-attachment"
+ assert html =~ "Download file"
+end
+
+test "image attachment chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:image_attachment}
+ author="Image User"
+ time="17:00"
+ image_src="https://example.com/image.jpg"
+ image_alt="Example image"
+ />
+ """)
+
+ assert html =~ "Image User"
+ assert html =~ "17:00"
+ assert html =~ ~s(src="https://example.com/image.jpg")
+ assert html =~ ~s(alt="Example image")
+ assert html =~ "pc-chat-bubble--image-attachment"
+end
+
+test "image gallery chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:image_gallery}
+ author="Gallery User"
+ time="18:00"
+ images={[
+ "https://example.com/image1.jpg",
+ "https://example.com/image2.jpg",
+ "https://example.com/image3.jpg",
+ "https://example.com/image4.jpg"
+ ]}
+ extra_images_count={2}
+ />
+ """)
+
+ assert html =~ "Gallery User"
+ assert html =~ "18:00"
+ assert html =~ ~s(src="https://example.com/image1.jpg")
+ assert html =~ ~s(src="https://example.com/image2.jpg")
+ assert html =~ ~s(src="https://example.com/image3.jpg")
+ assert html =~ ~s(src="https://example.com/image4.jpg")
+ assert html =~ "pc-chat-bubble--image-gallery"
+end
+
+test "URL preview sharing chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:url_preview_sharing}
+ author="URL User"
+ time="19:00"
+ url="https://example.com"
+ url_title="Example Title"
+ url_description="Example Description"
+ url_image="https://example.com/image.jpg"
+ url_domain="example.com"
+ />
+ """)
+
+ assert html =~ "URL User"
+ assert html =~ "19:00"
+ assert html =~ ~s(href="https://example.com")
+ assert html =~ "Example Title"
+ assert html =~ "Example Description"
+ assert html =~ ~s(src="https://example.com/image.jpg")
+ assert html =~ "example.com"
+ assert html =~ "pc-chat-bubble--url-preview"
+end
+
+test "outline chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:outline_chat_bubble}
+ author="Outline User"
+ time="20:00"
+ message="Outline message"
+ />
+ """)
+
+ assert html =~ "Outline User"
+ assert html =~ "20:00"
+ assert html =~ "Outline message"
+ assert html =~ "Delivered"
+ assert html =~ "pc-chat-bubble--outline-chat-bubble"
+end
+
+test "outline voicenote chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:outline_voicenote}
+ author="Outline Voice User"
+ time="23:00"
+ duration="3:00"
+ />
+ """)
+
+ assert html =~ "Outline Voice User"
+ assert html =~ "23:00"
+ assert html =~ "3:00"
+ assert html =~ "pc-chat-bubble--outline-voicenote"
+ assert html =~ ~s(
+ """)
+
+ assert html =~ "Outline File User"
+ assert html =~ "00:00"
+ assert html =~ "Outline File.pdf"
+ assert html =~ "10 MB"
+ assert html =~ "5 Pages"
+ assert html =~ "PDF"
+ assert html =~ "pc-chat-bubble--outline-file-attachment"
+ assert html =~ "Download file"
+end
+
+test "outline image attachment chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:outline_image_attachment}
+ author="Outline Image User"
+ time="01:00"
+ image_src="https://example.com/outline_image.jpg"
+ image_alt="Outline Example Image"
+ />
+ """)
+
+ assert html =~ "Outline Image User"
+ assert html =~ "01:00"
+ assert html =~ ~s(src="https://example.com/outline_image.jpg")
+ assert html =~ ~s(alt="Outline Example Image")
+ assert html =~ "pc-chat-bubble--outline-image-attachment"
+end
+
+test "outline image gallery chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:outline_image_gallery}
+ author="Outline Gallery User"
+ time="01:00"
+ images={[
+ "https://example.com/image1.jpg",
+ "https://example.com/image2.jpg",
+ "https://example.com/image3.jpg"
+ ]}
+ extra_images_count={5}
+ />
+ """)
+
+ assert html =~ "Outline Gallery User"
+ assert html =~ "01:00"
+ assert html =~ "pc-chat-bubble--outline-image-gallery"
+ assert html =~ ~s(src="https://example.com/image1.jpg")
+ assert html =~ ~s(src="https://example.com/image2.jpg")
+ assert html =~ ~s(src="https://example.com/image3.jpg")
+ assert html =~ "+5"
+end
+
+test "outline URL preview sharing chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:outline_url_preview_sharing}
+ author="Outline URL User"
+ time="22:00"
+ url="https://example.com"
+ url_title="Outline Example Title"
+ url_description="Outline Example Description"
+ url_image="https://example.com/image.jpg"
+ url_domain="example.com"
+ />
+ """)
+
+ assert html =~ "Outline URL User"
+ assert html =~ "22:00"
+ assert html =~ ~s(href="https://example.com")
+ assert html =~ "Outline Example Title"
+ assert html =~ "Outline Example Description"
+ assert html =~ ~s(src="https://example.com/image.jpg")
+ assert html =~ "example.com"
+ assert html =~ "pc-chat-bubble--outline-url-preview"
+end
+
+test "clean chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:clean_chat_bubble}
+ author="Clean User"
+ time="21:00"
+ message="Clean message"
+ />
+ """)
+
+ assert html =~ "Clean User"
+ assert html =~ "21:00"
+ assert html =~ "Clean message"
+ assert html =~ "Delivered"
+ assert html =~ "pc-chat-bubble--clean-chat-bubble"
+end
+
+test "clean voicenote chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:clean_voicenote}
+ author="Clean Voice User"
+ time="23:00"
+ duration="3:00"
+ />
+ """)
+
+ assert html =~ "Clean Voice User"
+ assert html =~ "23:00"
+ assert html =~ "3:00"
+ assert html =~ "pc-chat-bubble--clean-voicenote"
+ assert html =~ ~s(
+ """)
+
+ assert html =~ "Clean File User"
+ assert html =~ "00:00"
+ assert html =~ "Clean File.pdf"
+ assert html =~ "10 MB"
+ assert html =~ "5 Pages"
+ assert html =~ "PDF"
+ assert html =~ "pc-chat-bubble--clean-file-attachment"
+ assert html =~ "Download file"
+end
+
+test "clean image attachment chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:clean_image_attachment}
+ author="Clean Image User"
+ time="02:00"
+ image_src="https://example.com/clean_image.jpg"
+ image_alt="Clean Example Image"
+ />
+ """)
+
+ assert html =~ "Clean Image User"
+ assert html =~ "02:00"
+ assert html =~ ~s(src="https://example.com/clean_image.jpg")
+ assert html =~ ~s(alt="Clean Example Image")
+ assert html =~ "pc-chat-bubble--clean-image-attachment"
+end
+
+test "clean image gallery chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:clean_image_gallery}
+ author="Clean Gallery User"
+ time="03:00"
+ images={[
+ "https://example.com/image1.jpg",
+ "https://example.com/image2.jpg",
+ "https://example.com/image3.jpg"
+ ]}
+ extra_images_count={4}
+ />
+ """)
+
+ assert html =~ "Clean Gallery User"
+ assert html =~ "03:00"
+ assert html =~ "pc-chat-bubble--clean-image-gallery"
+ assert html =~ ~s(src="https://example.com/image1.jpg")
+ assert html =~ ~s(src="https://example.com/image2.jpg")
+ assert html =~ ~s(src="https://example.com/image3.jpg")
+ assert html =~ "+4"
+end
+
+test "clean URL preview sharing chat bubble" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ <.chat_bubble
+ kind={:clean_url_preview_sharing}
+ author="Clean URL User"
+ time="02:00"
+ url="https://example.com"
+ url_title="Clean Example Title"
+ url_description="Clean Example Description"
+ url_image="https://example.com/image.jpg"
+ url_domain="example.com"
+ />
+ """)
+
+ assert html =~ "Clean URL User"
+ assert html =~ "02:00"
+ assert html =~ ~s(href="https://example.com")
+ assert html =~ "Clean Example Title"
+ assert html =~ "Clean Example Description"
+ assert html =~ ~s(src="https://example.com/image.jpg")
+ assert html =~ "example.com"
+ assert html =~ "pc-chat-bubble--clean-url-preview"
+end
+end
\ No newline at end of file