pixel-react 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,
|