From 137babc4b269c2ae2ccf6bd2b0d8b8c2b39b7e98 Mon Sep 17 00:00:00 2001 From: Shorif Date: Tue, 27 Dec 2022 17:01:53 +0600 Subject: [PATCH 1/2] simplified code by using the includes method to check if an option is in the value array --- src/select-panel/select-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/select-panel/select-list.tsx b/src/select-panel/select-list.tsx index ffda49e..63f990c 100644 --- a/src/select-panel/select-list.tsx +++ b/src/select-panel/select-list.tsx @@ -37,7 +37,7 @@ const SelectList = ({ options, onClick, skipIndex }: ISelectListProps) => { tabIndex={tabIndex} option={o} onSelectionChanged={(c) => handleSelectionChanged(o, c)} - checked={!!value.find((s) => s.value === o.value)} + checked={value.includes(o)} onClick={(e) => onClick(e, tabIndex)} itemRenderer={ItemRenderer} disabled={o.disabled || disabled} From 00b5e5e249ec4e81580e267c144a14455ee87d53 Mon Sep 17 00:00:00 2001 From: shorif Date: Wed, 28 Dec 2022 23:38:26 +0600 Subject: [PATCH 2/2] improve package performance --- src/hooks/use-multi-select.tsx | 9 ++++++--- src/multi-select/arrow.tsx | 15 +++++++++------ src/multi-select/dropdown.tsx | 29 ++++++++++++++++------------- src/multi-select/header.tsx | 29 ++++++++++++++++++----------- src/select-panel/cross.tsx | 9 +++------ src/select-panel/default-item.tsx | 10 +++++++--- src/select-panel/index.tsx | 21 ++++++++++++--------- src/style.css | 12 ++++++++++++ 8 files changed, 83 insertions(+), 51 deletions(-) diff --git a/src/hooks/use-multi-select.tsx b/src/hooks/use-multi-select.tsx index 125956a..7b10f0c 100644 --- a/src/hooks/use-multi-select.tsx +++ b/src/hooks/use-multi-select.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Option, SelectProps } from "../lib/interfaces"; @@ -29,7 +29,7 @@ interface MultiSelectContextProps extends SelectProps { interface MultiSelectProviderProps { props: SelectProps; - children; + children: React.ReactNode; } const MultiSelectContext = React.createContext( @@ -41,7 +41,10 @@ export const MultiSelectProvider = ({ children, }: MultiSelectProviderProps) => { const [options, setOptions] = useState(props.options); - const t = (key) => props.overrideStrings?.[key] || defaultStrings[key]; + const t = useMemo( + () => (key) => props.overrideStrings?.[key] || defaultStrings[key], + [props.overrideStrings] + ); useEffect(() => { setOptions(props.options); diff --git a/src/multi-select/arrow.tsx b/src/multi-select/arrow.tsx index 5dee654..b2f8e9b 100644 --- a/src/multi-select/arrow.tsx +++ b/src/multi-select/arrow.tsx @@ -1,14 +1,17 @@ import React from "react"; +interface IArrowProps { + expanded: boolean | undefined; +} -export const Arrow = ({ expanded }) => ( +export const Arrow = ({ expanded }: IArrowProps) => ( - + ); diff --git a/src/multi-select/dropdown.tsx b/src/multi-select/dropdown.tsx index bbe8471..39fd027 100644 --- a/src/multi-select/dropdown.tsx +++ b/src/multi-select/dropdown.tsx @@ -3,7 +3,7 @@ * and hosts it in the component. When the component is selected, it * drops-down the contentComponent and applies the contentProps. */ -import React, { useEffect, useRef, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { useDidUpdateEffect } from "../hooks/use-did-update-effect"; import { useKey } from "../hooks/use-key"; @@ -43,7 +43,7 @@ const Dropdown = () => { const [hasFocus, setHasFocus] = useState(false); const FinalArrow = ArrowRenderer || Arrow; - const wrapper: any = useRef(); + const wrapperRef = useRef(null); useDidUpdateEffect(() => { onMenuToggle && onMenuToggle(expanded); @@ -54,7 +54,7 @@ const Dropdown = () => { setIsInternalExpand(false); setExpanded(isOpen); } - }, [isOpen]); + }, [isOpen, defaultIsOpen]); const handleKeyDown = (e) => { // allows space and enter when focused on input/button @@ -68,7 +68,7 @@ const Dropdown = () => { if (isInternalExpand) { if (e.code === KEY.ESCAPE) { setExpanded(false); - wrapper?.current?.focus(); + wrapperRef?.current?.focus(); } else { setExpanded(true); } @@ -77,11 +77,11 @@ const Dropdown = () => { }; useKey([KEY.ENTER, KEY.ARROW_DOWN, KEY.SPACE, KEY.ESCAPE], handleKeyDown, { - target: wrapper, + target: wrapperRef, }); - const handleHover = (iexpanded: boolean) => { - isInternalExpand && shouldToggleOnHover && setExpanded(iexpanded); + const handleHover = (isExpanded: boolean) => { + isInternalExpand && shouldToggleOnHover && setExpanded(isExpanded); }; const handleFocus = () => !hasFocus && setHasFocus(true); @@ -101,11 +101,14 @@ const Dropdown = () => { isInternalExpand && setExpanded(isLoading || disabled ? false : !expanded); }; - const handleClearSelected = (e) => { - e.stopPropagation(); - onChange([]); - isInternalExpand && setExpanded(false); - }; + const handleClearSelected = useCallback( + (e) => { + e.stopPropagation(); + onChange([]); + isInternalExpand && setExpanded(false); + }, + [onChange, isInternalExpand] + ); return (
{ aria-expanded={expanded} aria-readonly={true} aria-disabled={disabled} - ref={wrapper} + ref={wrapperRef} onFocus={handleFocus} onBlur={handleBlur} onMouseEnter={handleMouseEnter} diff --git a/src/multi-select/header.tsx b/src/multi-select/header.tsx index 9026798..20842ff 100644 --- a/src/multi-select/header.tsx +++ b/src/multi-select/header.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { useMultiSelect } from "../hooks/use-multi-select"; @@ -7,16 +7,23 @@ export const DropdownHeader = () => { const noneSelected = value.length === 0; const allSelected = value.length === options.length; - const customText = valueRenderer && valueRenderer(value, options); + const selectedText = valueRenderer + ? valueRenderer(value, options) + : "Text Undefined"; - const getSelectedText = () => value.map((s) => s.label).join(", "); - - return noneSelected ? ( - {customText || t("selectSomeItems")} - ) : ( - - {customText || - (allSelected ? t("allItemsAreSelected") : getSelectedText())} - + const getSelectedText = useMemo( + () => () => value.map((s) => s.label).join(", "), + [value] ); + + switch (true) { + case noneSelected: + return ( + {selectedText || t("selectSomeItems")} + ); + case allSelected: + return {selectedText || t("allItemsAreSelected")}; + default: + return {selectedText || getSelectedText()}; + } }; diff --git a/src/select-panel/cross.tsx b/src/select-panel/cross.tsx index 91e4b11..d07bbd9 100644 --- a/src/select-panel/cross.tsx +++ b/src/select-panel/cross.tsx @@ -4,12 +4,9 @@ export const Cross = () => ( - - + + ); diff --git a/src/select-panel/default-item.tsx b/src/select-panel/default-item.tsx index 466905e..eb72a11 100644 --- a/src/select-panel/default-item.tsx +++ b/src/select-panel/default-item.tsx @@ -6,7 +6,7 @@ interface IDefaultItemRendererProps { checked: boolean; option: Option; disabled?: boolean; - onClick; + onClick: () => void; } const DefaultItemRenderer = ({ @@ -15,8 +15,12 @@ const DefaultItemRenderer = ({ onClick, disabled, }: IDefaultItemRendererProps) => ( -
+
+ ); export default DefaultItemRenderer; diff --git a/src/select-panel/index.tsx b/src/select-panel/index.tsx index ae3c46b..17e160c 100644 --- a/src/select-panel/index.tsx +++ b/src/select-panel/index.tsx @@ -3,7 +3,7 @@ * user selects the component. It encapsulates the search filter, the * Select-all item, and the list of options. */ - import React, { +import React, { useCallback, useEffect, useMemo, @@ -43,14 +43,17 @@ const SelectPanel = () => { onCreateOption, } = useMultiSelect(); - const listRef = useRef(); - const searchInputRef = useRef(); + const listRef = useRef(null); + const searchInputRef = useRef(null); const [searchText, setSearchText] = useState(""); const [filteredOptions, setFilteredOptions] = useState(options); const [searchTextForFilter, setSearchTextForFilter] = useState(""); const [focusIndex, setFocusIndex] = useState(0); const debouncedSearch = useCallback( - debounce((query) => setSearchTextForFilter(query), debounceDuration), + debounce( + (query: string) => setSearchTextForFilter(query), + debounceDuration + ), [] ); @@ -68,7 +71,7 @@ const SelectPanel = () => { value: "", }; - const selectAllValues = (checked) => { + const selectAllValues = (checked: boolean) => { const filteredValues = filteredOptions .filter((o) => !o.disabled) .map((o) => o.value); @@ -90,13 +93,13 @@ const SelectPanel = () => { onChange(newOptions); }; - const handleSearchChange = (e) => { + const handleSearchChange = (e: React.ChangeEvent): void => { debouncedSearch(e.target.value); setSearchText(e.target.value); setFocusIndex(FocusType.SEARCH); }; - const handleClear = () => { + const handleClear = (): void => { setSearchTextForFilter(""); setSearchText(""); searchInputRef?.current?.focus(); @@ -105,7 +108,7 @@ const SelectPanel = () => { const handleItemClicked = (index: number) => setFocusIndex(index); // Arrow Key Navigation - const handleKeyDown = (e) => { + const handleKeyDown = (e: KeyboardEvent) => { switch (e.code) { case KEY.ARROW_UP: updateFocus(-1); @@ -174,7 +177,7 @@ const SelectPanel = () => { getFilteredOptions().then(setFilteredOptions); }, [searchTextForFilter, options]); - const creationRef: any = useRef(); + const creationRef: any = useRef(null); useKey([KEY.ENTER], handleOnCreateOption, { target: creationRef }); const showCreatable = diff --git a/src/style.css b/src/style.css index 8e3114a..2ad1471 100644 --- a/src/style.css +++ b/src/style.css @@ -173,6 +173,18 @@ animation: dash 1.5s ease-in-out infinite; } +.rmsc__arrow-svg { + fill: none; + stroke: currentColor; + stroke-width: 2; +} + +.rmsc__cross-svg { + fill: none; + stroke: currentColor; + stroke-width: 2; +} + @keyframes rotate { 100% { transform: rotate(360deg);