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

add split pane component #201

Merged
merged 1 commit into from
Jan 7, 2024
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- New component: `Doggo.disclosure_button/1`.
- New component: `Doggo.radio_group/1`.
- New component: `Doggo.split_pane/1`.
- New component: `Doggo.tabs/1`.
- New component: `Doggo.toolbar/1`.
- New component: `Doggo.tree/1`.
Expand Down
135 changes: 135 additions & 0 deletions lib/doggo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3610,6 +3610,141 @@ defmodule Doggo do
"""
end

@doc """
Renders a horizontal or vertical resizable split pane.

> #### In Development {: .warning}
>
> The necessary JavaScript for making this component fully functional and
> accessible will be added in a future version.
>
> **Missing features**
>
> - Resize panes with the mouse
> - Resize panes with the keyboard

## Examples

Horizontal separator with label:

```heex
<Doggo.split_pane
id="sidebar-splitter"
label="Sidebar"
orientation="horizontal"
>
<:primary>One</:primary>
<:secondary>Two</:secondary>
</Doggo.split_pane>
```

Horizontal separator with visible label:

```heex
<Doggo.split_pane id="sidebar-splitter"
labelledby="sidebar-label"
orientation="horizontal"
>
<:primary>
<h2 id="sidebar-label">Sidebar</h2>
<p>One</p>
</:primary>
<:secondary>Two</:secondary>
</Doggo.split_pane>
```

Nested window splitters:

```heex
<Doggo.split_pane
id="sidebar-splitter"
label="Sidebar"
orientation="horizontal"
>
<:primary>One</:primary>
<:secondary>
<Doggo.split_pane
id="filter-splitter"
label="Filters"
orientation="vertical"
>
<:primary>Two</:primary>
<:secondary>Three</:secondary>
</Doggo.split_pane>
</:secondary>
</Doggo.split_pane>
```
"""
@doc type: :component

attr :label, :string,
default: nil,
doc: """
An accessibility label for the separator if the primary pane has no visible
label. If it has a visible label, set the `labelledby` attribute instead.

Note that the label should describe the primary pane, not the resize handle.
"""

attr :labelledby, :string,
default: nil,
doc: """
If the primary pane has a visible label, set this attribute to the DOM ID
of that label. Otherwise, provide a label via the `label` attribute.
"""

attr :id, :string, required: true
attr :orientation, :string, values: ["horizontal", "vertical"], required: true
attr :default_size, :integer, required: true
attr :min_size, :integer, default: 0
attr :max_size, :integer, default: 100

slot :primary, required: true
slot :secondary, required: true

def split_pane(assigns) do
label = assigns[:label]
labelledby = assigns[:labelledby]

if (label && labelledby) || !(label || labelledby) do
raise """
invalid label attributes for split_pane

Doggo.split_pane requires either 'label' or 'labelledby' set for
accessibility, but not both.

## Examples

With label:

<Doggo.split_pane label="Favorite Dog" ... />

With labelledby:

<h3 id="favorite-dog-label">Favorite Dog</h3>
<Doggo.split_pane labelledby="favorite-dog-label" ... />
"""
end

~H"""
<div id={@id} class="split-pane" data-orientation={@orientation}>
<div id={"#{@id}-primary"}><%= render_slot(@primary) %></div>
<div
role="separator"
aria-label={@label}
aria-labelledby={@labelledby}
aria-controls={"#{@id}-primary"}
aria-orientation={@orientation}
aria-valuenow={@default_size}
aria-valuemin={@min_size}
aria-valuemax={@max_size}
>
</div>
<div id={"#{@id}-secondary"}><%= render_slot(@secondary) %></div>
</div>
"""
end

@doc """
Renders a navigation for form steps.

Expand Down
61 changes: 61 additions & 0 deletions priv/storybook/components/split_pane.story.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule Storybook.Components.SplitPane do
use PhoenixStorybook.Story, :component

def function, do: &Doggo.split_pane/1

