Skip to content

Commit 72c5daf

Browse files
feat: Added new props to control clear search icon, support for icons as js… (#9)
1 parent 7286a81 commit 72c5daf

File tree

10 files changed

+166
-83
lines changed

10 files changed

+166
-83
lines changed

README.md

+16-5
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ Provides you with an object to replace the default icons used.
235235
</td>
236236
<td><code>undefined</code></td>
237237
</tr>
238+
<tr>
239+
<td><code><b>clearSearchClick?:</b> function</code></td>
240+
<td>
241+
The callback function which will be triggered on clicking close icon inside search box
242+
</td>
243+
<td><code>undefined</code></td>
244+
</tr>
238245
</tbody>
239246
</table>
240247

@@ -257,6 +264,7 @@ the below code shows all the overridable styles:
257264
SearchIcon?: {...styles},
258265
ArrowIcon?: {...styles},
259266
HiddenChipsIndicator?: {...styles},
267+
ClearSearchIcon?: {...styles},
260268
SelectedMenuItem?: (id) => ({...styles}),
261269
UnSelectedMenuItem?: (id) => ({...styles}),
262270
ChipComponent?: (id) => ({...styles}),
@@ -270,9 +278,10 @@ To customize the style of various components, you can use the following prop nam
270278
- `Container`: Overrides the style of the multi-selection UI container.
271279
- `CheckedIcon`: Overrides the style of the checked icon.
272280
- `ChipCloseIcon`: Overrides the style of the close icon within the chip.
281+
- `ClearSearchIcon`: Overrides the style of the close icon within the search box.
273282
- `HelperText`: Overrides the style of the helper text.
274283
- `HiddenChipsIndicator`: Overrides the style of the bubble indicating the number of hidden chips if the thresholdForBubble prop has a value.
275-
- `InputBox`: Overrides the style of the box containing the chips and search bar.
284+
- `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.
276285
- `SearchIcon`: Overrides the style of the search icon.
277286
- `SearchComponent`: Overrides the styles of the search component.
278287
- `UnCheckedIcon`: Overrides the style of the unchecked box.
@@ -292,15 +301,17 @@ The following code displays the icons that can be customized
292301
<MultiSelection
293302
options={optionsArray}
294303
icons={{
295-
Search?: url,
296-
ChipClose?: url,
297-
Checked?: url,
298-
Arrow?: url
304+
Arrow?: url || JSX.Element,
305+
ChipClose?: url || JSX.Element,
306+
Checked?: url || JSX.Element,
307+
ClearSearch?: url || JSX.Element,
308+
Search?: url || JSX.Element
299309
}}
300310
/>
301311
```
302312

303313
- `Arrow` - Overrides the down arrow(right)
304314
- `ChipClose` - Overrides the chip close icon
305315
- `Checked` - Overrides the checkbox checked icon
316+
- `ClearSearch` - Overrides the close icon inside search box
306317
- `Search` - Overrides the search icon

src/lib/multi-select/chips.tsx

+9-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { MouseEvent, useMemo } from "react";
22
import closeIcon from "../../assets/x.svg";
33
import { ChipListPropType, OptionType } from "./types";
44
import { Elements, ElementsWithCallableStyle } from "./constants";
5-
import { getStyles } from "./utils/utils";
5+
import { getStyles, renderAsImage } from "./utils/utils";
66
import classes from "./styles.module.scss";
77

88
const Chips = (props: ChipListPropType): JSX.Element => {
@@ -50,12 +50,14 @@ const Chips = (props: ChipListPropType): JSX.Element => {
5050
onClick(e, item.id)
5151
}
5252
>
53-
<img
54-
src={icon ?? closeIcon}
55-
alt=""
56-
className={classes.chipClose}
57-
style={styles[Elements.ChipCloseIcon]}
58-
/>
53+
{renderAsImage(icon) ?
54+
<img
55+
src={icon as string ?? closeIcon}
56+
alt=""
57+
className={classes.chipClose}
58+
style={styles[Elements.ChipCloseIcon]}
59+
/>:
60+
icon}
5961
</button>
6062
</div>
6163
)

src/lib/multi-select/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export enum Elements {
1515
SearchIcon = "SearchIcon",
1616
ArrowIcon = "ArrowIcon",
1717
HiddenChipsIndicator = "HiddenChipsIndicator",
18+
ClearSearchIcon = "ClearSearchIcon"
1819
}
1920

2021
export enum ElementsWithCallableStyle {

src/lib/multi-select/menuItems.tsx

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import React from "react";
2-
import { getStyles } from "./utils/utils";
1+
import React, { useMemo } from "react";
2+
import CheckMark from "../../assets/CheckBox.svg";
3+
import { getStyles, renderAsImage } from "./utils/utils";
34
import { ModalProps, OptionType } from "./types";
45
import {
56
DEFAULT_EMPTY_LIST_MESSAGE,
@@ -22,6 +23,9 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
2223
onOptionClick,
2324
styles = {}
2425
} = props;
26+
27+
const showDefault = useMemo(()=> renderAsImage(icon), [icon]);
28+
2529
return (
2630
<>
2731
{list?.length && (list.length !== selectedIds.length || !hideSelected)
@@ -31,7 +35,7 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
3135
!hideSelected) && (
3236
<button
3337
key={item.id}
34-
className={classes.eachItem}
38+
className={`${classes.eachItem} ${selectedIds.includes(item.id) && classes.selectedItem}`}
3539
onClick={(): void => onOptionClick(item.id)}
3640
style={getStyles(
3741
!selectedIds.includes(item.id)
@@ -45,14 +49,16 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
4549
>
4650
{showCheckbox &&
4751
(selectedIds.includes(item.id) ? (
48-
<div
49-
className={`${classes.checkbox} ${classes.icon}`}
50-
style={{
51-
backgroundImage: `url(${icon})`,
52-
...styles[Elements.CheckedIcon]
53-
}}
54-
id="checked-checkbox"
55-
/>
52+
showDefault ?
53+
<div
54+
className={`${classes.checkbox} ${classes.icon}`}
55+
style={{
56+
backgroundImage: `url(${icon ?? CheckMark})`,
57+
...styles[Elements.CheckedIcon]
58+
}}
59+
id="checked-checkbox"
60+
/>
61+
: icon
5662
) : (
5763
<div
5864
className={`${classes.unchecked} ${classes.icon}`}
@@ -62,7 +68,7 @@ const OptionListingModal = (props: ModalProps): JSX.Element => {
6268
))}
6369
<div id="label">{item.name}</div>
6470
</button>
65-
)) || <></>
71+
)) || <React.Fragment key={item.id}/>
6672
)
6773
: !isLoading &&
6874
(renderEmptyItem || (

src/lib/multi-select/multiSelect.tsx

+25-18
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, { MouseEvent, useEffect, useMemo, useRef, useState } from "react";
22
import DownArrow from "../../assets/DropdownArrow.svg";
3-
import CheckMark from "../../assets/CheckBox.svg";
43
import SearchComponent from "./searchComponent";
54
import { MultiSelectPropType, OptionType } from "./types";
65
import { DEFAULT_PLACEHOLDER, Elements } from "./constants";
6+
import { renderAsImage } from "./utils/utils";
77
import classes from "./styles.module.scss";
88
import Chips from "./chips";
99
import MenuListing from "./menuItems";
@@ -28,11 +28,12 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
2828
icons = {},
2929
onSearch = undefined,
3030
onItemClick = undefined,
31-
setSelectedValues = undefined
31+
setSelectedValues = undefined,
32+
clearSearchClick = undefined
3233
} = props;
3334

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

5758
useEffect(() => {
58-
if (typeof document!== undefined) {
59+
if (typeof document!== 'undefined') {
5960
document.addEventListener("mouseup", onMouseUp);
6061
return () => document.removeEventListener("mouseup", onMouseUp);
6162
}
@@ -131,15 +132,18 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
131132
[options, selectedIds]
132133
);
133134

135+
const onClick = (): void =>{
136+
setShowAllChips(true);
137+
setIsModalVisible(true);
138+
}
139+
134140
return (
135141
<div className={classes.container} style={styles[Elements.Container]}>
136142
<div
137143
className={`${classes.box} ${hasError && classes.errorBorder}`}
138144
ref={intractableAreaRef}
139145
style={styles[Elements.InputBox]}
140-
onClick={(): void => {
141-
setShowAllChips(true);
142-
}}
146+
onClick={onClick}
143147
role="presentation"
144148
>
145149
<div className={classes.headSection}>
@@ -161,11 +165,12 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
161165
onFocus={triggerModalOpen}
162166
ref={inputRef}
163167
icon={Search}
168+
onCloseClick={clearSearchClick}
169+
closeIcon={ClearSearch}
164170
/>
165171
)}
166172
{hideSearch && !selectedIds.length && (
167-
// same style for the search box is used
168-
<div style={styles[Elements.SearchComponent]}>{placeholder}</div>
173+
<div className={`${classes.searchInput} ${classes.label}`}>{placeholder}</div>
169174
)}
170175
</div>
171176
<button
@@ -174,14 +179,16 @@ const MultiSelect = (props: MultiSelectPropType): JSX.Element => {
174179
onClick={(e: MouseEvent<HTMLButtonElement>): void => onArrowClick(e)}
175180
id="down-arrow"
176181
>
177-
<img
178-
src={Arrow ?? DownArrow}
179-
className={classes.rotation}
180-
style={{
181-
transform: `rotate(${isModalVisible ? "180deg" : "0deg"})`,
182-
...styles[Elements.ArrowIcon]
183-
}}
184-
/>
182+
{renderAsImage(arrowIcon)?
183+
<img
184+
src={arrowIcon as string ?? DownArrow}
185+
className={classes.rotation}
186+
style={{
187+
transform: `rotate(${isModalVisible ? "180deg" : "0deg"})`,
188+
...styles[Elements.ArrowIcon]
189+
}}
190+
/> :
191+
arrowIcon}
185192
</button>
186193
</div>
187194
{!isModalVisible && helperText && (

src/lib/multi-select/searchComponent.tsx

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { ForwardedRef, forwardRef, useEffect, useState } from "react";
22
import searchIcon from "../../assets/Search.svg";
3-
import closeIcon from "../../assets/x-circle.svg";
3+
import defaultCloseIcon from "../../assets/x-circle.svg";
4+
import { renderAsImage } from './utils/utils'
45
import { SearchComponentPropType } from "./types";
56
import { Elements } from "./constants";
67
import classes from "./styles.module.scss";
@@ -9,24 +10,30 @@ const SearchComponent = (
910
props: SearchComponentPropType,
1011
ref: ForwardedRef<HTMLInputElement>
1112
): JSX.Element => {
12-
const { onSearch, searchPlaceholder, styles = {}, onFocus, icon } = props;
13+
const { onSearch, searchPlaceholder, styles = {}, onFocus, icon, onCloseClick, closeIcon } = props;
1314
const [searchTerm, setSearchTerm] = useState<string>("");
1415

1516
useEffect(() => {
1617
onSearch(searchTerm);
1718
}, [searchTerm]);
1819

20+
const onCloseButtonClick = (): void => {
21+
setSearchTerm("");
22+
if(onCloseClick) onCloseClick();
23+
}
24+
1925
return (
2026
<div
2127
className={classes.searchContainer}
2228
style={styles[Elements.SearchComponent]}
2329
>
24-
<img
25-
src={icon ?? searchIcon}
26-
alt=""
27-
className={classes.chipClose}
28-
style={styles[Elements.SearchIcon]}
29-
/>
30+
{renderAsImage(icon)?
31+
<img
32+
src={icon as string ?? searchIcon}
33+
alt=""
34+
className={classes.chipClose}
35+
style={styles[Elements.SearchIcon]}
36+
/>: icon}
3037
<input
3138
type="text"
3239
onChange={(e): void => setSearchTerm(e.target.value)}
@@ -41,9 +48,17 @@ const SearchComponent = (
4148
<button
4249
id="clear-search-button"
4350
className={`${classes.buttonIcon} ${classes.icon}`}
44-
style={{ backgroundImage: `url(${closeIcon})` }}
45-
onClick={(): void => setSearchTerm("")}
46-
/>
51+
onClick={onCloseButtonClick}
52+
>
53+
{renderAsImage(closeIcon)?
54+
<img
55+
src={closeIcon as string ?? defaultCloseIcon}
56+
alt=""
57+
className={classes.chipClose}
58+
style={styles[Elements.ClearSearchIcon]}
59+
/> :
60+
closeIcon}
61+
</button>
4762
)}
4863
</div>
4964
);

src/lib/multi-select/styles.module.scss

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
padding: 0px 12px;
77
align-items: center;
88
position: relative;
9+
flex: 1
910
}
1011
.searchInput {
1112
width: 100%;
@@ -15,6 +16,8 @@
1516
font-size: inherit;
1617
font-family: "Poppins";
1718
background-color: inherit;
19+
text-overflow: ellipsis;
20+
min-width: 25px;
1821
}
1922
.icon {
2023
width: 16px;
@@ -87,6 +90,9 @@
8790
opacity: 0.7;
8891
}
8992
}
93+
.selectedItem{
94+
background-color: #D9E2F0;
95+
}
9096
.checkbox {
9197
width: 20px;
9298
height: 20px;
@@ -118,6 +124,7 @@
118124
display: flex;
119125
flex-wrap: wrap;
120126
gap: 10px;
127+
flex: 1
121128
}
122129
.error {
123130
color: #ff0000;
@@ -137,3 +144,6 @@
137144
.elevatedContent {
138145
z-index: 1;
139146
}
147+
.label {
148+
padding: 10px
149+
}

0 commit comments

Comments
 (0)