Add aria menu hook and use in sidebar

This commit is contained in:
Chris McCord 2021-11-18 09:55:09 -05:00
parent 809a8de665
commit 30b46e95e5
4 changed files with 66 additions and 3 deletions

View file

@ -12,6 +12,57 @@ let execJS = (selector, attr) => {
let Hooks = {} let Hooks = {}
Hooks.Menu = {
getAttr(name){
let val = this.el.getAttribute(name)
if(val === null){ throw(new Error(`no ${name} attribute configured for menu`)) }
return val
},
reset(){
this.activeClass = this.getAttr("data-active-class")
this.deactivate(this.menuItems())
this.activeItem = null
},
mounted(){
this.menuItemsContainer = document.querySelector(`[aria-labelledby="${this.el.id}"]`)
this.reset()
this.el.addEventListener("click", e => {
if(e.currentTarget.isSameNode(this.el)){
this.el.focus()
this.activate(0)
}
})
this.el.addEventListener("keydown", e => {
if(e.key === "Escape"){
document.body.click()
this.reset()
} else if(e.key === "Enter" && !this.activeItem){
this.activate(0)
} else if(e.key === "Enter"){
this.activeItem.click()
}
if(e.key === "ArrowDown"){
e.preventDefault()
let menuItems = this.menuItems()
this.deactivate(menuItems)
this.activate(menuItems.indexOf(this.activeItem) + 1, menuItems.length - 1)
} else if(e.key === "ArrowUp"){
e.preventDefault()
let menuItems = this.menuItems()
this.deactivate(menuItems)
this.activate(menuItems.indexOf(this.activeItem) - 1, 0)
}
})
},
activate(index, fallbackIndex){
let menuItems = this.menuItems()
this.activeItem = menuItems[index] || menuItems[fallbackIndex]
this.activeItem.classList.add(this.activeClass)
},
deactivate(items){ items.forEach(item => item.classList.remove(this.activeClass)) },
menuItems(){ return Array.from(this.menuItemsContainer.querySelectorAll("[role=menuitem]")) }
}
Hooks.Flash = { Hooks.Flash = {
mounted(){ mounted(){
let hide = () => this.el.click() let hide = () => this.el.click()

View file

@ -173,6 +173,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
{"transition ease-out duration-120", "transform opacity-0 scale-95", {"transition ease-out duration-120", "transform opacity-0 scale-95",
"transform opacity-100 scale-100"} "transform opacity-100 scale-100"}
) )
|> JS.set_attribute({"aria-expanded", "true"}, to: to)
end end
def hide_dropdown(to) do def hide_dropdown(to) do
@ -182,6 +183,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
{"transition ease-in duration-120", "transform opacity-100 scale-100", {"transition ease-in duration-120", "transform opacity-100 scale-100",
"transform opacity-0 scale-95"} "transform opacity-0 scale-95"}
) )
|> JS.remove_attribute("aria-expanded", to: to)
end end
def show_modal(js \\ %JS{}, id) when is_binary(id) do def show_modal(js \\ %JS{}, id) when is_binary(id) do

View file

@ -68,7 +68,7 @@ defmodule LiveBeatsWeb.LayoutView do
<!-- User account dropdown --> <!-- User account dropdown -->
<div class="px-3 mt-6 relative inline-block text-left"> <div class="px-3 mt-6 relative inline-block text-left">
<div> <div>
<button type="button" <button id={"#{@id}-menu"} type="button" phx-hook="Menu" data-active-class="bg-gray-100"
class="group w-full bg-gray-100 rounded-md px-3.5 py-2 text-sm text-left font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500" class="group w-full bg-gray-100 rounded-md px-3.5 py-2 text-sm text-left font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500"
phx-click={show_dropdown("##{@id}-dropdown")}> phx-click={show_dropdown("##{@id}-dropdown")}>
<span class="flex w-full justify-between items-center"> <span class="flex w-full justify-between items-center">
@ -91,13 +91,22 @@ defmodule LiveBeatsWeb.LayoutView do
</span> </span>
</button> </button>
</div> </div>
<div id={"#{@id}-dropdown"} phx-click-away={hide_dropdown("##{@id}-dropdown")} class="hidden z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200" role="menu" aria-expanded="true"> <div
id={"#{@id}-dropdown"}
phx-click-away={hide_dropdown("##{@id}-dropdown")}
class="hidden z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200"
role="menu"
aria-labelledby={"#{@id}-menu"}
phx-update="ignore"
>
<div class="py-1" role="none"> <div class="py-1" role="none">
<.link <.link
role="menuitem"
redirect_to={profile_path(@current_user)} redirect_to={profile_path(@current_user)}
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>View Profile</.link> >View Profile</.link>
<.link <.link
role="menuitem"
redirect_to={Routes.settings_path(LiveBeatsWeb.Endpoint, :edit)} redirect_to={Routes.settings_path(LiveBeatsWeb.Endpoint, :edit)}
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem"
>Settings</.link> >Settings</.link>
@ -105,6 +114,7 @@ defmodule LiveBeatsWeb.LayoutView do
<div class="py-1" role="none"> <div class="py-1" role="none">
<.link <.link
role="menuitem"
href={Routes.o_auth_callback_path(LiveBeatsWeb.Endpoint, :sign_out)} href={Routes.o_auth_callback_path(LiveBeatsWeb.Endpoint, :sign_out)}
method={:delete} method={:delete}
role="menuitem" role="menuitem"

View file

@ -25,7 +25,7 @@
"phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"}, "phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
"phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "a22cf65f39061195e1706b3c5b01d3a4bae70e3a", []}, "phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "1480b49e52e973f551c88bede2f802e5f5d8c298", []},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"}, "phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"}, "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},