def variations do
[
%Variation{
id: :horizontal,
attributes: %{
id: "horizontal-split-pane",
orientation: :horizontal,
label: "Sidebar",
default_size: 100
},
slots: [
"<:primary>One</:primary>",
"<:secondary>Two</:secondary>"
]
},
%Variation{
id: :vertical,
attributes: %{
id: "vertical-split-pane",
orientation: :vertical,
label: "Sidebar",
default_size: 100
},
slots: [
"<:primary>One</:primary>",
"<:secondary>Two</:secondary>"
]
},
%Variation{
id: :nested,
attributes: %{
id: "nested-split-pane",
orientation: :horizontal,
label: "Sidebar",
default_size: 100
},
slots: [
"<:primary>One</:primary>",
"""
<:secondary>
<Doggo.split_pane
id="filter-splitter"
label="Filters"
orientation="vertical"
default_size={50}
>
<:primary>Two</:primary>
<:secondary>Three</:secondary>
</Doggo.split_pane>
</:secondary>
"""
]
}
]
end
end
106 changes: 106 additions & 0 deletions test/doggo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3883,6 +3883,112 @@ defmodule DoggoTest do
end
end

describe "split_pane/1" do
test "with label" do
assigns = %{}

html =
parse_heex(~H"""
<Doggo.split_pane
id="sidebar-splitter"
label="Sidebar"
orientation="horizontal"
default_size={200}
min_size={100}
max_size={400}
>
<:primary>One</:primary>
<:secondary>Two</:secondary>
</Doggo.split_pane>
""")

assert attribute(html, "div:root", "class") == "split-pane"
assert attribute(html, ":root", "id") == "sidebar-splitter"
assert attribute(html, ":root", "data-orientation") == "horizontal"

div = find_one(html, ":root > :first-child")
assert attribute(div, "id") == "sidebar-splitter-primary"
assert text(div) == "One"

div = find_one(html, ":root > :last-child")
assert attribute(div, "id") == "sidebar-splitter-secondary"
assert text(div) == "Two"

div = find_one(html, ":root > div[role='separator']")
assert attribute(div, "aria-label") == "Sidebar"
assert attribute(div, "aria-labelledby") == nil
assert attribute(div, "aria-controls") == "sidebar-splitter-primary"
assert attribute(div, "aria-valuenow") == "200"
assert attribute(div, "aria-valuemin") == "100"
assert attribute(div, "aria-valuemax") == "400"
end

test "with labelledby" do
assigns = %{}

html =
parse_heex(~H"""
<Doggo.split_pane
id="sidebar-splitter"
labelledby="sidebar-heading"
orientation="horizontal"
default_size={200}
>
<:primary>
<h2 id="sidebar-heading">Sidebar</h2>
</:primary>
<:secondary>Two</:secondary>
</Doggo.split_pane>
""")

div = find_one(html, "[role='separator']")
assert attribute(div, "aria-label") == nil
assert attribute(div, "aria-labelledby") == "sidebar-heading"
end

test "raises if both label and labelledby are set" do
error =
assert_raise RuntimeError, fn ->
assigns = %{}

parse_heex(~H"""
<Doggo.split_pane
id="sidebar-splitter"
label="Sidebar"
labelledby="sidebar-heading"
orientation="horizontal"
default_size={200}
>
<:primary>One</:primary>
<:secondary>Two</:secondary>
</Doggo.split_pane>
""")
end

assert error.message =~ "invalid label attributes"
end

test "raises if neither label nor labelledby are set" do
error =
assert_raise RuntimeError, fn ->
assigns = %{}

parse_heex(~H"""
<Doggo.split_pane
id="sidebar-splitter"
orientation="horizontal"
default_size={200}
>
<:primary>One</:primary>
<:secondary>Two</:secondary>
</Doggo.split_pane>
""")
end

assert error.message =~ "invalid label attributes"
end
end

describe "stack/1" do
test "default" do
assigns = %{}
Expand Down