pixel-react 1.0.8 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/components/AllProjectsDropdown/AllProjectsDropdown.d.ts +3 -0
- package/lib/components/AllProjectsDropdown/AllProjectsDropdown.stories.d.ts +6 -0
- package/lib/components/AllProjectsDropdown/index.d.ts +1 -0
- package/lib/components/AppHeader/AppHeader.d.ts +4 -0
- package/lib/components/AppHeader/AppHeader.stories.d.ts +7 -0
- package/lib/components/AppHeader/index.d.ts +1 -0
- package/lib/components/AppHeader/types.d.ts +26 -0
- package/lib/components/Input/types.d.ts +1 -1
- package/lib/components/InputWithDropdown/types.d.ts +1 -1
- package/lib/components/Modal/types.d.ts +2 -0
- package/lib/components/MultiSelect/MultiSelect.d.ts +1 -1
- package/lib/components/MultiSelect/MultiSelect.stories.d.ts +1 -0
- package/lib/components/MultiSelect/MultiSelectTypes.d.ts +3 -0
- package/lib/index.d.ts +67 -4
- package/lib/index.esm.js +440 -89
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +442 -88
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/assets/Themes/BaseTheme.scss +5 -0
- package/src/assets/Themes/DarkTheme.scss +3 -0
- package/src/assets/icons/all_projects.svg +3 -0
- package/src/assets/icons/android_icon.svg +6 -0
- package/src/assets/icons/download_icon.svg +4 -0
- package/src/assets/icons/fireflink_icon.svg +4 -0
- package/src/assets/icons/fireflink_logo.svg +13 -0
- package/src/assets/icons/mobile_icon.svg +3 -0
- package/src/assets/icons/ms_dynamic.svg +4 -0
- package/src/assets/icons/sales_force.svg +7 -0
- package/src/assets/icons/switch_license_icon.svg +123 -0
- package/src/assets/icons/vertical_separator.svg +3 -0
- package/src/assets/icons/web&mobile_icon.svg +3 -0
- package/src/assets/icons/web_icon.svg +3 -0
- package/src/assets/styles/_colors.scss +2 -1
- package/src/components/AllProjectsDropdown/AllProjectsDropdown.scss +70 -0
- package/src/components/AllProjectsDropdown/AllProjectsDropdown.stories.tsx +21 -0
- package/src/components/AllProjectsDropdown/AllProjectsDropdown.tsx +148 -0
- package/src/components/AllProjectsDropdown/index.ts +1 -0
- package/src/components/AppHeader/AppHeader.scss +67 -0
- package/src/components/AppHeader/AppHeader.stories.tsx +156 -0
- package/src/components/AppHeader/AppHeader.tsx +124 -0
- package/src/components/AppHeader/index.ts +1 -0
- package/src/components/AppHeader/types.ts +27 -0
- package/src/components/Icon/iconList.ts +26 -0
- package/src/components/IconButton/IconButton.scss +2 -1
- package/src/components/Input/types.ts +1 -1
- package/src/components/InputWithDropdown/types.ts +1 -3
- package/src/components/Modal/Modal.stories.tsx +6 -2
- package/src/components/Modal/Modal.tsx +6 -2
- package/src/components/Modal/modal.scss +6 -4
- package/src/components/Modal/types.ts +2 -0
- package/src/components/MultiSelect/MultiSelect.scss +8 -1
- package/src/components/MultiSelect/MultiSelect.stories.tsx +26 -0
- package/src/components/MultiSelect/MultiSelect.tsx +68 -12
- package/src/components/MultiSelect/MultiSelectTypes.ts +3 -0
- package/src/components/Select/Select.scss +1 -1
- package/src/index.ts +7 -0
@@ -4,7 +4,6 @@ import './modal.scss';
|
|
4
4
|
import { ModalProps } from './types';
|
5
5
|
import { ThemeContext } from '../ThemeProvider/ThemeProvider';
|
6
6
|
|
7
|
-
|
8
7
|
const Modal: React.FC<ModalProps> = ({
|
9
8
|
isOpen,
|
10
9
|
onClose,
|
@@ -18,6 +17,8 @@ const Modal: React.FC<ModalProps> = ({
|
|
18
17
|
shouldCloseOnEsc = true,
|
19
18
|
ariaHideApp = true,
|
20
19
|
shouldCloseOnOverlayClick = true,
|
20
|
+
customWidth = '660px', // default width
|
21
|
+
customHeight = 'auto', // default height
|
21
22
|
children,
|
22
23
|
}) => {
|
23
24
|
useEffect(() => {
|
@@ -52,6 +53,7 @@ const Modal: React.FC<ModalProps> = ({
|
|
52
53
|
>
|
53
54
|
<div
|
54
55
|
className={`ff-modal-content ${currentTheme} ${contentClassName || ''}`}
|
56
|
+
style={{ width: customWidth, height: customHeight }}
|
55
57
|
onClick={(e) => e.stopPropagation()}
|
56
58
|
aria-label={contentLabel}
|
57
59
|
>
|
@@ -61,7 +63,9 @@ const Modal: React.FC<ModalProps> = ({
|
|
61
63
|
{children}
|
62
64
|
</div>
|
63
65
|
{isFooterDisplayed && (
|
64
|
-
<div className="ff-modal-footer"
|
66
|
+
<div className="ff-modal-footer" style={{ width: customWidth }}>
|
67
|
+
{footerContent}
|
68
|
+
</div>
|
65
69
|
)}
|
66
70
|
</div>,
|
67
71
|
document.body
|
@@ -1,4 +1,5 @@
|
|
1
1
|
@use '../../assets/styles/mixins' as *;
|
2
|
+
|
2
3
|
.ff-modal-overlay {
|
3
4
|
position: fixed;
|
4
5
|
top: 0;
|
@@ -13,18 +14,19 @@
|
|
13
14
|
.ff-modal-content {
|
14
15
|
background: var(--ff-mini-modal-border);
|
15
16
|
position: relative;
|
16
|
-
max-width:
|
17
|
-
width: 100%;
|
17
|
+
max-width: 100%;
|
18
18
|
border-radius: 12px 12px 0 0;
|
19
19
|
padding: 16px;
|
20
|
+
|
20
21
|
.ff-modal-header {
|
21
22
|
height: 32px;
|
22
|
-
width:
|
23
|
+
width: 100%;
|
23
24
|
}
|
24
25
|
}
|
26
|
+
|
25
27
|
.ff-modal-footer {
|
26
28
|
background-color: var(--expandable-menu-option-bg);
|
27
|
-
max-width:
|
29
|
+
max-width: 100%;
|
28
30
|
width: 100%;
|
29
31
|
height: 32px;
|
30
32
|
border-radius: 0 0 12px 12px;
|
@@ -108,6 +108,13 @@
|
|
108
108
|
}
|
109
109
|
}
|
110
110
|
}
|
111
|
+
.ff-multiselect-more-chip{
|
112
|
+
width: 1rem;
|
113
|
+
@extend .fontXs;
|
114
|
+
font-weight: 600;
|
115
|
+
line-height: 16px;
|
116
|
+
color: var(--brand-color);
|
117
|
+
}
|
111
118
|
}
|
112
119
|
}
|
113
120
|
&__toggle {
|
@@ -182,7 +189,7 @@
|
|
182
189
|
.error-text {
|
183
190
|
@extend .font-size-8;
|
184
191
|
position: absolute;
|
185
|
-
top:
|
192
|
+
margin-top: 0.25rem;
|
186
193
|
left: 12px;
|
187
194
|
color: var(--error-light);
|
188
195
|
letter-spacing: 0.5px;
|
@@ -52,6 +52,32 @@ export const Default3: Story = {
|
|
52
52
|
...defaultArgs,
|
53
53
|
},
|
54
54
|
};
|
55
|
+
export const EmailGroup: Story = {
|
56
|
+
render: () => {
|
57
|
+
const [options] = useState([
|
58
|
+
{ label: 'Sample1@gmail.com', value: 'sample1@gmail.com' },
|
59
|
+
{ label: 'Sample2@gmail.com', value: 'sample2@gmail.com' },
|
60
|
+
]);
|
61
|
+
const [selectedOptions, setSelectedOptions] = useState<
|
62
|
+
{ label?: string; value?: string }[]
|
63
|
+
>([{ label: 'Sample1@gmail.com', value: 'sample1@gmail.com'}]);
|
64
|
+
const onChange = (options: { label?: string; value?: string }[]) => {
|
65
|
+
setSelectedOptions(options);
|
66
|
+
};
|
67
|
+
return (
|
68
|
+
<MultiSelect
|
69
|
+
label={'Enter Email'}
|
70
|
+
type='email'
|
71
|
+
required
|
72
|
+
options={options}
|
73
|
+
selectedOptions={selectedOptions}
|
74
|
+
onChange={onChange}
|
75
|
+
acceptNewOption={true}
|
76
|
+
displayCount={true}
|
77
|
+
/>
|
78
|
+
);
|
79
|
+
},
|
80
|
+
};
|
55
81
|
|
56
82
|
export const Controlled: Story = {
|
57
83
|
render: () => {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
2
2
|
import { createPortal } from 'react-dom';
|
3
3
|
import classNames from 'classnames';
|
4
4
|
import './MultiSelect.scss';
|
@@ -30,8 +30,10 @@ const ChipElement = ({
|
|
30
30
|
};
|
31
31
|
const MultiSelect = ({
|
32
32
|
options,
|
33
|
+
type = "text",
|
33
34
|
selectedOptions = [],
|
34
35
|
onChange = () => {},
|
36
|
+
acceptNewOption = false,
|
35
37
|
zIndex = 100,
|
36
38
|
label = '',
|
37
39
|
onSearch = () => {},
|
@@ -40,6 +42,7 @@ const MultiSelect = ({
|
|
40
42
|
errorMessage = 'Fill this field',
|
41
43
|
withSelectButton = false,
|
42
44
|
onSelect = () => {},
|
45
|
+
displayCount = false
|
43
46
|
}: MultiSelectProps) => {
|
44
47
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
45
48
|
const [allOptions, setAllOptions] = useState(options);
|
@@ -47,6 +50,7 @@ const MultiSelect = ({
|
|
47
50
|
const [searchedKeyword, setSearchedKeyword] = useState('');
|
48
51
|
const [isSelectFocusedOnce, setIsSelectFocusedOnce] =
|
49
52
|
useState<boolean>(false);
|
53
|
+
const [inputError, setInputError] = useState<string>('')
|
50
54
|
|
51
55
|
const [dropdownPosition, setDropdownPosition] = useState<{
|
52
56
|
top: number;
|
@@ -67,6 +71,9 @@ const MultiSelect = ({
|
|
67
71
|
const selectWrapper = useRef<HTMLInputElement>(null);
|
68
72
|
let isFieldSkipped = isSelectFocusedOnce && selectedOptions.length === 0;
|
69
73
|
|
74
|
+
const maxVisibleChips = 2;
|
75
|
+
const hiddenCount = selectedOptions.length - maxVisibleChips;
|
76
|
+
|
70
77
|
const handleClick = () => {
|
71
78
|
if (!isOpen) {
|
72
79
|
setIsOpen(true);
|
@@ -102,6 +109,32 @@ const MultiSelect = ({
|
|
102
109
|
e.stopPropagation();
|
103
110
|
handleOptionChange(option, false);
|
104
111
|
};
|
112
|
+
const handleKeyEnter = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
113
|
+
if (acceptNewOption && e.key === "Enter") {
|
114
|
+
setInputError('');
|
115
|
+
if (type === "email") {
|
116
|
+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
117
|
+
if (!emailPattern.test(searchedKeyword)) {
|
118
|
+
setIsOpen(false);
|
119
|
+
setInputError("Please enter a valid email address.");
|
120
|
+
return;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
const newOption = {
|
125
|
+
label: searchedKeyword,
|
126
|
+
value: searchedKeyword.toLowerCase(),
|
127
|
+
isChecked: true,
|
128
|
+
};
|
129
|
+
const filteredOptions = [...allOptions].filter(option => option.isChecked === true);
|
130
|
+
|
131
|
+
setAllOptions([...allOptions, newOption]);
|
132
|
+
setSearchedKeyword('');
|
133
|
+
onChange?.([...filteredOptions, { label: searchedKeyword, value: searchedKeyword.toLocaleLowerCase() }]);
|
134
|
+
setIsOpen(false);
|
135
|
+
}
|
136
|
+
};
|
137
|
+
|
105
138
|
const calculatePosition = () => {
|
106
139
|
if (dropdownWrapper.current && selectWrapper.current) {
|
107
140
|
const rect = dropdownWrapper.current?.getBoundingClientRect();
|
@@ -159,6 +192,7 @@ const MultiSelect = ({
|
|
159
192
|
!dropdownRef.current.contains(event?.target as Node) &&
|
160
193
|
!selectWrapper.current.contains(event?.target as Node)
|
161
194
|
) {
|
195
|
+
setInputError('')
|
162
196
|
setIsOpen(false);
|
163
197
|
if (!isSelectFocusedOnce) {
|
164
198
|
setIsSelectFocusedOnce(true);
|
@@ -179,7 +213,7 @@ const MultiSelect = ({
|
|
179
213
|
className={classNames('ff-multiselect-wrapper', {
|
180
214
|
'ff-multiselect-wrapper--with-options': selectedOptions?.length,
|
181
215
|
'ff-multiselect-wrapper--opened-dropdown': isOpen,
|
182
|
-
'ff-multiselect-wrapper--error': isFieldSkipped && required,
|
216
|
+
'ff-multiselect-wrapper--error': (isFieldSkipped && required) || inputError,
|
183
217
|
'ff-multiselect-wrapper--disabled': disabled,
|
184
218
|
})}
|
185
219
|
>
|
@@ -197,15 +231,28 @@ const MultiSelect = ({
|
|
197
231
|
{label}
|
198
232
|
</span>
|
199
233
|
<div className="ff-multiselect-chip-container">
|
200
|
-
{
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
234
|
+
{displayCount ?
|
235
|
+
<>
|
236
|
+
{selectedOptions.slice(0, maxVisibleChips).map((option) => (
|
237
|
+
<ChipElement
|
238
|
+
key={option?.label}
|
239
|
+
label={option?.label || ''}
|
240
|
+
onChipCloseClick={(e) => handleChipCloseClick(option, e)}
|
241
|
+
/>
|
242
|
+
))}
|
243
|
+
</> :
|
244
|
+
selectedOptions.map((option) => (
|
245
|
+
<ChipElement
|
246
|
+
key={option?.label}
|
247
|
+
label={option?.label || ''}
|
248
|
+
onChipCloseClick={(e) => handleChipCloseClick(option, e)}
|
249
|
+
/>
|
250
|
+
))
|
251
|
+
}
|
207
252
|
<div className="ff-multiselect-input-container">
|
208
253
|
<input
|
254
|
+
value={searchedKeyword}
|
255
|
+
type={type}
|
209
256
|
autoComplete="off"
|
210
257
|
placeholder="search..."
|
211
258
|
ref={inputRef}
|
@@ -216,6 +263,7 @@ const MultiSelect = ({
|
|
216
263
|
setSearchedKeyword(e.target.value);
|
217
264
|
onSearch?.(e.target.value);
|
218
265
|
}}
|
266
|
+
onKeyDown={handleKeyEnter}
|
219
267
|
id="input-ele"
|
220
268
|
className="ff-select-input"
|
221
269
|
style={{
|
@@ -224,6 +272,14 @@ const MultiSelect = ({
|
|
224
272
|
}}
|
225
273
|
/>
|
226
274
|
</div>
|
275
|
+
{hiddenCount > 0 && (
|
276
|
+
<div
|
277
|
+
className="ff-multiselect-more-chip"
|
278
|
+
onClick={toggleDropdown}
|
279
|
+
>
|
280
|
+
+{hiddenCount}
|
281
|
+
</div>
|
282
|
+
)}
|
227
283
|
</div>
|
228
284
|
</div>
|
229
285
|
<div onClick={toggleDropdown} className="ff-multiselect__toggle">
|
@@ -238,8 +294,8 @@ const MultiSelect = ({
|
|
238
294
|
</div>
|
239
295
|
</div>
|
240
296
|
<div ref={dropdownWrapper}>
|
241
|
-
{isFieldSkipped && required && errorMessage && (
|
242
|
-
<div className="error-text">{errorMessage}</div>
|
297
|
+
{(inputError || (isFieldSkipped && required && errorMessage)) && (
|
298
|
+
<div className="error-text">{inputError || errorMessage }</div>
|
243
299
|
)}
|
244
300
|
{isOpen &&
|
245
301
|
createPortal(
|
@@ -261,4 +317,4 @@ const MultiSelect = ({
|
|
261
317
|
);
|
262
318
|
};
|
263
319
|
|
264
|
-
export default MultiSelect;
|
320
|
+
export default MultiSelect;
|
@@ -6,16 +6,19 @@ interface Option {
|
|
6
6
|
}
|
7
7
|
interface MultiSelectProps {
|
8
8
|
options: Option[];
|
9
|
+
type? : 'email' | 'text';
|
9
10
|
label: string;
|
10
11
|
selectedOptions?: Option[];
|
11
12
|
disabled?: boolean;
|
12
13
|
onSearch?: (searchedKeyword: string) => void;
|
13
14
|
onChange?: (selectedOptions: Option[]) => void;
|
15
|
+
acceptNewOption?: boolean;
|
14
16
|
zIndex?: number;
|
15
17
|
required?: boolean;
|
16
18
|
errorMessage?: string;
|
17
19
|
withSelectButton?: boolean;
|
18
20
|
onSelect?: () => void;
|
21
|
+
displayCount?:boolean;
|
19
22
|
}
|
20
23
|
|
21
24
|
export { Option, MultiSelectProps };
|
package/src/index.ts
CHANGED
@@ -36,7 +36,10 @@ import Search from './components/Search/Search';
|
|
36
36
|
import DatePicker from './components/DatePicker';
|
37
37
|
import StateDropdown from './components/StateDropdown';
|
38
38
|
import IconButton from './components/IconButton';
|
39
|
+
import Modal from './components/Modal';
|
39
40
|
import DragAndDrop from './components/DragAndDrop/DragAndDrop';
|
41
|
+
import AllProjectsDropdown from './components/AllProjectsDropdown';
|
42
|
+
import AppHeader from './components/AppHeader';
|
40
43
|
|
41
44
|
// Utils imports
|
42
45
|
import { checkEmpty } from './utils/checkEmpty/checkEmpty';
|
@@ -86,7 +89,11 @@ export {
|
|
86
89
|
StateDropdown,
|
87
90
|
StatusButton,
|
88
91
|
IconButton,
|
92
|
+
Modal,
|
93
|
+
|
89
94
|
DragAndDrop,
|
95
|
+
AllProjectsDropdown,
|
96
|
+
AppHeader,
|
90
97
|
|
91
98
|
// utils exports
|
92
99
|
checkEmpty,
|