Skip to content

Commit

Permalink
add menu component
Browse files Browse the repository at this point in the history
  • Loading branch information
woylie committed Feb 11, 2024
1 parent 3e9d34f commit 095dcfd
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- New component: `Doggo.alert_dialog/1`.
- New component: `Doggo.carousel/1`.
- New component: `Doggo.disclosure_button/1`.
- New component: `Doggo.menu/1`.
- New component: `Doggo.menu_bar/1`.
- New component: `Doggo.menu_button/1`.
- New component: `Doggo.radio_group/1`.
Expand Down
94 changes: 91 additions & 3 deletions lib/doggo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3424,13 +3424,100 @@ defmodule Doggo do
"""
end

@doc """
Renders a menu that offers a list of actions or functions.
This component is meant for organizing actions within an application, rather
than for navigating between different pages or sections of a website.
See also `menu_bar/1` and `menu_button/1`.
## Example
If the menu is always visible or can only be toggled by a keyboard shortcut,
set the `label` attribute.
```heex
<Doggo.menu label="Actions">
<:item>Copy</:item>
<:item>Paste</:item>
</Doggo.menu>
```
If the menu is toggled by a `menu_button/1`, ensure that the `controls`
attribute of the button matches the DOM ID of the menu and that the
`labelledby` attribute of the menu matches the DOM ID of the button.
<Doggo.menu_button controls="actions-menu" id="actions-button">
Actions
</Doggo.menu_button>
<Doggo.menu labelledby="actions-button" hidden></Doggo.menu>
"""
@doc type: :component
@doc since: "0.5.0"

attr :label, :string,
default: nil,
doc: """
A accessibility label for the menubar. Set as `aria-label` attribute.
You should ensure that either the `label` or the `labelledby` attribute is
set.
"""

attr :labelledby, :string,
default: nil,
doc: """
The DOM ID of an element that labels this menubar. If the menu is toggled
by a `menu_button/1`, this attribute should be set to the DOM ID of that
button.
Example:
```html
<Doggo.menu_button controls="actions-menu" id="actions-button">
Actions
</Doggo.menu_button>
<Doggo.menu labelledby="actions-button" hidden></Doggo.menu>
```
You should ensure that either the `label` or the `labelledby` attribute is
set.
"""

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 :item, required: true

def menu(assigns) do
ensure_label!(assigns, "Doggo.menu", "Dog Actions")

~H"""
<ul
class={@class}
role="menu"
aria-label={@label}
aria-labelledby={@labelledby}
{@rest}
>
<li :for={item <- @item} role="none">
<%= render_slot(item) %>
</li>
</ul>
"""
end

@doc """
Renders a menubar, similar to those found in desktop applications.
This component is meant for organizing actions within an application, rather
than for navigating between different pages or sections of a website.
See also `menu_button/1`.
See also `menu/1` and `menu_button/1`.
## Example
Expand Down Expand Up @@ -3497,7 +3584,7 @@ defmodule Doggo do
@doc """
Renders a button that toggles an actions menu.
This component can be used on its own or as part of a `menubar/1`.
This component can be used on its own or as part of a `menubar/1` or `menu/1`.
For a button that toggles the visibility of an element that is not a menu, use
`disclosure_button/1`. For a button that toggles other states, use
Expand Down Expand Up @@ -3526,7 +3613,8 @@ defmodule Doggo do
</div>
```
If this menu button is a child of a `menubar/1`, set the `menuitem` attribute.
If this menu button is a child of a `menubar/1` or a `menu/1`, set the
`menuitem` attribute.
```heex
<Doggo.menu_button controls="actions-menu" id="actions-button" menuitem>
Expand Down
97 changes: 97 additions & 0 deletions test/doggo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3689,6 +3689,103 @@ defmodule DoggoTest do
end
end

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

html =
parse_heex(~H"""
<Doggo.menu label="Dog actions">
<:item>A</:item>
</Doggo.menu>
""")

ul = find_one(html, "ul:root")
assert attribute(ul, "role") == "menu"
assert attribute(ul, "aria-label") == "Dog actions"

assert li = find_one(html, "ul > li")
assert attribute(li, "role") == "none"
assert text(li) == "A"
end

test "with labelledby" do
assigns = %{}

html =
parse_heex(~H"""
<Doggo.menu labelledby="dog-menu-label">
<:item>A</:item>
</Doggo.menu>
""")

assert attribute(html, ":root", "aria-labelledby") == "dog-menu-label"
end

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

parse_heex(~H"""
<Doggo.menu label="Dog actions" labelledby="dog-menu-label">
<:item>A</:item>
</Doggo.menu>
""")
end
end

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

parse_heex(~H"""
<Doggo.menu>
<:item>A</:item>
</Doggo.menu>
""")
end
end

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

html =
parse_heex(~H"""
<Doggo.menu label="Dog actions" class="is-rad">
<:item>A</:item>
</Doggo.menu>
""")

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

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

html =
parse_heex(~H"""
<Doggo.menu label="Dog Carousel" class={["is-rad", "is-good"]}>
<:item>A</:item>
</Doggo.menu>
""")

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

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

html =
parse_heex(~H"""
<Doggo.menu label="Dog actions" data-test="hello">
<:item>A</:item>
</Doggo.menu>
""")

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

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

0 comments on commit 095dcfd

Please sign in to comment.