Skip to content

Commit

Permalink
Merge pull request #186 from woylie/tabs
Browse files Browse the repository at this point in the history
tabs
  • Loading branch information
woylie authored Jan 1, 2024
2 parents 4255d92 + d1dd1f2 commit 5f8b94b
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ jobs:
- name: Compile
run: mix compile --warnings-as-errors
- name: Run Tests
run: mix coveralls.github
run: mix coveralls.github --warnings-as-errors
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Added

- New component: `Doggo.tabs/1`.

## [0.4.0] - 2023-12-31

### Added
Expand Down
114 changes: 111 additions & 3 deletions lib/doggo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2910,7 +2910,8 @@ defmodule Doggo do
To toggle the modal visibility dynamically with the `open` attribute:
1. Omit the `open` attribute in the template.
2. Use the `show_modal` and `hide_modal` functions to change the visibility.
2. Use the `show_modal/1` and `hide_modal/1` functions to change the
visibility.
#### Example
Expand All @@ -2926,10 +2927,10 @@ defmodule Doggo do
</Doggo.modal>
```
To open modal, use the `show_modal` function.
To open modal, use the `show_modal/1` function.
```heex
<.link phx-click={show_modal("pet-modal")}>show</.link>
<.link phx-click={Doggo.show_modal("pet-modal")}>show</.link>
```
## CSS
Expand Down Expand Up @@ -3665,6 +3666,10 @@ defmodule Doggo do
@doc """
Renders navigation tabs.
This component is meant for tabs that link to a different view or live action.
If you want to render tabs that switch between in-page content panels, use
`tabs/1` instead.
## Example
```heex
Expand Down Expand Up @@ -3749,6 +3754,109 @@ defmodule Doggo do
"""
end

@doc """
Renders tab panels.
This component is meant for tabs that toggle content panels within the page.
If you want to link to a different view or live action, use
`tab_navigation/1` instead.
> #### Keyboard interaction {: .warning}
>
> Keyboard interaction will be added in a future version.
## Example
```heex
<Doggo.tabs id="dog-breed-profiles" title="Dog Breed Profiles">
<:panel label="Golden Retriever">
<p>
Friendly, intelligent, great with families. Origin: Scotland. Needs
regular exercise.
</p>
</:panel>
<:panel label="Siberian Husky">
<p>
Energetic, outgoing, distinctive appearance. Origin: Northeast Asia.
Loves cold climates.
</p>
</:panel>
<:panel label="Dachshund">
<p>
Playful, stubborn, small size. Origin: Germany. Enjoys sniffing games.
</p>
</:panel>
</Doggo.tabs>
```
"""
@doc type: :component

attr :id, :string, required: true
attr :title, :string, required: true, doc: "A title that labels the tabs."

attr :class, :any,
default: [],
doc: "Additional CSS classes. Can be a string or a list of strings."

attr :rest, :global, doc: "Any additional HTML attributes."

slot :panel, required: true do
attr :label, :string
end

def tabs(assigns) do
~H"""
<div id={@id} class={["tabs" | List.wrap(@class)]} {@rest}>
<h3 id={"#{@id}-title"}><%= @title %></h3>
<div role="tablist" aria-labelledby={"#{@id}-title"}>
<button
:for={{panel, index} <- Enum.with_index(@panel, 1)}
type="button"
role="tab"
id={"#{@id}-tab-#{index}"}
aria-selected={to_string(index == 1)}
aria-controls={"#{@id}-panel-#{index}"}
tabindex={index != 1 && "-1"}
phx-click={show_tab(@id, index)}
>
<%= panel.label %>
</button>
</div>
<div
:for={{panel, index} <- Enum.with_index(@panel, 1)}
id={"#{@id}-panel-#{index}"}
role="tabpanel"
aria-labelledby={"#{@id}-tab-#{index}"}
hidden={index != 1}
>
<%= render_slot(panel) %>
</div>
</div>
"""
end

@doc """
Shows the tab with the given index of the `tabs/1` component with the given
ID.
## Example
Doggo.show_tab("my-tabs", 2)
"""
def show_tab(js \\ %JS{}, id, index)
when is_binary(id) and is_integer(index) do
other_tabs = "##{id} [role='tab']:not(##{id}-tab-#{index})"
other_panels = "##{id} [role='tabpanel']:not(##{id}-panel-#{index})"

js
|> JS.set_attribute({"aria-selected", "true"}, to: "##{id}-tab-#{index}")
|> JS.set_attribute({"tabindex", "0"}, to: "##{id}-tab-#{index}")
|> JS.remove_attribute("hidden", to: "##{id}-panel-#{index}")
|> JS.set_attribute({"aria-selected", "false"}, to: other_tabs)
|> JS.set_attribute({"tabindex", "-1"}, to: other_tabs)
|> JS.set_attribute({"hidden", "hidden"}, to: other_panels)
end

@doc """
Renders a button that toggles a state.
Expand Down
42 changes: 42 additions & 0 deletions priv/storybook/components/tabs.story.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Storybook.Components.Tabs do
use PhoenixStorybook.Story, :component

