Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Simple and lightweight multiple selection dropdown component with `checkboxes`,

## ✨ Features

- 🍃 Lightweight (~4KB)
- 🍃 Lightweight (~3KB)
- 💅 Themeable
- ✌ Written w/ TypeScript

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

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

```css
.multi-select {
--rmsc-primary: #4285f4;
--rmsc-main: #4285f4;
--rmsc-hover: #f1f3f5;
--rmsc-selected: #e2e6ea;
--rmsc-border: #ccc;
--rmsc-gray: #aaa;
--rmsc-background: #fff;
--rmsc-spacing: 10px;
--rmsc-border-radius: 4px;
--rmsc-height: 38px;
--rmsc-bg: #fff;
--rmsc-p: 10px; /* Spacing */
--rmsc-radius: 4px; /* Radius */
--rmsc-h: 38px; /* Height */
}
```

Expand Down
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-multi-select-component",
"version": "2.0.14",
"version": "3.0.0",
"description": "Simple and lightweight multiple selection dropdown component with checkboxes, search and select-all",
"author": "Harsh Zalavadiya",
"license": "MIT",
Expand All @@ -22,7 +22,6 @@
"react": ">=16"
},
"dependencies": {
"@rooks/use-outside-click": "^3.6.0",
"goober": "^1.8.0"
},
"devDependencies": {
Expand Down Expand Up @@ -69,5 +68,16 @@
"track": [
"./dist/*.production.min.js"
]
}
},
"keywords": [
"react",
"multi",
"select",
"checkboxes",
"select-all",
"dropdown",
"component",
"tiny",
"lightweight"
]
}
Binary file modified preview.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 2 additions & 8 deletions src/lib/get-string.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ const strings = {
search: "Search",
};

