Skip to content

Commit

Permalink
Added new props to control clear search icon, support for icons as js…
Browse files Browse the repository at this point in the history
…x, bug fixesrun coverage
  • Loading branch information
bilta-keyvalue committed Dec 20, 2023
1 parent 7286a81 commit ebbb8bf
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 58 deletions.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,13 @@ Provides you with an object to replace the default icons used.
</td>
<td><code>undefined</code></td>
</tr>
<tr>
<td><code><b>clearSearchClick?:</b> function</code></td>
<td>
The callback function which will be triggered on clicking close icon inside search box
</td>
<td><code>undefined</code></td>
</tr>
</tbody>
</table>

Expand All @@ -257,6 +264,7 @@ the below code shows all the overridable styles:
SearchIcon?: {...styles},
ArrowIcon?: {...styles},
HiddenChipsIndicator?: {...styles},
ClearSearchIcon?: {....styles},
SelectedMenuItem?: (id) => ({...styles}),
UnSelectedMenuItem?: (id) => ({...styles}),
ChipComponent?: (id) => ({...styles}),
Expand All @@ -270,9 +278,10 @@ To customize the style of various components, you can use the following prop nam
- `Container`: Overrides the style of the multi-selection UI container.
- `CheckedIcon`: Overrides the style of the checked icon.
- `ChipCloseIcon`: Overrides the style of the close icon within the chip.
- `ClearSearchIcon`: Overrides the style of the close icon within the search box.
- `HelperText`: Overrides the style of the helper text.
- `HiddenChipsIndicator`: Overrides the style of the bubble indicating the number of hidden chips if the thresholdForBubble prop has a value.
- `InputBox`: Overrides the style of the box containing the chips and search bar.
- `InputBox`: Overrides the style of the box containing the chips and search bar. Can be used to style the placeholder if the search is hidden.
- `SearchIcon`: Overrides the style of the search icon.
- `SearchComponent`: Overrides the styles of the search component.
- `UnCheckedIcon`: Overrides the style of the unchecked box.
Expand All @@ -292,15 +301,17 @@ The following code displays the icons that can be customized
<MultiSelection
options={optionsArray}
icons={{
Search?: url,
ChipClose?: url,
Checked?: url,
Arrow?: url
Search?: url || JSX.Element,
ChipClose?: url || JSX.Element,
Checked?: url || JSX.Element,
Arrow?: url || JSX.Element,
ClearSearch?: url || JSX.Element
}}
/>
```

- `Arrow` - Overrides the down arrow(right)
- `ChipClose` - Overrides the chip close icon
- `Checked` - Overrides the checkbox checked icon
- `ClearSearch` - Overrides the close icon inside search box
- `Search` - Overrides the search icon
16 changes: 9 additions & 7 deletions src/lib/multi-select/chips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { MouseEvent, useMemo } from "react";
import closeIcon from "../../assets/x.svg";
import { ChipListPropType, OptionType } from "./types";
import { Elements, ElementsWithCallableStyle } from "./constants";
import { getStyles } from "./utils/utils";
import { getStyles, renderAsImage } from "./utils/utils";
import classes from "./styles.module.scss";

const Chips = (props: ChipListPropType): JSX.Element => {
Expand Down Expand Up @@ -50,12 +50,14 @@ const Chips = (props: ChipListPropType): JSX.Element => {
onClick(e, item.id)
}
>
<img
src={icon ?? closeIcon}
alt=""
className={classes.chipClose}
style={styles[Elements.ChipCloseIcon]}
/>
{renderAsImage(icon) ?
<img
src={icon as string ?? closeIcon}
alt=""
className={classes.chipClose}
style={styles[Elements.ChipCloseIcon]}
/>:
icon}
</button>
</div>
)
Expand Down
1 change: 1 addition & 0 deletions src/lib/multi-select/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum Elements {
SearchIcon = "SearchIcon",
ArrowIcon = "ArrowIcon",
HiddenChipsIndicator = "HiddenChipsIndicator",
ClearSearchIcon = "ClearSearchIcon"
}

export enum ElementsWithCallableStyle {
Expand Down
30 changes: 18 additions & 12 deletions src/lib/multi-select/menuItems.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { getStyles } from "./utils/utils";
import React, { useMemo } from "react";
import CheckMark from "../../assets/CheckBox.svg";
import { getStyles, renderAsImage } from "./utils/utils";
import { ModalProps, OptionType } from "./types";
import {
DEFAULT_EMPTY_LIST_MESSAGE,
Expand All @@ -22,6 +23,9 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
onOptionClick,
styles = {}
} = props;

const renderOwnComponent = useMemo(()=> renderAsImage(icon), [icon]);

return (
<>
{list?.length && (list.length !== selectedIds.length || !hideSelected)
Expand All @@ -31,7 +35,7 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
!hideSelected) && (
<button
key={item.id}
className={classes.eachItem}
className={`${classes.eachItem} ${selectedIds.includes(item.id) && classes.selectedItem}`}
onClick={(): void => onOptionClick(item.id)}
style={getStyles(
!selectedIds.includes(item.id)
Expand All @@ -45,14 +49,16 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
>
{showCheckbox &&
(selectedIds.includes(item.id) ? (
<div
className={`${classes.checkbox} ${classes.icon}`}
style={{
backgroundImage: `url(${icon})`,
...styles[Elements.CheckedIcon]
}}
id="checked-checkbox"
/>
renderOwnComponent ?
<div
className={`${classes.checkbox} ${classes.icon}`}
style={{
backgroundImage: `url(${icon ?? CheckMark})`,
...styles[Elements.CheckedIcon]
}}
id="checked-checkbox"
/>
: icon
) : (
<div
className={`${classes.unchecked} ${classes.icon}`}
Expand All @@ -62,7 +68,7 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
))}
<div id="label">{item.name}</div>
</button>
)) || <></>
)) || <React.Fragment key={item.id}/>
)
: !isLoading &&
(renderEmptyItem || (
Expand Down
34 changes: 19 additions & 15 deletions src/lib/multi-select/multiSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { MouseEvent, useEffect, useMemo, useRef, useState } from "react";
import DownArrow from "../../assets/DropdownArrow.svg";
import CheckMark from "../../assets/CheckBox.svg";
import SearchComponent from "./searchComponent";
import { MultiSelectPropType, OptionType } from "./types";
import { DEFAULT_PLACEHOLDER, Elements } from "./constants";
import { renderAsImage } from "./utils/utils";
import classes from "./styles.module.scss";
import Chips from "./chips";
import MenuListing from "./menuItems";
Expand All @@ -28,11 +28,12 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
icons = {},
onSearch = undefined,
onItemClick = undefined,
setSelectedValues = undefined
setSelectedValues = undefined,
clearSearchClick = undefined
} = props;

const { Checked = CheckMark, Search, ChipClose, Arrow } = icons;

const { Checked, Search, ChipClose, Arrow, ClearSearch } = icons;
const arrowIcon = Arrow;
// to show/hide div containing the checkboxes
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [list, setList] = useState<OptionType[]>([]);
Expand All @@ -55,7 +56,7 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
}, [options]);

useEffect(() => {
if (typeof document!== undefined) {
if (typeof document!== 'undefined') {
document.addEventListener("mouseup", onMouseUp);
return () => document.removeEventListener("mouseup", onMouseUp);
}
Expand Down Expand Up @@ -161,11 +162,12 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
onFocus={triggerModalOpen}
ref={inputRef}
icon={Search}
onCloseClick={clearSearchClick}
closeIcon={ClearSearch}
/>
)}
{hideSearch && !selectedIds.length && (
// same style for the search box is used
<div style={styles[Elements.SearchComponent]}>{placeholder}</div>
<div className={`${classes.searchInput} ${classes.label}`}>{placeholder}</div>
)}
</div>
<button
Expand All @@ -174,14 +176,16 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
onClick={(e: MouseEvent<HTMLButtonElement>): void => onArrowClick(e)}
id="down-arrow"
>
<img
src={Arrow ?? DownArrow}
className={classes.rotation}
style={{
transform: `rotate(${isModalVisible ? "180deg" : "0deg"})`,
...styles[Elements.ArrowIcon]
}}
/>
{renderAsImage(arrowIcon)?
<img
src={arrowIcon as string ?? DownArrow}
className={classes.rotation}
style={{
transform: `rotate(${isModalVisible ? "180deg" : "0deg"})`,
...styles[Elements.ArrowIcon]
}}
/> :
arrowIcon}
</button>
</div>
{!isModalVisible && helperText && (
Expand Down
37 changes: 26 additions & 11 deletions src/lib/multi-select/searchComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { ForwardedRef, forwardRef, useEffect, useState } from "react";
import searchIcon from "../../assets/Search.svg";
import closeIcon from "../../assets/x-circle.svg";
import defaultCloseIcon from "../../assets/x-circle.svg";
import { renderAsImage } from './utils/utils'
import { SearchComponentPropType } from "./types";
import { Elements } from "./constants";
import classes from "./styles.module.scss";
Expand All @@ -9,24 +10,30 @@ const SearchComponent = (
props: SearchComponentPropType,
ref: ForwardedRef<HTMLInputElement>
): JSX.Element => {
const { onSearch, searchPlaceholder, styles = {}, onFocus, icon } = props;
const { onSearch, searchPlaceholder, styles = {}, onFocus, icon, onCloseClick, closeIcon } = props;
const [searchTerm, setSearchTerm] = useState<string>("");

useEffect(() => {
onSearch(searchTerm);
}, [searchTerm]);

const onCloseButtonClick = (): void =>{
setSearchTerm("");
if(onCloseClick) onCloseClick();
}

return (
<div
className={classes.searchContainer}
style={styles[Elements.SearchComponent]}
>
<img
src={icon ?? searchIcon}
alt=""
className={classes.chipClose}
style={styles[Elements.SearchIcon]}
/>
{renderAsImage(icon)?
<img
src={icon as string ?? searchIcon}
alt=""
className={classes.chipClose}
style={styles[Elements.SearchIcon]}
/>: icon}
<input
type="text"
onChange={(e): void => setSearchTerm(e.target.value)}
Expand All @@ -41,9 +48,17 @@ const SearchComponent = (
<button
id="clear-search-button"
className={`${classes.buttonIcon} ${classes.icon}`}
style={{ backgroundImage: `url(${closeIcon})` }}
onClick={(): void => setSearchTerm("")}
/>
onClick={onCloseButtonClick}
>
{renderAsImage(closeIcon)?
<img
src={closeIcon as string ?? defaultCloseIcon}
alt=""
className={classes.chipClose}
style={styles[Elements.ClearSearchIcon]}
/> :
closeIcon}
</button>
)}
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions src/lib/multi-select/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
font-size: inherit;
font-family: "Poppins";
background-color: inherit;
text-overflow: ellipsis;
}
.icon {
width: 16px;
Expand Down Expand Up @@ -87,6 +88,9 @@
opacity: 0.7;
}
}
.selectedItem{
background-color: #D9E2F0;
}
.checkbox {
width: 20px;
height: 20px;
Expand Down Expand Up @@ -137,3 +141,6 @@
.elevatedContent {
z-index: 1;
}
.label {
padding: 10px
}
19 changes: 12 additions & 7 deletions src/lib/multi-select/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface MultiSelectPropType {
onSearch?: (value: string) => void;
onItemClick?: (id: string | number) => void;
setSelectedValues?: (ids: Array<string | number>) => void;
clearSearchClick?: () => void;
}

export type OptionType = {
Expand All @@ -30,15 +31,17 @@ export type SearchComponentPropType = {
searchPlaceholder: string;
styles?: StyleProp;
showChips?: boolean;
icon?: string;
icon?: string | JSX.Element;
onFocus: () => void;
onSearch: (value: string) => void;
onCloseClick?:() => void,
closeIcon?: string | JSX.Element
};

export interface ChipListPropType {
list: OptionType[];
styles?: StyleProp;
icon?: string;
icon?: string | JSX.Element;
onClick: (event: MouseEvent<HTMLButtonElement>, id: string | number) => void;
thresholdForBubble?: number;
showAllChips: boolean;
Expand All @@ -58,21 +61,23 @@ export type StyleProp = {
SearchIcon?: object;
ArrowIcon?: object;
HiddenChipsIndicator?: object;
ClearSearchIcon?: object;
};

export type IconsProps = {
Search?: string;
ChipClose?: string;
Checked?: string;
Arrow?: string;
Search?: string | JSX.Element;
ChipClose?: string | JSX.Element;
Checked?: string | JSX.Element;
Arrow?: string | JSX.Element;
ClearSearch?: string | JSX.Element;
};

export type ModalProps = {
list: OptionType[];
selectedIds: (string | number)[];
hideSelected: boolean;
showCheckbox: boolean;
icon: string;
icon?: string | JSX.Element;
isLoading: boolean;
renderEmptyItem?: JSX.Element;
renderLoader?: JSX.Element;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/multi-select/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ export const getStyles = (
styles: StyleProp,
id: string | number
): object => {
// id will be available for styles given to user as functions
const getElementStyle = styles[element];
if (getElementStyle) {
return getElementStyle(id);
}
return {};
};

export const renderAsImage = (icon?: JSX.Element | string): boolean => typeof icon === 'undefined' || typeof icon === 'string'
Loading

0 comments on commit ebbb8bf

Please sign in to comment.