svg preview renderer

This commit is contained in:
zack 2024-10-26 21:41:22 -04:00
parent 43a8412f06
commit faa9599849
No known key found for this signature in database
GPG key ID: 5F873416BCF59F35
29 changed files with 1027 additions and 254 deletions

View file

@ -1,6 +1,7 @@
defmodule ZoeyscomputerWeb.CodeBlock do
use Phoenix.Component
alias Phoenix.LiveView.JS
import ZoeyscomputerWeb.CoreComponents
@moduledoc """
A code block component with syntax highlighting using Shiki.
@ -37,6 +38,7 @@ defmodule ZoeyscomputerWeb.CodeBlock do
attr :code, :string, required: true
attr :language, :string, required: true
attr :title, :string, default: nil
attr :class, :string, default: ""
attr :line_numbers, :boolean, default: false
attr :highlighted_lines, :list, default: []
@ -44,48 +46,46 @@ defmodule ZoeyscomputerWeb.CodeBlock do
# Calculate the number of lines for line numbers
assigns = assign(assigns, :num_lines, String.split(assigns.code, "\n") |> length())
id = System.unique_integer()
~H"""
<div class="relative ctp-bg-base rounded-lg overflow-hidden">
<div class={"relative rounded-lg overflow-hidden w-full bg-ctp-mantle border border-ctp-surface0 #{@class} "}>
<%= if @title do %>
<div class="ctp-bg-mantle px-4 py-2 border-b ctp-border-surface0">
<h3 class="ctp-text-text text-sm font-medium"><%= @title %></h3>
<div class="bg-ctp-crust px-4 py-2 border-b border-ctp-surface0">
<h3 class="ctp-text-text text-[0.75rem] font-medium"><%= @title %></h3>
</div>
<% end %>
<div class="relative">
<%= if @line_numbers do %>
<div class="absolute left-0 top-0 bottom-0 ctp-bg-crust w-12 flex flex-col items-end pr-2 py-4 ctp-text-surface2 select-none">
<%= for line_num <- 1..@num_lines do %>
<span class={[
"text-sm leading-6",
line_num in @highlighted_lines && "ctp-text-mauve font-medium"
]}>
<%= line_num %>
</span>
<% end %>
</div>
<% end %>
<div class="overflow-x-scroll bg-ctp-mantle">
<div class="relative bg-ctp-crust">
<%= if @line_numbers do %>
<div class="absolute bg-ctp-crust left-0 top-0 bottom-0 ctp-bg-crust min-w-12 flex flex-col items-end py-4 ctp-text-surface2 select-none">
<%= for line_num <- 1..@num_lines do %>
<span class={[
"text-sm leading-6 pr-2",
line_num in @highlighted_lines &&
"text-ctp-mauve bg-ctp-mauve/15 w-full text-right font-bold"
]}>
<%= line_num %>
</span>
<% end %>
</div>
<% end %>
<div class={["overflow-x-auto", @line_numbers && "pl-12"]}>
<pre
class="p-4"
id={"code-block-#{System.unique_integer()}"}
phx-hook="CodeBlockHook"
data-code={@code}
data-language={@language}
data-highlighted-lines={Jason.encode!(@highlighted_lines)}
><code class="text-sm"><%= @code %></code></pre>
<div class={["bg-ctp-mantle overflow-x-scroll pb-0.5", @line_numbers && "pl-12"]}>
<pre
class="p-4 leading-6 text-sm mb-4"
id={"code-block-#{id}"}
phx-hook="CodeBlockHook"
data-code={@code}
data-language={@language}
data-highlighted-lines={Jason.encode!(@highlighted_lines)}
><code class="text-sm"><%= @code %></code></pre>
</div>
</div>
</div>
<div class="ctp-bg-mantle px-4 py-2 border-t ctp-border-surface0 flex justify-end">
<button
type="button"
class="ctp-text-subtext0 hover:ctp-text-text text-sm"
phx-click={JS.dispatch("clipcopy", detail: %{text: @code})}
>
Copy code
</button>
<div class="bg-ctp-mantle px-4 py-2 border-t border-ctp-surface0 flex justify-end">
<.copy_button id="code-copy-btn" content={"code-block-#{id}"} />
</div>
</div>
"""

View file

@ -235,8 +235,9 @@ defmodule ZoeyscomputerWeb.CoreComponents do
<button
type={@type}
class={[
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3",
"text-sm font-semibold leading-6 text-white active:text-white/80",
"phx-submit-loading:opacity-75 rounded-lg bg-ctp-mauve text-ctp-crust hover:brightness-125 py-2 px-3",
"text-sm font-semibold leading-6",
"transition-all duration-150 ease-in",
@class
]}
{@rest}
@ -359,7 +360,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
id={@id}
name={@name}
class={[
"mt-2 block w-full rounded-lg text-ctp-overlay2 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
"mt-2 block w-full bg-ctp-base rounded-lg text-ctp-text focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
@errors == [] && "border-ctp-surface2 focus:border-zinc-400",
@errors != [] && "border-rose-400 focus:border-rose-400"
]}
@ -400,7 +401,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
def label(assigns) do
~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-ctp-text">
<label for={@for} class="block text-sm font-semibold leading-6 text-ctp-overlay1">
<%= render_slot(@inner_block) %>
</label>
"""
@ -458,7 +459,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
content={@content}
phx-click={JS.dispatch("phx:copy", to: "##{@content}")}
type="button"
class="rounded-md inline-flex items-center bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
class="rounded-md inline-flex items-center bg-ctp-blue px-2.5 py-1.5 text-sm font-semibold text-ctp-crust shadow-sm shadow-ctp-blue/25 ring-ctp-blue ring-1 ring-inset transition-all duration-150 ease-in hover:brightness-125"
>
Copy
</button>

