Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tabs #186

Merged
merged 2 commits into from
Jan 1, 2024
Merged

tabs #186

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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