mirror of
https://github.com/ClaperCo/Claper.git
synced 2026-02-24 04:01:04 +01:00
Add DaisyUI-inspired Tabs component
Create tabs component based on DaisyUI design system: - Styles: bordered (underline), lifted (raised active), boxed (pill) - Support for slot-based tab items with active state - Individual tab_item component for more control - Accessible with role="tablist" and aria-selected - Support for phx-click handlers on tabs Import component globally in claper_web.ex. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
157
lib/claper_web/views/components/tabs_component.ex
Normal file
157
lib/claper_web/views/components/tabs_component.ex
Normal file
@@ -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>
|
||||
<:tab active>Tab 2</:tab>
|
||||
<:tab>Tab 3</:tab>
|
||||
</.tabs>
|
||||
|
||||
<.tabs style={:boxed}>
|
||||
<:tab>Home</:tab>
|
||||
<:tab active>Profile</:tab>
|
||||
<:tab>Settings</:tab>
|
||||
</.tabs>
|
||||
|
||||
<.tabs style={:lifted}>
|
||||
<:tab>Overview</:tab>
|
||||
<:tab active>Details</:tab>
|
||||
<:tab>History</:tab>
|
||||
</.tabs>
|
||||
|
||||
## With click handlers
|
||||
|
||||
<.tabs style={:boxed}>
|
||||
<:tab active={@active_tab == "home"} phx-click="set_tab" phx-value-tab="home">Home</:tab>
|
||||
<:tab active={@active_tab == "profile"} phx-click="set_tab" phx-value-tab="profile">Profile</:tab>
|
||||
</.tabs>
|
||||
"""
|
||||
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"""
|
||||
<div
|
||||
role="tablist"
|
||||
class={[
|
||||
container_classes(@style),
|
||||
@class
|
||||
]}
|
||||
{@rest}
|
||||
>
|
||||
<%= for {tab, _index} <- Enum.with_index(@tab) do %>
|
||||
<button
|
||||
role="tab"
|
||||
class={[
|
||||
tab_base_classes(@style),
|
||||
tab_state_classes(@style, tab[:active] || false)
|
||||
]}
|
||||
aria-selected={tab[:active] || false}
|
||||
{assigns_to_attributes(tab, [:active, :class, :inner_block])}
|
||||
>
|
||||
{render_slot(tab)}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders a single tab item. Use this for more control over individual tabs.
|
||||
|
||||
## Examples
|
||||
|
||||
<.tab_item active>Active Tab</.tab_item>
|
||||
<.tab_item phx-click="change_tab">Inactive Tab</.tab_item>
|
||||
"""
|
||||
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"""
|
||||
<button
|
||||
role="tab"
|
||||
class={[
|
||||
tab_base_classes(@style),
|
||||
tab_state_classes(@style, @active),
|
||||
@class
|
||||
]}
|
||||
aria-selected={@active}
|
||||
{@rest}
|
||||
>
|
||||
{render_slot(@inner_block)}
|
||||
</button>
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user