mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 06:02:32 -05:00
🐛 Fix some a11y errors on select component
This commit is contained in:
parent
e683564ab2
commit
cef89e7734
4 changed files with 69 additions and 38 deletions
|
@ -17,6 +17,8 @@
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(def listbox-id-index (atom 0))
|
||||||
|
|
||||||
(def ^:private schema:select-option
|
(def ^:private schema:select-option
|
||||||
[:and
|
[:and
|
||||||
[:map {:title "option"}
|
[:map {:title "option"}
|
||||||
|
@ -71,14 +73,9 @@
|
||||||
::mf/schema schema:select}
|
::mf/schema schema:select}
|
||||||
[{:keys [options class disabled default-selected on-change] :rest props}]
|
[{:keys [options class disabled default-selected on-change] :rest props}]
|
||||||
(let [open* (mf/use-state false)
|
(let [open* (mf/use-state false)
|
||||||
|
listbox-id-ref (mf/use-ref (dm/str "select-listbox-" (swap! listbox-id-index inc)))
|
||||||
|
listbox-id (mf/ref-val listbox-id-ref)
|
||||||
open (deref open*)
|
open (deref open*)
|
||||||
on-click
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps disabled)
|
|
||||||
(fn [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(when-not disabled
|
|
||||||
(swap! open* not))))
|
|
||||||
|
|
||||||
selected* (mf/use-state #(get-selected-option-id options default-selected))
|
selected* (mf/use-state #(get-selected-option-id options default-selected))
|
||||||
selected (deref selected*)
|
selected (deref selected*)
|
||||||
|
@ -86,6 +83,18 @@
|
||||||
focused* (mf/use-state nil)
|
focused* (mf/use-state nil)
|
||||||
focused (deref focused*)
|
focused (deref focused*)
|
||||||
|
|
||||||
|
has-focus* (mf/use-state false)
|
||||||
|
has-focus (deref has-focus*)
|
||||||
|
|
||||||
|
on-click
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps disabled)
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(reset! has-focus* true)
|
||||||
|
(when-not disabled
|
||||||
|
(swap! open* not))))
|
||||||
|
|
||||||
on-option-click
|
on-option-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps on-change)
|
(mf/deps on-change)
|
||||||
|
@ -116,7 +125,8 @@
|
||||||
(let [click-outside (nil? (.-relatedTarget event))]
|
(let [click-outside (nil? (.-relatedTarget event))]
|
||||||
(when click-outside
|
(when click-outside
|
||||||
(reset! focused* nil)
|
(reset! focused* nil)
|
||||||
(reset! open* false)))))
|
(reset! open* false)
|
||||||
|
(reset! has-focus* false)))))
|
||||||
|
|
||||||
on-key-down
|
on-key-down
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
@ -146,12 +156,17 @@
|
||||||
(do (reset! open* false)
|
(do (reset! open* false)
|
||||||
(reset! focused* nil)))))))
|
(reset! focused* nil)))))))
|
||||||
|
|
||||||
class (dm/str class " " (stl/css :select))
|
on-focus
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [_] (reset! has-focus* true)))
|
||||||
|
|
||||||
|
class (dm/str class " " (stl/css-case :select true
|
||||||
|
:focused has-focus))
|
||||||
|
|
||||||
props (mf/spread-props props {:class class
|
props (mf/spread-props props {:class class
|
||||||
:role "combobox"
|
:role "combobox"
|
||||||
:aria-controls "listbox"
|
:aria-controls listbox-id
|
||||||
:aria-haspopup "listbox"
|
:aria-haspopup listbox-id
|
||||||
:aria-activedescendant focused
|
:aria-activedescendant focused
|
||||||
:aria-expanded open
|
:aria-expanded open
|
||||||
:on-key-down on-key-down
|
:on-key-down on-key-down
|
||||||
|
@ -166,7 +181,10 @@
|
||||||
(mf/with-effect [options]
|
(mf/with-effect [options]
|
||||||
(mf/set-ref-val! options-ref options))
|
(mf/set-ref-val! options-ref options))
|
||||||
|
|
||||||
[:div {:class (stl/css :select-wrapper)}
|
[:div {:class (stl/css :select-wrapper)
|
||||||
|
:on-click on-click
|
||||||
|
:on-focus on-focus
|
||||||
|
:on-blur on-blur}
|
||||||
[:> :button props
|
[:> :button props
|
||||||
[:span {:class (stl/css-case :select-header true
|
[:span {:class (stl/css-case :select-header true
|
||||||
:header-icon (some? icon))}
|
:header-icon (some? icon))}
|
||||||
|
|
|
@ -53,6 +53,10 @@
|
||||||
appearance: none;
|
appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focused {
|
||||||
|
--select-outline-color: var(--color-accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
color: var(--select-icon-fg-color);
|
color: var(--select-icon-fg-color);
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
|
|
|
@ -16,34 +16,37 @@
|
||||||
{::mf/props :obj
|
{::mf/props :obj
|
||||||
::mf/private true}
|
::mf/private true}
|
||||||
[{:keys [id label icon aria-label on-click selected set-ref focused] :rest props}]
|
[{:keys [id label icon aria-label on-click selected set-ref focused] :rest props}]
|
||||||
[:> :li {:value id
|
|
||||||
:class (stl/css-case :option true
|
|
||||||
:option-with-icon (some? icon)
|
|
||||||
:option-current focused)
|
|
||||||
:aria-selected selected
|
|
||||||
:ref (fn [node]
|
|
||||||
(set-ref node id))
|
|
||||||
:role "option"
|
|
||||||
:id id
|
|
||||||
:on-click on-click
|
|
||||||
:data-id id
|
|
||||||
:data-testid "dropdown-option"}
|
|
||||||
|
|
||||||
(when (some? icon)
|
(let [_ (prn selected)]
|
||||||
[:> icon*
|
[:> :li {:value id
|
||||||
{:id icon
|
:class (stl/css-case :option true
|
||||||
:size "s"
|
:option-with-icon (some? icon)
|
||||||
:class (stl/css :option-icon)
|
:option-selected selected
|
||||||
:aria-hidden (when label true)
|
:option-current focused)
|
||||||
:aria-label (when (not label) aria-label)}])
|
:aria-selected selected
|
||||||
|
:ref (fn [node]
|
||||||
|
(set-ref node id))
|
||||||
|
:role "option"
|
||||||
|
:id id
|
||||||
|
:on-click on-click
|
||||||
|
:data-id id
|
||||||
|
:data-testid "dropdown-option"}
|
||||||
|
|
||||||
[:span {:class (stl/css :option-text)} label]
|
(when (some? icon)
|
||||||
(when selected
|
[:> icon*
|
||||||
[:> icon*
|
{:id icon
|
||||||
{:id i/tick
|
:size "s"
|
||||||
:size "s"
|
:class (stl/css :option-icon)
|
||||||
:class (stl/css :option-check)
|
:aria-hidden (when label true)
|
||||||
:aria-hidden (when label true)}])])
|
:aria-label (when (not label) aria-label)}])
|
||||||
|
|
||||||
|
[:span {:class (stl/css :option-text)} label]
|
||||||
|
(when selected
|
||||||
|
[:> icon*
|
||||||
|
{:id i/tick
|
||||||
|
:size "s"
|
||||||
|
:class (stl/css :option-check)
|
||||||
|
:aria-hidden (when label true)}])]))
|
||||||
|
|
||||||
(mf/defc options-dropdown*
|
(mf/defc options-dropdown*
|
||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
outline: $b-1 solid var(--options-dropdown-outline-color);
|
outline: $b-1 solid var(--options-dropdown-outline-color);
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
background-color: var(--options-dropdown-bg-color);
|
background-color: var(--options-dropdown-bg-color);
|
||||||
|
color: var(--options-dropdown-fg-color);
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&[aria-selected="true"] {
|
&[aria-selected="true"] {
|
||||||
|
@ -72,3 +73,8 @@
|
||||||
--options-dropdown-outline-color: var(--color-accent-primary);
|
--options-dropdown-outline-color: var(--color-accent-primary);
|
||||||
outline: $b-1 solid var(--options-dropdown-outline-color);
|
outline: $b-1 solid var(--options-dropdown-outline-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.option-selected {
|
||||||
|
--options-dropdown-fg-color: var(--color-accent-primary);
|
||||||
|
--options-dropdown-icon-fg-color: var(--color-accent-primary);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue