Skip to content

Commit 0a4adcf

Browse files
♿ accessibility improvements (#70)
1 parent d63c15e commit 0a4adcf

14 files changed

+133
-124
lines changed

README.md

+12-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Simple and lightweight multiple selection dropdown component with `checkboxes`,
99

1010
## ✨ Features
1111

12-
- 🍃 Lightweight (~4KB)
12+
- 🍃 Lightweight (~3KB)
1313
- 💅 Themeable
1414
- ✌ Written w/ TypeScript
1515

@@ -33,6 +33,12 @@ const Example: React.FC = () => {
3333
{ label: "Grapes 🍇", value: "grapes" },
3434
{ label: "Mango 🥭", value: "mango" },
3535
{ label: "Strawberry 🍓", value: "strawberry", disabled: true },
36+
{ label: "Watermelon 🍉", value: "watermelon" },
37+
{ label: "Pear 🍐", value: "pear" },
38+
{ label: "Apple 🍎", value: "apple" },
39+
{ label: "Tangerine 🍊", value: "tangerine" },
40+
{ label: "Pineapple 🍍", value: "pineapple" },
41+
{ label: "Peach 🍑", value: "peach" },
3642
];
3743

3844
const [selected, setSelected] = useState([]);
@@ -128,15 +134,15 @@ You can override CSS variables to customize the appearance
128134

129135
```css
130136
.multi-select {
131-
--rmsc-primary: #4285f4;
137+
--rmsc-main: #4285f4;
132138
--rmsc-hover: #f1f3f5;
133139
--rmsc-selected: #e2e6ea;
134140
--rmsc-border: #ccc;
135141
--rmsc-gray: #aaa;
136-
--rmsc-background: #fff;
137-
--rmsc-spacing: 10px;
138-
--rmsc-border-radius: 4px;
139-
--rmsc-height: 38px;
142+
--rmsc-bg: #fff;
143+
--rmsc-p: 10px; /* Spacing */
144+
--rmsc-radius: 4px; /* Radius */
145+
--rmsc-h: 38px; /* Height */
140146
}
141147
```
142148

package.json

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-multi-select-component",
3-
"version": "2.0.14",
3+
"version": "3.0.0",
44
"description": "Simple and lightweight multiple selection dropdown component with checkboxes, search and select-all",
55
"author": "Harsh Zalavadiya",
66
"license": "MIT",
@@ -22,7 +22,6 @@
2222
"react": ">=16"
2323
},
2424
"dependencies": {
25-
"@rooks/use-outside-click": "^3.6.0",
2625
"goober": "^1.8.0"
2726
},
2827
"devDependencies": {
@@ -69,5 +68,16 @@
6968
"track": [
7069
"./dist/*.production.min.js"
7170
]
72-
}
71+
},
72+
"keywords": [
73+
"react",
74+
"multi",
75+
"select",
76+
"checkboxes",
77+
"select-all",
78+
"dropdown",
79+
"component",
80+
"tiny",
81+
"lightweight"
82+
]
7383
}

preview.gif

11.3 KB
Loading

src/lib/get-string.tsx

+2-8
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@ const strings = {
55
search: "Search",
66
};
77

