Skip to content

Commit

Permalink
feat: Added new props to control clear search icon, support for icons…
Browse files Browse the repository at this point in the history
… as js… (#9)
  • Loading branch information
bilta-keyvalue authored Dec 22, 2023
1 parent 7286a81 commit 72c5daf
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 83 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
Arrow?: url || JSX.Element,
ChipClose?: url || JSX.Element,
Checked?: url || JSX.Element,
ClearSearch?: url || JSX.Element,
Search?: 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 showDefault = 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"
/>
showDefault ?
<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
43 changes: 25 additions & 18 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 @@ -131,15 +132,18 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
[options, selectedIds]
);

const onClick = (): void =>{
setShowAllChips(true);
setIsModalVisible(true);
}

return (
<div className={classes.container} style={styles[Elements.Container]}>
<div
className={`${classes.box} ${hasError && classes.errorBorder}`}
ref={intractableAreaRef}
style={styles[Elements.InputBox]}
onClick={(): void => {
setShowAllChips(true);
}}
onClick={onClick}
role="presentation"
>
<div className={classes.headSection}>
Expand All @@ -161,11 +165,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 +179,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
10 changes: 10 additions & 0 deletions src/lib/multi-select/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
padding: 0px 12px;
align-items: center;
position: relative;
flex: 1
}
.searchInput {
width: 100%;
Expand All @@ -15,6 +16,8 @@
font-size: inherit;
font-family: "Poppins";
background-color: inherit;
text-overflow: ellipsis;
min-width: 25px;
}
.icon {
width: 16px;
Expand Down Expand Up @@ -87,6 +90,9 @@
opacity: 0.7;
}
}
.selectedItem{
background-color: #D9E2F0;
}
.checkbox {
width: 20px;
height: 20px;
Expand Down Expand Up @@ -118,6 +124,7 @@
display: flex;
flex-wrap: wrap;
gap: 10px;
flex: 1
}
.error {
color: #ff0000;
Expand All @@ -137,3 +144,6 @@
.elevatedContent {
z-index: 1;
}
.label {
padding: 10px
}
Loading

0 comments on commit 72c5daf

Please sign in to comment.