Packages
mishka_chelekom
0.0.9
0.0.10-alpha.5
0.0.10-alpha.4
0.0.10-alpha.3
0.0.10-alpha.2
0.0.10-alpha.1
0.0.9
0.0.9-rc.2
0.0.9-rc.1
0.0.9-beta.5
0.0.9-beta.4
0.0.9-beta.3
0.0.9-beta.2
0.0.9-beta.1
0.0.9-alpha.20
0.0.9-alpha.18
0.0.9-alpha.17
0.0.9-alpha.16
0.0.9-alpha.15
0.0.9-alpha.14
0.0.9-alpha.13
0.0.9-alpha.12
0.0.9-alpha.11
0.0.9-alpha.10
0.0.9-alpha.9
0.0.9-alpha.8
0.0.9-alpha.7
0.0.9-alpha.6
0.0.9-alpha.5
0.0.9-alpha.4
0.0.9-alpha.3
0.0.9-alpha.2
0.0.9-alpha.1
0.0.8
0.0.8-rc.2
0.0.8-rc.1
0.0.8-beta.5
0.0.8-beta.4
0.0.8-beta.3
0.0.8-beta.2
0.0.8-beta.1
0.0.8-alpha.4
0.0.8-alpha.3
0.0.8-alpha.2
0.0.8-alpha.1
0.0.7
0.0.6
0.0.6-alpha.2
0.0.6-alpha.1
0.0.5
0.0.5-beta.2
0.0.5-beta.1
0.0.5-alpha.12
0.0.5-alpha.11
0.0.5-alpha.10
0.0.5-alpha.9
0.0.5-alpha.8
0.0.5-alpha.7
0.0.5-alpha.6
0.0.5-alpha.5
0.0.5-alpha.4
0.0.5-alpha.3
0.0.5-alpha.2
0.0.5-alpha.1
0.0.4
0.0.4-beta.3
0.0.4-beta.2
0.0.4-beta.1
0.0.4-alpha.9
0.0.4-alpha.8
0.0.4-alpha.7
0.0.4-alpha.6
0.0.4-alpha.5
0.0.4-alpha.4
0.0.4-alpha.3
0.0.4-alpha.2
0.0.4-alpha.1
0.0.3
0.0.3-alpha.3
0.0.3-alpha.2
0.0.3-alpha.1
0.0.2
0.0.2-rc.2
0.0.2-rc.1
0.0.2-beta.4
0.0.2-beta.3
0.0.2-beta.2
0.0.2-beta.1
0.0.2-alpha.3
0.0.2-alpha.2
0.0.2-alpha.1
0.0.1
Mishka Chelekom is a fully featured components and UI kit library for Phoenix & Phoenix LiveView
Current section
Files
Jump to
Current section
Files
priv/headless/tabs.eex
defmodule <%= @module %> do
@moduledoc """
Headless **tabs** — a value-driven tablist with an animated indicator (Base UI parity).
The `Tabs` engine owns roving focus (arrow keys along `orientation` + Home/End), activates a tab by
its `value` on click (and on focus when `activate_on_focus`), shows the matching `panel`, and measures
the active tab to publish the indicator CSS vars `--active-tab-left/right/top/bottom/width/height` (on
the `indicator` + tablist) so the underline slides/resizes. `data-activation-direction`
(left/right/up/down) is set for directional animations.
**Server push**: pass `on_change` (a LiveView event) and the active tab's `value` is pushed (`{value}`)
whenever the user switches; pass `value` to control the active tab from the server (re-renders re-sync
the indicator + panels without re-pushing). Options mirror Base UI: `value`/`default_value`,
`orientation`, plus per-`<:tab>` `value`/`disabled`. State: root/list/tab/panel/indicator
`data-orientation` + `data-activation-direction`; tab `data-active`/`data-disabled`; panel
`data-hidden`. Style via `chelekom-tabs*`.
WAI-ARIA APG: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
"""
use Phoenix.Component
@doc type: :component
attr :id, :string, required: true
attr :value, :string, default: nil, doc: "Active tab value (controlled by the server)"
attr :default_value, :string, default: nil, doc: "Initial active tab value (uncontrolled)"
attr :orientation, :string, default: "horizontal", values: ~w(horizontal vertical)
attr :activate_on_focus, :boolean, default: true, doc: "Activate a tab when it receives focus (automatic)"
attr :indicator, :boolean, default: true, doc: "Render the animated indicator element"
attr :on_change, :string, default: nil, doc: "LiveView event pushed on user switch ({value})"
attr :on_change_target, :string, default: nil, doc: "Optional pushEventTo target (e.g. a LiveComponent selector)"
attr :class, :any, default: nil
attr :list_class, :any, default: nil, doc: ~s|Extra classes for the `data-part="tablist"` row|
attr :indicator_class, :any, default: nil, doc: ~s|Extra classes for the `data-part="indicator"`|
attr :panels_class, :any, default: nil, doc: ~s|Extra classes for the wrapper around all panels (`data-part="panels"`)|
attr :rest, :global
slot :tab, required: true, doc: "A tab label" do
attr :value, :string, doc: "Stable value (defaults to the positional index)"
attr :disabled, :boolean
attr :class, :any, doc: ~s|Extra classes for this `data-part="tab"`|
end
slot :panel, required: true, doc: "A tab panel (matched to its tab by value/position)" do
attr :value, :string
attr :class, :any, doc: ~s|Extra classes for this `data-part="panel"`|
end
def <%= @component_prefix %>tabs(assigns) do
tab_value = fn item, i -> item[:value] || "#{assigns.id}-#{i}" end
active =
assigns.value || assigns.default_value ||
(assigns.tab
|> Enum.with_index()
|> Enum.find(fn {t, _} -> !t[:disabled] end)
|> case do
{t, i} -> tab_value.(t, i)
_ -> "#{assigns.id}-0"
end)
assigns = assign(assigns, active: active, tab_value: tab_value)
~H"""
<div
id={@id}
phx-hook="Tabs"
data-part="root"
data-orientation={@orientation}
data-activate-on-focus={@activate_on_focus}
data-on-change={@on_change}
data-on-change-target={@on_change_target}
data-value={@active}
class={["chelekom-tabs", @class]}
{@rest}
>
<div role="tablist" data-part="tablist" aria-orientation={@orientation} class={["chelekom-tabs__list", @list_class]}>
<button
:for={{tab, i} <- Enum.with_index(@tab)}
type="button"
role="tab"
data-part="tab"
id={"#{@id}-tab-#{i}"}
data-value={@tab_value.(tab, i)}
aria-controls={"#{@id}-panel-#{i}"}
aria-selected={to_string(@tab_value.(tab, i) == @active)}
data-active={@tab_value.(tab, i) == @active}
data-disabled={tab[:disabled]}
data-orientation={@orientation}
disabled={tab[:disabled]}
tabindex={if @tab_value.(tab, i) == @active, do: "0", else: "-1"}
class={["chelekom-tabs__tab", tab[:class]]}
>
{render_slot(tab)}
</button>
<div
:if={@indicator}
data-part="indicator"
data-orientation={@orientation}
aria-hidden="true"
class={["chelekom-tabs__indicator", @indicator_class]}
>
</div>
</div>
<div data-part="panels" class={["chelekom-tabs__panels", @panels_class]}>
<div
:for={{panel, i} <- Enum.with_index(@panel)}
id={"#{@id}-panel-#{i}"}
role="tabpanel"
data-part="panel"
data-value={panel[:value] || "#{@id}-#{i}"}
aria-labelledby={"#{@id}-tab-#{i}"}
data-index={i}
data-orientation={@orientation}
hidden={(panel[:value] || "#{@id}-#{i}") != @active}
data-hidden={(panel[:value] || "#{@id}-#{i}") != @active}
tabindex="0"
class={["chelekom-tabs__panel", panel[:class]]}
>
{render_slot(panel)}
</div>
</div>
</div>
"""
end
end