defmodule Shino.UI.Sheet do @moduledoc """ Provides sheet related components. > Displays content that complements the main content of the screen. ## Related components * `Shino.UI.Dialog` ## Availale components Components are divided into two categories: control components and style components. Control components: * `` * `` * `` * `` Style components: * `` * `` * `` * `` ## Opening and closing a sheet For opening a sheet, you can use: * `:default_open` attr of `` component * `` component For closing a sheet, you can use: * `` component * the builtin user interactions: * clicking outside of the sheet * pressing ESC key ## Controlling the side of a sheet Use `:side` attr of of `` component. ## Controlling the width of a sheet By default, the sheet width is determined by its content. This behaviour can be customized by applying classes to `` component. For example: ```heex <% # content %> ``` ## Examples ### A basic sheet ```heex <.button>Open Basic Sheet <.button variant="secondary">Close Content ``` This example shows the main components to build a basic sheet. You can extend this piece of code to whatever you want based on sheet. ### Using more style components ```heex <.button>Open Edit profile Make changes to your profile here. Click save when you're done. <% # example content %>
<.skeleton class="h-[125px] w-full rounded-xl" />
<.skeleton class="h-4 w-full" /> <.skeleton class="h-4 w-3/4" />
<.button>Cancel <.button>Save
``` ### A sheet as sidebar ```heex <.button>Open Sidebar Navigation ``` ## References * [shadcn/ui - Sheet](https://ui.shadcn.com/docs/components/sheet) """ use Shino.UI, :component defmodule Root do @moduledoc false defstruct [:id, :side] end @doc """ The root contains all the parts of a sheet. """ attr :id, :string, required: true attr :default_open, :boolean, default: false attr :side, :string, values: ["left", "right", "top", "bottom"], default: "left" attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def root(assigns) do ~H"""
<%= render_slot(@inner_block, %Root{id: @id, side: @side}) %>
""" end @doc """ Renders a sheet trigger. """ attr :for, Root, required: true attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def trigger(assigns) do ~H"""
<%= render_slot(@inner_block) %>
""" end @doc """ Renders a sheet content. """ attr :for, Root, required: true attr :class, :any, default: nil slot :inner_block, required: true def content(assigns) do ~H"""
""" end @doc """ Renders a sheet close. """ attr :for, Root, required: true attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def close(assigns) do ~H"""
<%= render_slot(@inner_block) %>
""" end @doc """ Renders a sheet header. """ attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def header(assigns) do ~H"""
<%= render_slot(@inner_block) %>
""" end @doc """ Renders a sheet title. """ attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def title(assigns) do ~H"""

<%= render_slot(@inner_block) %>

""" end @doc """ Renders a sheet description. """ attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def description(assigns) do ~H"""

<%= render_slot(@inner_block) %>

""" end @doc """ Renders a sheet footer. """ attr :class, :any, default: nil attr :rest, :global slot :inner_block, required: true def footer(assigns) do ~H"""
<%= render_slot(@inner_block) %>
""" end @side_classes %{ "left" => "flex-row justify-start", "right" => "flex-row justify-end", "top" => "flex-col justify-start", "bottom" => "flex-col justify-end" } defp side_class(side), do: Map.fetch!(@side_classes, side) defp show_sheet(js \\ %JS{}, id, side) do selector_content = "##{id}-content" selector_overlay = "##{id}-overlay" selector_sheet = "##{id}-sheet" transition_overlay = {"transition duration-300 ease-in-out", "opacity-0", "opacity-100"} transition_sheet = case side do "left" -> {"transition duration-300 ease-in-out", "-translate-x-full", "translate-x-0"} "right" -> {"transition duration-300 ease-in-out", "translate-x-full", "translate-x-0"} "top" -> {"transition duration-300 ease-in-out", "-translate-y-full", "translate-y-0"} "bottom" -> {"transition duration-300 ease-in-out", "translate-y-full", "translate-y-0"} end js |> JS.remove_class("hidden", to: selector_content) |> JS.transition(transition_overlay, to: selector_overlay, time: 300) |> JS.transition(transition_sheet, to: selector_sheet, time: 300) |> JS.add_class("overflow-hidden", to: "body") |> JS.focus_first(to: selector_sheet) end defp hide_sheet(js \\ %JS{}, id, side) do selector_content = "##{id}-content" selector_overlay = "##{id}-overlay" selector_sheet = "##{id}-sheet" transition_overlay = {"transition duration-300 ease-in-out", "opacity-100", "opacity-0"} transition_sheet = case side do "left" -> {"transition duration-300 ease-in-out", "translate-x-0", "-translate-x-full"} "right" -> {"transition duration-300 ease-in-out", "translate-x-0", "translate-x-full"} "top" -> {"transition duration-300 ease-in-out", "translate-y-0", "-translate-y-full"} "bottom" -> {"transition duration-300 ease-in-out", "translate-y-0", "translate-y-full"} end js |> JS.add_class("hidden", to: selector_content, transition: {"duration-300", "", ""}, time: 300 ) |> JS.transition(transition_overlay, to: selector_overlay, time: 300) |> JS.transition(transition_sheet, to: selector_sheet, time: 300) |> JS.remove_class("overflow-hidden", to: "body") |> JS.pop_focus() end end