Current section

Files

Jump to
phoenix_storybook lib phoenix_storybook live search.ex
Raw

lib/phoenix_storybook/live/search.ex

defmodule PhoenixStorybook.Search do
@moduledoc false
use PhoenixStorybook.Web, :live_component
alias Phoenix.LiveView.JS
alias PhoenixStorybook.LayoutView
alias PhoenixStorybook.SearchHelpers
def mount(socket) do
{:ok, socket}
end
def update(assigns = %{root_path: _, backend_module: backend_module}, socket) do
stories = backend_module.leaves()
{:ok,
socket
|> assign(assigns)
|> assign(:show, false)
|> assign(:all_stories, stories)
|> assign(:stories, stories)
|> assign(:fa_plan, backend_module.config(:font_awesome_plan, :free))}
end
def handle_event("navigate", %{"path" => path}, socket) do
{:noreply, push_patch(socket, to: path)}
end
def handle_event("search", %{"search" => %{"input" => ""}}, socket) do
{:noreply, assign(socket, :stories, socket.assigns.all_stories)}
end
def handle_event("search", %{"search" => %{"input" => input}}, socket) do
stories = SearchHelpers.search_by(input, socket.assigns.all_stories, [:path, :name])
{:noreply, assign(socket, :stories, stories)}
end
def render(assigns) do
~H"""
<div
id="search-container"
phx-hook="SearchHook"
phx-show={show_container()}
phx-hide={hide_container()}
class="psb psb:hidden psb:opacity-0 psb:relative psb:z-10 psb:transition-all"
>
<div class="psb psb:fixed psb:inset-0 psb:backdrop-blur psb:bg-gray-500 psb:bg-opacity-25">
</div>
<div class="psb psb:fixed psb:inset-0 psb:z-10 psb:overflow-y-auto psb:p-4 psb:sm:p-6 psb:md:p-20">
<div
id="search-modal"
phx-show={show_modal()}
phx-hide={hide_modal()}
phx-click-away={JS.dispatch("psb:close-search")}
phx-window-keydown={JS.exec("phx-click-away")}
phx-key="Escape"
class="psb psb:opacity-0 psb:scale-90 psb:mx-auto psb:max-w-xl psb:mt-16 psb:transform psb:divide-y psb:divide-gray-100 psb:dark:divide-slate-600 psb:overflow-hidden psb:rounded-xl psb:bg-white psb:dark:bg-slate-800 psb:shadow-2xl psb:transition-all"
>
<.form
:let={f}
for={%{}}
as={:search}
phx-debounce={500}
id="search-form"
class="psb psb:relative"
>
<.fa_icon
style={:light}
name="search"
plan={@fa_plan}
class="psb:pointer-events-none psb:absolute psb:top-3.5 psb:left-4 psb:h-5 psb:w-5 psb:text-gray-400"
/>
{text_input(f, :input,
id: "search-input",
"phx-change": "search",
"phx-target": @myself,
placeholder: "Search...",
autocomplete: "off",
class:
"psb psb:h-12 psb:w-full psb:border-0 psb:bg-transparent psb:pl-11 psb:pr-4 psb:text-gray-800 psb:dark:text-slate-300 psb:placeholder-gray-400 psb:dark:placeholder-slate-500 psb:outline-none psb:focus:ring-0 psb:sm:text-sm"
)}
</.form>
<%= if Enum.empty?(@stories) do %>
<div class="psb psb:text-center psb:text-gray-600 psb:dark:text-slate-300 psb:py-4">
<p>No stories found</p>
</div>
<% end %>
<ul
id="search-list"
class="psb psb:max-h-72 psb:scroll-py-2 psb:divide-y psb:divide-gray-200 psb:dark:divide-slate-600 psb:overflow-y-auto psb:pb-2 psb:text-sm psb:text-gray-800"
>
<%= for {story, i} <- Enum.with_index(@stories) do %>
<li
id={"story-#{i}"}
phx-highlight={
JS.add_class(
"psb:bg-slate-50 psb:dark:bg-slate-700 psb:text-indigo-600 psb:dark:text-sky-400"
)
}
phx-baseline={
JS.remove_class(
"psb:bg-slate-50 psb:dark:bg-slate-700 psb:text-indigo-600 psb:dark:text-sky-400"
)
}
class="psb psb:flex psb:justify-between psb:group psb:select-none psb:px-4 psb:py-4 psb:space-x-4 psb:cursor-pointer psb:hover:bg-slate-50 psb:dark:hover:bg-slate-700"
tabindex="-1"
phx-click={JS.navigate(Path.join(@root_path, story.path))}
>
<.link
patch={Path.join(@root_path, story.path)}
class="psb psb:font-semibold psb:whitespace-nowrap psb:dark:text-slate-300"
>
{story.name}
</.link>
<div class="psb psb:truncate">
{LayoutView.render_breadcrumb(@socket, story.path,
span_class: "psb:text-xs psb:dark:text-slate-300"
)}
</div>
</li>
<% end %>
</ul>
</div>
</div>
</div>
"""
end
defp show_container(js \\ %JS{}) do
JS.show(
js,
transition: {"psb:ease-out psb:duration-300", "psb:opacity-0", "psb:opacity-100"},
to: "#search-container"
)
end
defp hide_container(js \\ %JS{}) do
JS.hide(
js,
transition: {"psb:ease-in psb:duration-200", "psb:opacity-100", "psb:opacity-0"},
to: "#search-container"
)
end
defp show_modal(js \\ %JS{}) do
JS.transition(
js,
{"psb:ease-out psb:duration-300", "psb:opacity-0 psb:scale-90",
"psb:opacity-100 psb:scale-100"},
to: "#search-modal"
)
end
defp hide_modal(js \\ %JS{}) do
JS.transition(
js,
{"psb:ease-in psb:duration-200", "psb:opacity-100 psb:scale-100",
"psb:opacity-0 psb:scale-90"},
to: "#search-modal"
)
end
end