8-
function getString(key: string, overrideStrings?): string {
9-
if (overrideStrings && overrideStrings[key]) {
10-
return overrideStrings[key];
11-
}
12-
13-
return strings[key];
8+
export default function getString(key: string, overrideStrings?): string {
9+
return overrideStrings?.[key] || strings[key];
1410
}
15-
16-
export default getString;

src/multi-select/arrow.tsx

+13-21
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
11
import React from "react";
22

3-
export default function Arrow({ expanded = false }) {
3+
interface ArrowProps {
4+
expanded?: boolean;
5+
}
6+
7+
export default function Arrow({ expanded }: ArrowProps) {
48
return (
5-
<span
9+
<svg
10+
width="24"
11+
height="24"
12+
fill="none"
13+
stroke="currentColor"
14+
strokeWidth="2"
615
className="dropdown-heading-dropdown-arrow gray"
7-
style={{ paddingTop: "4px" }}
816
>
9-
<svg
10-
xmlns="http://www.w3.org/2000/svg"
11-
width="24"
12-
height="24"
13-
fill="none"
14-
stroke="currentColor"
15-
strokeLinecap="round"
16-
strokeLinejoin="round"
17-
strokeWidth="2"
18-
viewBox="0 0 24 24"
19-
>
20-
{expanded ? (
21-
<polyline points="18 15 12 9 6 15"></polyline>
22-
) : (
23-
<path d="M6 9L12 15 18 9"></path>
24-
)}
25-
</svg>
26-
</span>
17+
<path d={expanded ? "M18 15 12 9 6 15" : "M6 9L12 15 18 9"} />
18+
</svg>
2719
);
2820
}

src/multi-select/dropdown.tsx

+28-23
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
* and hosts it in the component. When the component is selected, it
44
* drops-down the contentComponent and applies the contentProps.
55
*/
6-
import useOutsideClick from "@rooks/use-outside-click";
76
import { css } from "goober";
8-
import React, { useRef, useState, useEffect } from "react";
7+
import React, { useEffect, useRef, useState } from "react";
98

109
import Arrow from "./arrow";
1110
import Loading from "./loading";
@@ -31,41 +30,38 @@ const PanelContainer = css({
3130
".panel-content": {
3231
maxHeight: "300px",
3332
overflowY: "auto",
34-
borderRadius: "var(--rmsc-border-radius)",
35-
backgroundColor: "var(--rmsc-background)",
36-
boxShadow:
37-
"0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)",
33+
borderRadius: "var(--rmsc-radius)",
34+
background: "var(--rmsc-bg)",
35+
boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 11px rgba(0, 0, 0, 0.1)",
3836
},
3937
});
4038

4139
const DropdownContainer = css({
4240
position: "relative",
43-
outline: "none",
44-
backgroundColor: "var(--rmsc-background)",
41+
outline: 0,
42+
backgroundColor: "var(--rmsc-bg)",
4543
border: "1px solid var(--rmsc-border)",
46-
borderRadius: "var(--rmsc-border-radius)",
44+
borderRadius: "var(--rmsc-radius)",
4745
"&:focus-within": {
48-
boxShadow: "var(--rmsc-primary) 0px 0px 0px 1px",
49-
borderColor: "var(--rmsc-primary)",
46+
boxShadow: "var(--rmsc-main) 0 0 0 1px",
47+
borderColor: "var(--rmsc-main)",
5048
},
5149
});
5250

5351
const DropdownHeading = css({
5452
position: "relative",
55-
padding: "0 var(--rmsc-spacing)",
53+
padding: "0 var(--rmsc-p)",
5654
display: "flex",
5755
alignItems: "center",
58-
justifyContent: "flex-end",
59-
overflow: "hidden",
6056
width: "100%",
61-
height: "var(--rmsc-height)",
57+
height: "var(--rmsc-h)",
6258
cursor: "default",
63-
outline: "none",
59+
outline: 0,
6460
".dropdown-heading-value": {
6561
overflow: "hidden",
6662
textOverflow: "ellipsis",
6763
whiteSpace: "nowrap",
68-
flex: "1",
64+
flex: 1,
6965
},
7066
});
7167

@@ -86,8 +82,6 @@ const Dropdown = ({
8682

8783
const wrapper: any = useRef();
8884

89-
useOutsideClick(wrapper, () => setExpanded(false));
90-
9185
/* eslint-disable react-hooks/exhaustive-deps */
9286
useEffect(() => {
9387
onMenuToggle && onMenuToggle(expanded);
@@ -98,7 +92,9 @@ const Dropdown = ({
9892
case 27: // Escape
9993
case 38: // Up Arrow
10094
setExpanded(false);
95+
wrapper?.current?.focus();
10196
break;
97+
case 32: // Space
10298
case 13: // Enter Key
10399
case 40: // Down Arrow
104100
setExpanded(true);
@@ -108,15 +104,24 @@ const Dropdown = ({
108104
}
109105
e.preventDefault();
110106
};
107+
111108
const handleHover = (iexpanded: boolean) => {
112109
shouldToggleOnHover && setExpanded(iexpanded);
113110
};
114-
const handleFocus = (e) => {
115-
e.target === wrapper && !hasFocus && setHasFocus(true);
111+
112+
const handleFocus = () => !hasFocus && setHasFocus(true);
113+
114+
const handleBlur = (e) => {
115+
if (!e.relatedTarget) {
116+
setHasFocus(false);
117+
setExpanded(false);
118+
}
116119
};
117-
const handleBlur = () => hasFocus && setHasFocus(false);
120+
118121
const handleMouseEnter = () => handleHover(true);
122+
119123
const handleMouseLeave = () => handleHover(false);
124+
120125
const toggleExpanded = () =>
121126
setExpanded(isLoading || disabled ? false : !expanded);
122127

@@ -126,7 +131,7 @@ const Dropdown = ({
126131
className={`${DropdownContainer} dropdown-container`}
127132
aria-labelledby={labelledBy}
128133
aria-expanded={expanded}
129-
aria-readonly="true"
134+
aria-readonly={true}
130135
aria-disabled={disabled}
131136
ref={wrapper}
132137
onKeyDown={handleKeyDown}

src/multi-select/header.tsx

+9-14
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,16 @@ const DropdownHeader = ({ value, options, valueRenderer, overrideStrings }) => {
99

1010
const getSelectedText = () => value.map((s) => s.label).join(", ");
1111

12-
if (noneSelected) {
13-
return (
14-
<span className="gray">
15-
{customText || getString("selectSomeItems", overrideStrings)}
16-
</span>
17-
);
18-
}
19-
20-
return (
12+
return noneSelected ? (
13+
<span className="gray">
14+
{customText || getString("selectSomeItems", overrideStrings)}
15+
</span>
16+
) : (
2117
<span>
22-
{customText
23-
? customText
24-
: allSelected
25-
? getString("allItemsAreSelected", overrideStrings)
26-
: getSelectedText()}
18+
{customText ||
19+
(allSelected
20+
? getString("allItemsAreSelected", overrideStrings)
21+
: getSelectedText())}
2722
</span>
2823
);
2924
};

src/multi-select/index.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import Dropdown from "./dropdown";
77
import DropdownHeader from "./header";
88

99
const MultiSelectBox = css({
10-
"--rmscPrimary": "#4285f4",
10+
"--rmscMain": "#4285f4",
1111
"--rmscHover": "#f1f3f5",
1212
"--rmscSelected": "#e2e6ea",
1313
"--rmscBorder": "#ccc",
1414
"--rmscGray": "#aaa",
15-
"--rmscBackground": "#fff",
16-
"--rmscSpacing": "10px",
17-
"--rmscBorderRadius": "4px",
18-
"--rmscHeight": "38px",
15+
"--rmscBg": "#fff",
16+
"--rmscP": "10px",
17+
"--rmscRadius": "4px",
18+
"--rmscH": "38px",
1919

2020
"*": {
2121
boxSizing: "border-box",
@@ -29,7 +29,7 @@ const MultiSelectBox = css({
2929
const MultiSelect = ({
3030
focusSearchOnOpen = true,
3131
hasSelectAll = true,
32-
shouldToggleOnHover = false,
32+
shouldToggleOnHover,
3333
className = "multi-select",
3434
options,
3535
value,

src/multi-select/loading.tsx

+8-11
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,24 @@ const Spinner = css({
1919

2020
"@keyframes dash": {
2121
"0%": {
22-
strokeDasharray: "1, 150",
23-
strokeDashoffset: "0",
22+
strokeDasharray: "1,150",
23+
strokeDashoffset: 0,
2424
},
2525
"50%": {
26-
strokeDasharray: "90, 150",
26+
strokeDasharray: "90,150",
2727
strokeDashoffset: "-35",
2828
},
2929
"100%": {
30-
strokeDasharray: "90, 150",
30+
strokeDasharray: "90,150",
3131
strokeDashoffset: "-124",
3232
},
3333
},
3434
});
3535

36-
function Loading({ size = 26 }) {
36+
function Loading({ size = 24 }) {
3737
return (
38-
<div
38+
<span
3939
style={{
40-
cursor: "pointer",
41-
display: "table-cell",
42-
verticalAlign: "middle",
4340
width: size,
4441
marginRight: "0.2rem",
4542
}}
@@ -49,11 +46,11 @@ function Loading({ size = 26 }) {
4946
height={size}
5047
className={Spinner}
5148
viewBox="0 0 50 50"
52-
style={{ display: "inline-block", verticalAlign: "middle" }}
49+
style={{ display: "inline", verticalAlign: "middle" }}
5350
>
5451
<circle cx="25" cy="25" r="20" fill="none" className="path"></circle>
5552
</svg>
56-
</div>
53+
</span>
5754
);
5855
}
5956

0 commit comments

Comments
 (0)