diff --git a/lib/claper_web.ex b/lib/claper_web.ex index 82c232e..c03cb5e 100644 --- a/lib/claper_web.ex +++ b/lib/claper_web.ex @@ -112,6 +112,7 @@ defmodule ClaperWeb do # Import design system components import ClaperWeb.Component.Button import ClaperWeb.Component.Badge + import ClaperWeb.Component.Tabs unquote(verified_routes()) end diff --git a/lib/claper_web/views/components/tabs_component.ex b/lib/claper_web/views/components/tabs_component.ex new file mode 100644 index 0000000..54c7053 --- /dev/null +++ b/lib/claper_web/views/components/tabs_component.ex @@ -0,0 +1,157 @@ +defmodule ClaperWeb.Component.Tabs do + @moduledoc """ + DaisyUI-inspired Tabs component for Claper. + + ## Styles + - `:bordered` - Simple tabs with bottom border on active (default) + - `:lifted` - Lifted appearance with border on sides and top for active + - `:boxed` - Pill-shaped tabs with background, active has primary fill + + ## Examples + + <.tabs> + <:tab>Tab 1 + <:tab active>Tab 2 + <:tab>Tab 3 + + + <.tabs style={:boxed}> + <:tab>Home + <:tab active>Profile + <:tab>Settings + + + <.tabs style={:lifted}> + <:tab>Overview + <:tab active>Details + <:tab>History + + + ## With click handlers + + <.tabs style={:boxed}> + <:tab active={@active_tab == "home"} phx-click="set_tab" phx-value-tab="home">Home + <:tab active={@active_tab == "profile"} phx-click="set_tab" phx-value-tab="profile">Profile + + """ + use ClaperWeb, :view_component + + attr :style, :atom, default: :bordered, values: [:bordered, :lifted, :boxed] + attr :class, :string, default: nil + attr :rest, :global + + slot :tab, required: true do + attr :active, :boolean + attr :class, :string + end + + def tabs(assigns) do + ~H""" +
+ <%= for {tab, _index} <- Enum.with_index(@tab) do %> + + <% end %> +
+ """ + end + + @doc """ + Renders a single tab item. Use this for more control over individual tabs. + + ## Examples + + <.tab_item active>Active Tab + <.tab_item phx-click="change_tab">Inactive Tab + """ + attr :style, :atom, default: :bordered, values: [:bordered, :lifted, :boxed] + attr :active, :boolean, default: false + attr :class, :string, default: nil + attr :rest, :global, include: ~w(phx-click phx-target phx-value-tab) + + slot :inner_block, required: true + + def tab_item(assigns) do + ~H""" + + """ + end + + # Container classes based on style + defp container_classes(:bordered) do + "flex items-center border-b border-gray-200" + end + + defp container_classes(:lifted) do + "flex items-end" + end + + defp container_classes(:boxed) do + "inline-flex items-center bg-gray-100 p-0.5 rounded-full" + end + + # Base tab classes based on style + defp tab_base_classes(:bordered) do + "px-4 py-1.5 text-sm font-normal font-display transition-all duration-200 rounded-t-lg -mb-px" + end + + defp tab_base_classes(:lifted) do + "px-4 py-1.5 text-sm font-normal font-display transition-all duration-200 rounded-t-lg border-b border-gray-200" + end + + defp tab_base_classes(:boxed) do + "px-4 py-1.5 text-sm font-normal font-display transition-all duration-200 rounded-full" + end + + # Tab state classes (active/inactive) based on style + defp tab_state_classes(:bordered, true) do + "text-gray-800 border-b-2 border-gray-800 font-medium" + end + + defp tab_state_classes(:bordered, false) do + "text-gray-500 hover:text-gray-700 border-b-2 border-transparent" + end + + defp tab_state_classes(:lifted, true) do + "text-gray-800 bg-white border-l border-r border-t border-gray-200 border-b-0 -mb-px" + end + + defp tab_state_classes(:lifted, false) do + "text-gray-500 hover:text-gray-700 bg-transparent" + end + + defp tab_state_classes(:boxed, true) do + "text-white bg-primary-500 font-bold" + end + + defp tab_state_classes(:boxed, false) do + "text-gray-500 hover:text-gray-700 bg-transparent" + end +end