Repository: woylie/doggo
Pull request #226 (2024-02-11 12:25:28)

menu item
- add menu item component, rename menubar to menu_bar
- add admonition blocks

contributes to #124 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d07dbc..89fa91a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
 - New component: `Doggo.menu/1`.
 - New component: `Doggo.menu_bar/1`.
 - New component: `Doggo.menu_button/1`.
+- New component: `Doggo.menu_item/1`.
 - New component: `Doggo.radio_group/1`.
 - New component: `Doggo.split_pane/1`.
 - New component: `Doggo.tabs/1`.
diff --git a/lib/doggo.ex b/lib/doggo.ex
index cb6a154..0a11026 100644
--- a/lib/doggo.ex
+++ b/lib/doggo.ex
@@ -3432,6 +3432,16 @@ defmodule Doggo do
 
   See also `menu_bar/1` and `menu_button/1`.
 
+  > #### In Development {: .warning}
+  >
+  > The necessary JavaScript for making this component fully functional and
+  > accessible will be added in a future version.
+  >
+  > **Missing features**
+  >
+  > - Focus management
+  > - keyboard support
+
   ## Example
 
   If the menu is always visible or can only be toggled by a keyboard shortcut,
@@ -3512,19 +3522,56 @@ defmodule Doggo do
   end
 
   @doc """
-  Renders a menubar, similar to those found in desktop applications.
+  Renders a menu bar, 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/1` and `menu_button/1`.
+  See also `menu/1`, `menu_button/1`, and `menu_item/1`.
+
+  > #### In Development {: .warning}
+  >
+  > The necessary JavaScript for making this component fully functional and
+  > accessible will be added in a future version.
+  >
+  > **Missing features**
+  >
+  > - Focus management
+  > - keyboard support
 
   ## Example
 
   ```heex
-  <Doggo.menubar label="Main">
-    <:item>A</:item>
-  </Doggo.menubar>
+  <Doggo.menu_bar label="Main">
+    <:item>
+      <Doggo.menu_button controls="actions-menu" id="actions-button">
+        Actions
+      </Doggo.menu_button>
+
+      <Doggo.menu id="actions-menu" labelledby="actions-button" hidden>
+        <:item>
+          <Doggo.menu_item on_click={JS.push("view-dog-profiles")}>
+            View Dog Profiles
+          </Doggo.menu_item>
+        </:item>
+        <:item>
+          <Doggo.menu_item on_click={JS.push("add-dog-profile")}>
+            Add Dog Profile
+          </Doggo.menu_item>
+        </:item>
+        <:item>
+          <Doggo.menu_item on_click={JS.push("dog-care-tips")}>
+            Dog Care Tips
+          </Doggo.menu_item>
+        </:item>
+      </Doggo.menu>
+    </:item>
+    <:item>
+      <Doggo.menu_item on_click={JS.dispatch("myapp:help")}>
+        Help
+      </Doggo.menu_item>
+    </:item>
+  </Doggo.menu_bar>
   ```
   """
   @doc type: :component
@@ -3542,13 +3589,13 @@ defmodule Doggo do
   attr :labelledby, :string,
     default: nil,
     doc: """
-    The DOM ID of an element that labels this menubar.
+    The DOM ID of an element that labels this menu bar.
 
     Example:
 
     ```html
     <h3 id="dog-menu-label">Dog Actions</h3>
-    <.menubar labelledby="dog-menu-label"></.menubar>
+    <Doggo.menu_bar labelledby="dog-menu-label"></Doggo.menu_bar>
     ```
 
     You should ensure that either the `label` or the `labelledby` attribute is
@@ -3563,8 +3610,8 @@ defmodule Doggo do
 
   slot :item, required: true
 
-  def menubar(assigns) do
-    ensure_label!(assigns, "Doggo.menubar", "Dog Actions")
+  def menu_bar(assigns) do
+    ensure_label!(assigns, "Doggo.menu_bar", "Dog Actions")
 
     ~H"""
     <ul
@@ -3584,7 +3631,8 @@ 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` or `menu/1`.
+  This component can be used on its own or as part of a `menu_bar/1` or `menu/1`.
+  See also `menu_item/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
@@ -3605,21 +3653,41 @@ defmodule Doggo do
       Actions
     </Doggo.menu_button>
 
-    <ul id="actions-menu" role="menu" aria-labelledby="actions-button" hidden>
-      <li role="menuitem">View Dog Profiles</li>
-      <li role="menuitem">Add Dog Profile</li>
-      <li role="menuitem">Dog Care Tips</li>
-    </ul>
+    <Doggo.menu id="actions-menu" labelledby="actions-button" hidden>
+      <:item>
+        <Doggo.menu_item on_click={JS.push("view-dog-profiles")}>
+          View Dog Profiles
+        </Doggo.menu_item>
+      </:item>
+      <:item>
+        <Doggo.menu_item on_click={JS.push("add-dog-profile")}>
+          Add Dog Profile
+        </Doggo.menu_item>
+      </:item>
+      <:item>
+        <Doggo.menu_item on_click={JS.push("dog-care-tips")}>
+          Dog Care Tips
+        </Doggo.menu_item>
+      </:item>
+    </Doggo.menu>
   </div>
   ```
 
-  If this menu button is a child of a `menubar/1` or a `menu/1`, set the
+  If this menu button is a child of a `menu_bar/1` or a `menu/1`, set the
   `menuitem` attribute.
 
   ```heex