function getString(key: string, overrideStrings?): string {
if (overrideStrings && overrideStrings[key]) {
return overrideStrings[key];
}

return strings[key];
export default function getString(key: string, overrideStrings?): string {
return overrideStrings?.[key] || strings[key];
}

export default getString;
34 changes: 13 additions & 21 deletions src/multi-select/arrow.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
import React from "react";

export default function Arrow({ expanded = false }) {
interface ArrowProps {
expanded?: boolean;
}

export default function Arrow({ expanded }: ArrowProps) {
return (
<span
<svg
width="24"
height="24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="dropdown-heading-dropdown-arrow gray"
style={{ paddingTop: "4px" }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
>
{expanded ? (
<polyline points="18 15 12 9 6 15"></polyline>
) : (
<path d="M6 9L12 15 18 9"></path>
)}
</svg>
</span>
<path d={expanded ? "M18 15 12 9 6 15" : "M6 9L12 15 18 9"} />
</svg>
);
}
51 changes: 28 additions & 23 deletions src/multi-select/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
* and hosts it in the component. When the component is selected, it
* drops-down the contentComponent and applies the contentProps.
*/
import useOutsideClick from "@rooks/use-outside-click";
import { css } from "goober";
import React, { useRef, useState, useEffect } from "react";
import React, { useEffect, useRef, useState } from "react";

import Arrow from "./arrow";
import Loading from "./loading";
Expand All @@ -31,41 +30,38 @@ const PanelContainer = css({
".panel-content": {
maxHeight: "300px",
overflowY: "auto",
borderRadius: "var(--rmsc-border-radius)",
backgroundColor: "var(--rmsc-background)",
boxShadow:
"0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)",
borderRadius: "var(--rmsc-radius)",
background: "var(--rmsc-bg)",
boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 11px rgba(0, 0, 0, 0.1)",
},
});

const DropdownContainer = css({
position: "relative",
outline: "none",
backgroundColor: "var(--rmsc-background)",
outline: 0,
backgroundColor: "var(--rmsc-bg)",
border: "1px solid var(--rmsc-border)",
borderRadius: "var(--rmsc-border-radius)",
borderRadius: "var(--rmsc-radius)",
"&:focus-within": {
boxShadow: "var(--rmsc-primary) 0px 0px 0px 1px",
borderColor: "var(--rmsc-primary)",
boxShadow: "var(--rmsc-main) 0 0 0 1px",
borderColor: "var(--rmsc-main)",
},
});

const DropdownHeading = css({
position: "relative",
padding: "0 var(--rmsc-spacing)",
padding: "0 var(--rmsc-p)",
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
overflow: "hidden",
width: "100%",
height: "var(--rmsc-height)",
height: "var(--rmsc-h)",
cursor: "default",
outline: "none",
outline: 0,
".dropdown-heading-value": {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
flex: "1",
flex: 1,
},
});

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

const wrapper: any = useRef();

useOutsideClick(wrapper, () => setExpanded(false));

/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
onMenuToggle && onMenuToggle(expanded);
Expand All @@ -98,7 +92,9 @@ const Dropdown = ({
case 27: // Escape
case 38: // Up Arrow
setExpanded(false);
wrapper?.current?.focus();
break;
case 32: // Space
case 13: // Enter Key
case 40: // Down Arrow
setExpanded(true);
Expand All @@ -108,15 +104,24 @@ const Dropdown = ({
}
e.preventDefault();
};

const handleHover = (iexpanded: boolean) => {
shouldToggleOnHover && setExpanded(iexpanded);
};
const handleFocus = (e) => {
e.target === wrapper && !hasFocus && setHasFocus(true);

const handleFocus = () => !hasFocus && setHasFocus(true);

const handleBlur = (e) => {
if (!e.relatedTarget) {
setHasFocus(false);
setExpanded(false);
}
};
const handleBlur = () => hasFocus && setHasFocus(false);

const handleMouseEnter = () => handleHover(true);

const handleMouseLeave = () => handleHover(false);

const toggleExpanded = () =>
setExpanded(isLoading || disabled ? false : !expanded);

Expand All @@ -126,7 +131,7 @@ const Dropdown = ({
className={`${DropdownContainer} dropdown-container`}
aria-labelledby={labelledBy}
aria-expanded={expanded}
aria-readonly="true"
aria-readonly={true}
aria-disabled={disabled}
ref={wrapper}
onKeyDown={handleKeyDown}
Expand Down
23 changes: 9 additions & 14 deletions src/multi-select/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,16 @@ const DropdownHeader = ({ value, options, valueRenderer, overrideStrings }) => {

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

if (noneSelected) {
return (
<span className="gray">
{customText || getString("selectSomeItems", overrideStrings)}
</span>
);
}

return (
return noneSelected ? (
<span className="gray">
{customText || getString("selectSomeItems", overrideStrings)}
</span>
) : (
<span>
{customText
? customText
: allSelected
? getString("allItemsAreSelected", overrideStrings)
: getSelectedText()}
{customText ||
(allSelected
? getString("allItemsAreSelected", overrideStrings)
: getSelectedText())}
</span>
);
};
Expand Down
12 changes: 6 additions & 6 deletions src/multi-select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import Dropdown from "./dropdown";
import DropdownHeader from "./header";

const MultiSelectBox = css({
"--rmscPrimary": "#4285f4",
"--rmscMain": "#4285f4",
"--rmscHover": "#f1f3f5",
"--rmscSelected": "#e2e6ea",
"--rmscBorder": "#ccc",
"--rmscGray": "#aaa",
"--rmscBackground": "#fff",
"--rmscSpacing": "10px",
"--rmscBorderRadius": "4px",
"--rmscHeight": "38px",
"--rmscBg": "#fff",
"--rmscP": "10px",
"--rmscRadius": "4px",
"--rmscH": "38px",

"*": {
boxSizing: "border-box",
Expand All @@ -29,7 +29,7 @@ const MultiSelectBox = css({
const MultiSelect = ({
focusSearchOnOpen = true,
hasSelectAll = true,
shouldToggleOnHover = false,
shouldToggleOnHover,
className = "multi-select",
options,
value,
Expand Down
19 changes: 8 additions & 11 deletions src/multi-select/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,24 @@ const Spinner = css({

"@keyframes dash": {
"0%": {
strokeDasharray: "1, 150",
strokeDashoffset: "0",
strokeDasharray: "1,150",
strokeDashoffset: 0,
},
"50%": {
strokeDasharray: "90, 150",
strokeDasharray: "90,150",
strokeDashoffset: "-35",
},
"100%": {
strokeDasharray: "90, 150",
strokeDasharray: "90,150",
strokeDashoffset: "-124",
},
},
});

function Loading({ size = 26 }) {
function Loading({ size = 24 }) {
return (
<div
<span
style={{
cursor: "pointer",
display: "table-cell",
verticalAlign: "middle",
width: size,
marginRight: "0.2rem",
}}
Expand All @@ -49,11 +46,11 @@ function Loading({ size = 26 }) {
height={size}
className={Spinner}
viewBox="0 0 50 50"
style={{ display: "inline-block", verticalAlign: "middle" }}
style={{ display: "inline", verticalAlign: "middle" }}
>
<circle cx="25" cy="25" r="20" fill="none" className="path"></circle>
</svg>
</div>
</span>
);
}

Expand Down
Loading