View file

@ -1,5 +1,5 @@
<main class="bg-ctp-base px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<div class="mx-auto lg:max-w-7xl">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>

View file

@ -1,81 +0,0 @@
defmodule ZoeyscomputerWeb.SearchableDropdown do
use Phoenix.Component
alias Phoenix.LiveView.JS
attr :id, :string, required: true
attr :options, :list, required: true
attr :selected, :string, default: nil
attr :class, :string, default: nil
attr :name, :string, required: true
attr :form, :any, required: true
def searchable_dropdown(assigns) do
~H"""
<div class={["relative w-full", @class]} id={"#{@id}-container"}>
<div class="relative" phx-click-away={JS.hide(to: "##{@id}-dropdown", transition: "fade-out")}>
<input type="hidden" name={@name} value={@selected} id={"#{@id}-input"} />
<button
type="button"
class="flex w-full items-center justify-between rounded-md border border-ctp-surface0 bg-ctp-base px-3 py-2 text-sm text-ctp-text shadow-sm hover:bg-ctp-surface0 focus:outline-none focus:ring-2 focus:ring-ctp-lavender transition-colors duration-200"
phx-click={
JS.toggle(to: "##{@id}-dropdown", in: "fade-in", out: "fade-out")
|> JS.focus(to: "##{@id}-search")
}
aria-haspopup="listbox"
aria-expanded="false"
>
<span class="block truncate">
<%= @selected || "Select an option..." %>
</span>
<svg
class="h-5 w-5 text-ctp-overlay0 transform transition-transform duration-200"
class={"#{if @selected, do: "rotate-180", else: ""}"}
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3z"
clip-rule="evenodd"
/>
</svg>
</button>
<div
id={"#{@id}-dropdown"}
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border border-ctp-surface0 bg-ctp-base shadow-lg hidden transition-all duration-200 ease-in-out"
phx-hook="DropdownAnimation"
>
<div class="p-2">
<input
type="text"
id={"#{@id}-search"}
placeholder="Search..."
class="w-full rounded-md border border-ctp-surface0 bg-ctp-mantle px-3 py-2 text-sm text-ctp-text placeholder-ctp-overlay0 focus:outline-none focus:ring-2 focus:ring-ctp-lavender transition-colors duration-200"
phx-hook="SearchableDropdown"
data-dropdown-id={@id}
data-form-id={@form.id}
/>
</div>
<ul class="max-h-48 overflow-y-auto py-1" role="listbox" id={"#{@id}-options"}>
<li :for={option <- @options} class="transition-colors duration-150 ease-in-out">
<button
type="button"
class="w-full px-3 py-2 text-left text-sm hover:bg-ctp-surface0 focus:bg-ctp-surface0 focus:outline-none cursor-pointer transition-colors duration-150 ease-in-out"
phx-click={
JS.push("select_language", value: %{language: option}, target: "##{@form.id}")
|> JS.hide(to: "##{@id}-dropdown", transition: "fade-out")
}
phx-target={"##{@form.id}"}
role="option"
data-option={option}
>
<%= option %>
</button>
</li>
</ul>
</div>
</div>
</div>
"""
end
end