-  <Doggo.menu_button controls="actions-menu" id="actions-button" menuitem>
-    Actions
-  </Doggo.menu_button>
+  <Doggo.menu id="actions-menu">
+    <:item>
+      <Doggo.menu_button controls="actions-menu" id="actions-button" menuitem>
+        Dog Actions
+      </Doggo.menu_button>
+      <Doggo.menu id="dog-actions-menu" labelledby="actions-button" hidden>
+        <:item><!-- ... --></:item>
+      </Doggo.menu>
+    </:item>
+    <:item><!-- ... --></:item>
+  </Doggo.menu>
   ```
   """
   @doc type: :button
@@ -3642,7 +3710,7 @@ defmodule Doggo do
     default: false,
     doc: """
     Set this attribute to `true` if the menu button is used as a child of a
-    `menubar/1`. This ensures that the `role` is set to `menuitem`.
+    `menu_bar/1`. This ensures that the `role` is set to `menuitem`.
     """
 
   attr :rest, :global
@@ -3666,6 +3734,45 @@ defmodule Doggo do
     """
   end
 
+  @doc """
+  Renders a button that acts as a menu item within a `menu/1` or `menu_bar/1`.
+
+  A menu item is meant to be used to trigger an action. For a button that
+  toggles the visibility of a menu, use `menu_button/1`.
+
+  ## Example
+
+  ```heex
+  <Doggo.menu label="Actions">
+    <:item>
+      <Doggo.menu_item on_click={JS.dispatch("myapp:copy")}>
+        Copy
+      </Doggo.menu_item>
+    </:item>
+    <:item>
+      <Doggo.menu_item on_click={JS.dispatch("myapp:paste")}>
+        Paste
+      </Doggo.menu_item>
+    </:item>
+  </Doggo.menu>
+  ```
+  """
+  @doc type: :component
+  @doc since: "0.5.0"
+
+  attr :on_click, JS, required: true
+  attr :rest, :global
+
+  slot :inner_block, required: true
+
+  def menu_item(assigns) do
+    ~H"""
+    <button type="button" role="menuitem" phx-click={@on_click} {@rest}>
+      <%= render_slot(@inner_block) %>
+    </button>
+    """
+  end
+
   @doc """
   Renders a modal dialog for content such as forms and informational panels.
 


Test patch:
diff --git a/test/doggo_test.exs b/test/doggo_test.exs
index e08753f..7cbe7ad 100644
--- a/test/doggo_test.exs
+++ b/test/doggo_test.exs
@@ -3792,9 +3792,9 @@ defmodule DoggoTest do
 
       html =
         parse_heex(~H"""
-        <Doggo.menubar label="Dog actions">
+        <Doggo.menu_bar label="Dog actions">
           <:item>A</:item>
-        </Doggo.menubar>
+        </Doggo.menu_bar>
         """)
 
       ul = find_one(html, "ul:root")
@@ -3811,9 +3811,9 @@ defmodule DoggoTest do
 
       html =
         parse_heex(~H"""
-        <Doggo.menubar labelledby="dog-menu-label">
+        <Doggo.menu_bar labelledby="dog-menu-label">
           <:item>A</:item>
-        </Doggo.menubar>
+        </Doggo.menu_bar>
         """)
 
       assert attribute(html, ":root", "aria-labelledby") == "dog-menu-label"
@@ -3824,9 +3824,9 @@ defmodule DoggoTest do
         assigns = %{}
 
         parse_heex(~H"""
-        <Doggo.menubar label="Dog actions" labelledby="dog-menu-label">
+        <Doggo.menu_bar label="Dog actions" labelledby="dog-menu-label">
           <:item>A</:item>
-        </Doggo.menubar>
+        </Doggo.menu_bar>
         """)
       end
     end
@@ -3836,9 +3836,9 @@ defmodule DoggoTest do
         assigns = %{}
 
         parse_heex(~H"""
-        <Doggo.menubar>
+        <Doggo.menu_bar>
           <:item>A</:item>
-        </Doggo.menubar>
+        </Doggo.menu_bar>
         """)
       end
     end
@@ -3848,9 +3848,9 @@ defmodule DoggoTest do
 
       html =
         parse_heex(~H"""
-        <Doggo.menubar label="Dog actions" class="is-rad">
+        <Doggo.menu_bar label="Dog actions" class="is-rad">
           <:item>A</:item>
-        </Doggo.menubar>
+        </Doggo.menu_bar>
         """)
 
       assert attribute(html, ":root", "class") == "is-rad"
@@ -3861,9 +3861,9 @@ defmodule DoggoTest do
 
       html =
         parse_heex(~H"""
-        <Doggo.menubar label="Dog Carousel" class={["is-rad", "is-good"]}>
+        <Doggo.menu_bar label="Dog Carousel" class={["is-rad", "is-good"]}>
           <:item>A</:item>
-        </Doggo.menubar>
+        </Doggo.menu_bar>
         """)
 
       assert attribute(html, ":root", "class") == "is-rad is-good"
@@ -3874,9 +3874,9 @@ defmodule DoggoTest do
 
       html =
         parse_heex(~H"""
-        <Doggo.menubar label="Dog actions" data-test="hello">
+        <Doggo.menu_bar label="Dog actions" data-test="hello">
           <:item>A</:item>
-        </Doggo.menubar>
+        </Doggo.menu_bar>
         """)
 
       assert attribute(html, ":root", "data-test") == "hello"
@@ -3919,6 +3919,25 @@ defmodule DoggoTest do
     end
   end
 
+  describe "menu_item/1" do
+    test "default" do
+      assigns = %{}
+
+      html =
+        parse_heex(~H"""
+        <Doggo.menu_item on_click={JS.push("hello")}>
+          Action
+        </Doggo.menu_item>
+        """)
+
+      button = find_one(html, "button:root")
+      assert attribute(button, "type") == "button"
+      assert attribute(button, "phx-click")
+      assert attribute(button, "role") == "menuitem"
+      assert text(button) == "Action"
+    end
+  end
+
   describe "modal/1" do
     test "default" do
       assigns = %{}