Current section
Files
Jump to
Current section
Files
lib/components/buttons.ex
defmodule SigmaKit.Components.Buttons do
use Phoenix.LiveComponent
import SigmaKit.Components.Icons
import SigmaKit.Components.Popups, only: [popover: 1]
@doc """
Renders a simple button component.
"""
attr :class, :any, default: nil, doc: "Additional CSS classes to apply to the button."
attr :primary, :boolean, default: false, doc: "Apply the primary style to the button."
attr :dark, :boolean, default: false, doc: "Apply a dark style to the button."
attr :light, :boolean, default: false, doc: "Apply the light style to the button."
attr :danger, :boolean, default: false, doc: "Apply the danger style to the button."
attr :inline, :boolean, default: false, doc: "Apply the inline style to the button."
attr :plain, :boolean, default: false, doc: "Apply no border or shadow to the button."
attr :rest, :global,
include: ~w(type disabled form name value),
doc: "Additional attributes to apply to the button."
slot :inner_block, required: true, doc: "The content to render inside the button."
def button(assigns) do
~H"""
<button
class={[
"phx-submit-loading:opacity-75 rounded transition-all",
"text-sm font-semibold leading-6 active:shadow-inner text-nowrap",
"disabled:text-zinc-50/50",
!@inline && "py-2 px-3",
!@primary && !@light && !@danger && !@dark &&
"bg-white text-zinc-600 disabled:text-zinc-500/50",
!@primary && !@light && !@danger && !@dark && !@plain &&
"border border-gray-200 border-gray-200 hover:border-gray-300 hover:bg-gray-50 hover:text-zinc-800",
@plain && "hover:bg-gray-200 hover:text-zinc-900",
@dark && "bg-zinc-900 hover:bg-zinc-700 text-white",
@primary && "bg-primary-600 hover:bg-primary-800 text-white",
@light && "bg-gray-200 hover:bg-gray-300 text-zinc-800",
@danger && "bg-red-700 text-zinc-50 hover:bg-red-900",
@inline &&
"px-3 py-1",
@class
]}
{@rest}
>
{render_slot(@inner_block)}
</button>
"""
end
@doc """
Renders a toggle switch with icons on the left and right sides.
"""
attr :left_icon, :string,
required: true,
doc: "The name of the icon to display on the left side of the toggle switch."
attr :right_icon, :string,
required: true,
doc: "The name of the icon to display on the right side of the toggle switch."
attr :event, :string,
required: true,
doc: "The event name triggered when the toggle switch is clicked."
attr :target, :any, default: nil, doc: "The target for the `phx-click` event."
attr :left, :boolean, required: true, doc: "Determines if the toggle is in the 'left' position."
attr :right, :boolean,
required: true,
doc: "Determines if the toggle is in the 'right' position."
def icon_toggle_switch(assigns) do
~H"""
<div class="flex gap-2 items-center cursor-pointer" phx-click={@event} phx-target={@target}>
<.icon name={@left_icon} class={["w-5 h-5", !@left && "text-zinc-300"]} />
<div class={[
"h-6 w-12 bg-gray-100 shadow-inner rounded-full flex items-center px-2",
@left && "justify-start",
@right && "justify-end",
!@left && !@right && "justify-center"
]}>
<div class="w-4 h-4 rounded-full bg-primary-500"></div>
</div>
<.icon name={@right_icon} class={["w-5 h-5", !@right && "text-zinc-300"]} />
</div>
"""
end
@doc """
Renders a dropdown button with a customizable label and a list of actions.
"""
attr :label, :string, doc: "The label displayed on the dropdown button.", default: nil
attr :id, :string, required: true, doc: "A unique identifier for the dropdown component."
attr :inline, :boolean, default: true, doc: "Apply the inline style to the dropdown button."
attr :plain, :boolean, default: false, doc: "Applies no border or color to the button"
attr :icon, :string, doc: "The name of the icon to display on the button."
attr :class, :any, default: nil
slot :action, doc: "A dropdwn menu item" do
attr :divider, :boolean, doc: "This item will create a dividing line"
attr :label, :string, doc: "The label for the menu item."
attr :icon, :string, doc: "The name of the icon to display"
attr :event, :string, doc: "The event triggered when the menu item is clicked."
attr :target, :any, doc: "The target for the `phx-click` event."
attr :value, :any, doc: "The value passed with the `phx-click` event."
attr :value_id, :string, doc: "The phx-value-id for the menu item"
attr :danger, :boolean
end
def dropdown(assigns) do
~H"""
<.popover id={@id} placement="bottom" trigger_click nopad minwidth>
<:trigger>
<.button
:if={@label}
inline={@inline}
type="button"
plain={@plain}
class={["flex items-center", @class]}
>
<.icon :if={assigns[:icon]} name={@icon} class="w-4 h-4 me-2" />
{@label}
<.icon name="hero-chevron-down" class="ms-2 h-3 w-3" />
</.button>
<button :if={!@label} class={["bg-white px-3 hover:bg-gray-100", @class]} type="button">
<.icon name="hero-ellipsis-horizontal" class="h-4 w-4" />
</button>
</:trigger>
<ul class="select-none z-50">
<li :for={action <- @action}>
<div :if={action[:divider]} class="border-l-4 border-gray-300 py-1">
<div class="border-t border-gray-300 mt-1" />
</div>
<div
:if={!action[:divider]}
phx-click={action[:event]}
phx-target={action[:target]}
phx-value-value={action[:value]}
phx-value-id={action[:value_id]}
class={[
"text-sm text-nowrap font-medium",
"flex items-center transition-all relative cursor-pointer",
"border-l-4 border-gray-300",
"sui-dropdown-item",
action[:danger] &&
"text-red-800 font-bold hover:text-red-600 hover:border-red-600",
!action[:danger] &&
"text-zinc-600 hover:text-primary-600 font-medium hover:border-primary-600"
]}
>
<div :if={action[:icon]} class="px-2 py-2 ">
<.icon name={action.icon} class="w-4 h-4" />
</div>
<div class="py-2 ps-2 pe-4">
{action.label}
</div>
</div>
</li>
</ul>
</.popover>
"""
end
attr :label, :string, default: nil, doc: "The label displayed on the dropdown button."
attr :id, :string, required: true, doc: "A unique identifier for the dropdown component."
attr :inline, :boolean, default: true, doc: "Apply the inline style to the dropdown button."
attr :plain, :boolean, default: false, doc: "Applies no border or color to the button"
attr :icon, :string, default: nil, doc: "The name of the icon to display on the button."
attr :options, :list,
default: [],
doc: "A list of dropdown options in the form of a label/value tuples"
attr :event, :any, default: nil, doc: "The event to fire when an item is selected"
attr :value, :any, default: nil, doc: "The currently selected value"
attr :target, :any, default: nil, doc: "The target for the selection event"
def dropdown_select(assigns) do
~H"""
<div class={[
"flex items-center divide-x",
"rounded transition-all",
"text-sm font-semibold leading-6 active:shadow-inner",
"disabled:text-zinc-50/50",
"bg-white text-zinc-600 disabled:text-zinc-500/50",
"border border-gray-200 border-gray-200"
]}>
<div class="font-bold px-3 py-1 text-zinc-600 text-sm flex">
<.icon :if={@icon} name={@icon} class="h-4 w-4 me-0 pe-0" />
<span :if={@label}>{@label}</span>
</div>
<.popover id={@id} placement="bottom" trigger_click nopad minwidth>
<:trigger>
<button class="flex items-center px-3 py-1 hover:border-gray-300 hover:bg-gray-50 hover:text-zinc-800">
{active_label(@options, @value) || "None"}
<.icon name="hero-chevron-down" class="ms-2 h-3 w-3" />
</button>
</:trigger>
<ul class="select-none">
<li :for={{label, value} <- @options}>
<div
phx-click={@event}
phx-target={@target}
phx-value-value={value}
class="block text-sm text-zinc-600 text-nowrap font-medium flex items-center transition-all relative cursor-pointer border-l-4 border-gray-400 hover:border-primary-600 hover:text-primary-600 sui-dropdown-item"
>
<div class="py-2 px-2">
{label}
</div>
</div>
</li>
</ul>
</.popover>
</div>
"""
end
attr :date, :any, required: true, doc: "The currently selected date value"
attr :id, :string, required: true
def date_select(assigns) do
assigns =
assign(assigns,
day_count:
Timex.diff(
Timex.end_of_month(assigns.date),
Timex.beginning_of_month(assigns.date),
:days
),
start_offset: Timex.weekday(Timex.beginning_of_month(assigns.date)) - 1
)
~H"""
<.popover trigger_click placement="bottom">
<:trigger>
<.button inline>
<.icon name="hero-calendar" class="w-4 h-4 me-2 mb-1" />
{Timex.format!(@date, "{0M}/{0D}/{YY}")}
</.button>
</:trigger>
<div class="flex justify-between items-center text-xs mb-2">
<button
class="py-2 hover:bg-zinc-100 px-2"
phx-click="date-select"
phx-value-id={@id}
phx-value-date={Timex.shift(@date, months: -1)}
>
<.icon name="hero-chevron-left" class="w-4 h-4" />
</button>
<div class="text-center py-2 font-bold">{Timex.format!(@date, "{Mshort}, {YYYY}")}</div>
<button
class="py-2 hover:bg-zinc-100 px-2"
phx-click="date-select"
phx-value-id={@id}
phx-value-date={Timex.shift(@date, months: 1)}
>
<.icon name="hero-chevron-right" class="w-4 h-4" />
</button>
</div>
<div class="grid grid-cols-7 text-xs text-center">
<div class="w-6 bg-zinc-600 text-zinc-50 font-bold py-1">S</div>
<div class="w-6 bg-zinc-600 text-zinc-50 font-bold py-1">M</div>
<div class="w-6 bg-zinc-600 text-zinc-50 font-bold py-1">T</div>
<div class="w-6 bg-zinc-600 text-zinc-50 font-bold py-1">W</div>
<div class="w-6 bg-zinc-600 text-zinc-50 font-bold py-1">T</div>
<div class="w-6 bg-zinc-600 text-zinc-50 font-bold py-1">F</div>
<div class="w-6 bg-zinc-600 text-zinc-50 font-bold py-1">S</div>
<div :for={_ <- 0..assigns.start_offset} class="w-6"></div>
<div
:for={d <- 0..assigns.day_count}
phx-click="date-select"
phx-value-id={@id}
phx-value-date={
Timex.beginning_of_day(DateTime.add(Timex.beginning_of_month(@date), d, :day))
}
class={[
"w-6 py-1 cursor-pointer rounded-full",
Timex.beginning_of_day(DateTime.add(Timex.beginning_of_month(@date), d, :day)) ==
Timex.beginning_of_day(@date) && "bg-primary-600 text-white",
Timex.beginning_of_day(DateTime.add(Timex.beginning_of_month(@date), d, :day)) !=
Timex.beginning_of_day(@date) && "hover:bg-gray-200"
]}
>
{d + 1}
</div>
</div>
</.popover>
"""
end
defp active_label(options, value) do
Enum.find_value(options, fn {label, v} ->
if value == v, do: label, else: nil
end)
end
end