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.
Files changed (58) hide show
  1. package/lib/components/AllProjectsDropdown/AllProjectsDropdown.d.ts +3 -0
  2. package/lib/components/AllProjectsDropdown/AllProjectsDropdown.stories.d.ts +6 -0
  3. package/lib/components/AllProjectsDropdown/index.d.ts +1 -0
  4. package/lib/components/AppHeader/AppHeader.d.ts +4 -0
  5. package/lib/components/AppHeader/AppHeader.stories.d.ts +7 -0
  6. package/lib/components/AppHeader/index.d.ts +1 -0
  7. package/lib/components/AppHeader/types.d.ts +26 -0
  8. package/lib/components/Input/types.d.ts +1 -1
  9. package/lib/components/InputWithDropdown/types.d.ts +1 -1
  10. package/lib/components/Modal/types.d.ts +2 -0
  11. package/lib/components/MultiSelect/MultiSelect.d.ts +1 -1
  12. package/lib/components/MultiSelect/MultiSelect.stories.d.ts +1 -0
  13. package/lib/components/MultiSelect/MultiSelectTypes.d.ts +3 -0
  14. package/lib/index.d.ts +67 -4
  15. package/lib/index.esm.js +440 -89
  16. package/lib/index.esm.js.map +1 -1
  17. package/lib/index.js +442 -88
  18. package/lib/index.js.map +1 -1
  19. package/lib/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +1 -1
  21. package/src/assets/Themes/BaseTheme.scss +5 -0
  22. package/src/assets/Themes/DarkTheme.scss +3 -0
  23. package/src/assets/icons/all_projects.svg +3 -0
  24. package/src/assets/icons/android_icon.svg +6 -0
  25. package/src/assets/icons/download_icon.svg +4 -0
  26. package/src/assets/icons/fireflink_icon.svg +4 -0
  27. package/src/assets/icons/fireflink_logo.svg +13 -0
  28. package/src/assets/icons/mobile_icon.svg +3 -0
  29. package/src/assets/icons/ms_dynamic.svg +4 -0
  30. package/src/assets/icons/sales_force.svg +7 -0
  31. package/src/assets/icons/switch_license_icon.svg +123 -0
  32. package/src/assets/icons/vertical_separator.svg +3 -0
  33. package/src/assets/icons/web&mobile_icon.svg +3 -0
  34. package/src/assets/icons/web_icon.svg +3 -0
  35. package/src/assets/styles/_colors.scss +2 -1
  36. package/src/components/AllProjectsDropdown/AllProjectsDropdown.scss +70 -0
  37. package/src/components/AllProjectsDropdown/AllProjectsDropdown.stories.tsx +21 -0
  38. package/src/components/AllProjectsDropdown/AllProjectsDropdown.tsx +148 -0
  39. package/src/components/AllProjectsDropdown/index.ts +1 -0
  40. package/src/components/AppHeader/AppHeader.scss +67 -0
  41. package/src/components/AppHeader/AppHeader.stories.tsx +156 -0
  42. package/src/components/AppHeader/AppHeader.tsx +124 -0
  43. package/src/components/AppHeader/index.ts +1 -0
  44. package/src/components/AppHeader/types.ts +27 -0
  45. package/src/components/Icon/iconList.ts +26 -0
  46. package/src/components/IconButton/IconButton.scss +2 -1
  47. package/src/components/Input/types.ts +1 -1
  48. package/src/components/InputWithDropdown/types.ts +1 -3
  49. package/src/components/Modal/Modal.stories.tsx +6 -2
  50. package/src/components/Modal/Modal.tsx +6 -2
  51. package/src/components/Modal/modal.scss +6 -4
  52. package/src/components/Modal/types.ts +2 -0
  53. package/src/components/MultiSelect/MultiSelect.scss +8 -1
  54. package/src/components/MultiSelect/MultiSelect.stories.tsx +26 -0
  55. package/src/components/MultiSelect/MultiSelect.tsx +68 -12
  56. package/src/components/MultiSelect/MultiSelectTypes.ts +3 -0
  57. package/src/components/Select/Select.scss +1 -1
  58. 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">{footerContent}</div>
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: 549px;
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: 448px;
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: 549px;
29
+ max-width: 100%;
28
30
  width: 100%;
29
31
  height: 32px;
30
32
  border-radius: 0 0 12px 12px;
@@ -34,4 +34,6 @@ export interface ModalProps {
34
34
  /***Content to be displayed inside the modal */
35
35
  children: ReactNode;
36
36
  isFooterDisplayed: boolean;
37
+ customWidth: string;
38
+ customHeight?: string;
37
39
  }
@@ -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: 36px;
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
- {selectedOptions.map((option) => (
201
- <ChipElement
202
- key={option?.label}
203
- label={option?.label || ''}
204
- onChipCloseClick={(e) => handleChipCloseClick(option, e)}
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 };
@@ -112,7 +112,7 @@
112
112
  @include absolute-position(6px, auto, auto, auto);
113
113
  width: calc(100% - 38px);
114
114
  min-height: calc(100% - 8px);
115
- padding: 0 8px;
115
+ padding: 0 28px 0 8px;
116
116
  border-radius: 4px;
117
117
  border: 1px solid transparent;
118
118
  z-index: 100;
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,