def function, do: &Doggo.tabs/1

def variations do
[
%Variation{
id: :default,
attributes: %{
id: "dog-breed-profiles",
title: "Dog Breed Profiles"
},
slots: [
"""
<:panel label="Golden Retriever">
<p>
Friendly, intelligent, great with families. Origin: Scotland. Needs
regular exercise.
</p>
</:panel>
""",
"""
<:panel label="Siberian Husky">
<p>
Energetic, outgoing, distinctive appearance. Origin: Northeast Asia.
Loves cold climates.
</p>
</:panel>
""",
"""
<:panel label="Dachshund">
<p>
Playful, stubborn, small size. Origin: Germany. Enjoys sniffing games.
</p>
</:panel>
"""
]
}
]
end
end
93 changes: 93 additions & 0 deletions test/doggo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3983,6 +3983,99 @@ defmodule DoggoTest do
end
end

describe "tabs/1" do
test "default" do
assigns = %{}

html =
parse_heex(~H"""
<Doggo.tabs id="my-tabs" title="My Tabs">
<:panel label="Panel 1">some text</:panel>
<:panel label="Panel 2">some other text</:panel>
</Doggo.tabs>
""")

div = find_one(html, "div:root")
assert attribute(div, "class") == "tabs"
assert attribute(div, "id") == "my-tabs"

assert attribute(html, ":root > h3", "id") == "my-tabs-title"
assert text(html, ":root > h3") == "My Tabs"

div = find_one(html, ":root > div[role='tablist']")
assert attribute(div, "aria-labelledby") == "my-tabs-title"

button = find_one(div, "button:first-child")
assert attribute(button, "type") == "button"
assert attribute(button, "role") == "tab"
assert attribute(button, "id") == "my-tabs-tab-1"
assert attribute(button, "aria-selected") == "true"
assert attribute(button, "aria-controls") == "my-tabs-panel-1"
assert attribute(button, "tabindex") == nil
assert text(button) == "Panel 1"

button = find_one(div, "button:last-child")
assert attribute(button, "type") == "button"
assert attribute(button, "role") == "tab"
assert attribute(button, "id") == "my-tabs-tab-2"
assert attribute(button, "aria-selected") == "false"
assert attribute(button, "aria-controls") == "my-tabs-panel-2"
assert attribute(button, "tabindex") == "-1"
assert text(button) == "Panel 2"

div = find_one(html, ":root > div#my-tabs-panel-1")
assert attribute(div, "role") == "tabpanel"
assert attribute(div, "aria-labelledby") == "my-tabs-tab-1"
assert attribute(div, "hidden") == nil
assert text(div) == "some text"

div = find_one(html, ":root > div#my-tabs-panel-2")
assert attribute(div, "role") == "tabpanel"
assert attribute(div, "aria-labelledby") == "my-tabs-tab-2"
assert attribute(div, "hidden") == "hidden"
assert text(div) == "some other text"
end

test "with additional class as string" do
assigns = %{}

html =
parse_heex(~H"""
<Doggo.tabs id="my-tabs" title="My Tabs" class="is-rad">
<:panel label="Panel 1">some text</:panel>
</Doggo.tabs>
""")

assert attribute(html, ":root", "class") == "tabs is-rad"
end

test "with additional classes as list" do
assigns = %{}

html =
parse_heex(~H"""
<Doggo.tabs id="my-tabs" title="My Tabs" class={["is-rad", "is-dark"]}>
<:panel label="Panel 1">some text</:panel>
</Doggo.tabs>
""")

assert attribute(html, ":root", "class") == "tabs is-rad is-dark"
end

test "with global attribute" do
assigns = %{}

html =
parse_heex(~H"""
<Doggo.tabs id="my-tabs" title="My Tabs" data-test="hello">
<:panel label="Panel 1">some text</:panel>
</Doggo.tabs>
""")

assert attribute(html, ":root", "data-test") == "hello"
end
end

describe "table/1" do
test "default" do
assigns = %{pets: [%{id: 1, name: "George"}]}
Expand Down

0 comments on commit 5f8b94b

Please sign in to comment.