pixel-react 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. package/lib/components/InputWithDropdown/types.d.ts +1 -1
  2. package/lib/components/LabelEditTextField/LabelEditTextField.d.ts +5 -0
  3. package/lib/components/LabelEditTextField/LabelEditTextField.stories.d.ts +11 -0
  4. package/lib/components/LabelEditTextField/index.d.ts +1 -0
  5. package/lib/components/LabelEditTextField/types.d.ts +38 -0
  6. package/lib/components/Select/Select.d.ts +1 -1
  7. package/lib/components/Select/components/Dropdown/Dropdown.d.ts +1 -1
  8. package/lib/components/Select/components/Dropdown/dropdownTypes.d.ts +2 -0
  9. package/lib/components/Select/types.d.ts +11 -4
  10. package/lib/index.d.ts +56 -8
  11. package/lib/index.esm.js +318 -124
  12. package/lib/index.esm.js.map +1 -1
  13. package/lib/index.js +318 -123
  14. package/lib/index.js.map +1 -1
  15. package/lib/tsconfig.tsbuildinfo +1 -1
  16. package/lib/utils/getSelectOptionValue/getSelectOptionValue.d.ts +8 -0
  17. package/package.json +1 -1
  18. package/src/assets/Themes/BaseTheme.scss +8 -1
  19. package/src/assets/Themes/DarkTheme.scss +17 -8
  20. package/src/components/AppHeader/AppHeader.scss +5 -2
  21. package/src/components/Drawer/Drawer.scss +0 -1
  22. package/src/components/Drawer/Drawer.tsx +1 -1
  23. package/src/components/Icon/Icons.scss +1 -0
  24. package/src/components/InputWithDropdown/types.ts +1 -1
  25. package/src/components/LabelEditTextField/LabelEditTextField.scss +85 -0
  26. package/src/components/LabelEditTextField/LabelEditTextField.stories.tsx +136 -0
  27. package/src/components/LabelEditTextField/LabelEditTextField.tsx +207 -0
  28. package/src/components/LabelEditTextField/index.ts +1 -0
  29. package/src/components/LabelEditTextField/types.ts +38 -0
  30. package/src/components/Select/Select.stories.tsx +5 -3
  31. package/src/components/Select/Select.tsx +13 -5
  32. package/src/components/Select/components/Dropdown/Dropdown.tsx +3 -1
  33. package/src/components/Select/components/Dropdown/dropdownTypes.ts +3 -0
  34. package/src/components/Select/types.ts +12 -5
  35. package/src/index.ts +2 -0
  36. package/src/utils/getSelectOptionValue/getSelectOptionValue.ts +31 -0
@@ -0,0 +1,207 @@
1
+ import React, { useState, useRef, useEffect, FC } from 'react';
2
+ import './LabelEditTextField.scss';
3
+ import { LabelEditTextFieldTypes } from './types';
4
+ import Typography from '../Typography';
5
+ import HighlightText from '../HighlightText';
6
+ import Icon from '../Icon';
7
+
8
+ const getErrorMessage = (
9
+ inputValue: string,
10
+ text: string,
11
+ customError?: string
12
+ ): string => {
13
+ if (inputValue === text) {
14
+ return 'No changes were made.';
15
+ } else if (!inputValue) {
16
+ return 'Text is required';
17
+ } else if (inputValue.length < 3) {
18
+ return 'Please enter at least 3 characters.';
19
+ } else if (customError) {
20
+ return customError;
21
+ }
22
+ return '';
23
+ };
24
+ const LabelEditTextField: FC<LabelEditTextFieldTypes> = ({
25
+ label,
26
+ text,
27
+ highlightText,
28
+ customError,
29
+ confirmIcon,
30
+ cancelIcon,
31
+ variant = 'textField',
32
+ dropdownData = [],
33
+ width = '300px',
34
+ height = '22px',
35
+ confirmAction,
36
+ }) => {
37
+ const [isEditing, setIsEditing] = useState(false);
38
+ const [inputValue, setInputValue] = useState(text);
39
+ const [dropdownValue, setDropdownValue] = useState(
40
+ dropdownData[0]?.value ?? ''
41
+ );
42
+ const [showError, setShowError] = useState('');
43
+ const [isTextFieldModified, setIsTextFieldModified] = useState(false);
44
+ const [isDropdownModified, setIsDropdownModified] = useState(false);
45
+ const containerRef = useRef<HTMLDivElement | null>(null);
46
+ const cancelRef = useRef<HTMLDivElement | null>(null); // New ref for cancel icon
47
+
48
+ const handleDoubleClick = () => {
49
+ setIsEditing(true);
50
+ setShowError('');
51
+ };
52
+
53
+ const handleConfirm = () => {
54
+ const errorMessage = getErrorMessage(inputValue, text, customError);
55
+
56
+ if (errorMessage && isEditing) {
57
+ setShowError(errorMessage);
58
+ } else {
59
+ setIsEditing(false);
60
+ setShowError('');
61
+ if (confirmAction) confirmAction(inputValue, dropdownValue);
62
+ }
63
+ };
64
+
65
+ const handleCancel = () => {
66
+ setInputValue(text);
67
+ setDropdownValue(dropdownData[0]?.value ?? '');
68
+ setIsEditing(false);
69
+ setShowError('');
70
+ setIsTextFieldModified(false);
71
+ setIsDropdownModified(false);
72
+ };
73
+
74
+ const handleTextFieldChange = (e: React.ChangeEvent<HTMLInputElement>) => {
75
+ setInputValue(e.target.value);
76
+ setIsTextFieldModified(true);
77
+ setShowError('');
78
+ };
79
+
80
+ const handleDropdownChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
81
+ setDropdownValue(e.target.value);
82
+ setIsDropdownModified(true);
83
+ setShowError('');
84
+ };
85
+
86
+ const handleBlur = (e: MouseEvent) => {
87
+ if (
88
+ containerRef.current &&
89
+ !containerRef.current.contains(e.target as Node) &&
90
+ cancelRef.current !== e.target // Exclude clicks on cancel icon
91
+ ) {
92
+ const errorMessage = getErrorMessage(inputValue, text, customError);
93
+
94
+ if (errorMessage && isEditing) {
95
+ setShowError(errorMessage);
96
+ } else {
97
+ setIsEditing(false);
98
+ setShowError('');
99
+ }
100
+ }
101
+ };
102
+
103
+ useEffect(() => {
104
+ document.addEventListener('click', handleBlur);
105
+ return () => {
106
+ document.removeEventListener('click', handleBlur);
107
+ };
108
+ }, [inputValue]);
109
+
110
+ return (
111
+ <div
112
+ className="ff-label-edit-text-field"
113
+ ref={containerRef}
114
+ style={{ width }}
115
+ >
116
+ {isEditing ? (
117
+ <div className="ff-label-text-field">
118
+ {variant === 'textFieldWithDropdown' ? (
119
+ <div
120
+ className={`ff-label-text-field-with-dropdown ${
121
+ isEditing ? 'open' : ''
122
+ }`}
123
+ style={{ height }}
124
+ >
125
+ <input
126
+ type="text"
127
+ value={inputValue}
128
+ onChange={handleTextFieldChange}
129
+ className={`ff-text-dropdown-field ${
130
+ isTextFieldModified ? 'modified' : ''
131
+ }`}
132
+ placeholder=" "
133
+ style={{
134
+ width,
135
+ }}
136
+ />
137
+ {label && <label className="ff-label">{label}</label>}
138
+ <select
139
+ value={dropdownValue}
140
+ onChange={handleDropdownChange}
141
+ className={`dropdown ${isDropdownModified ? 'modified' : ''}`}
142
+ >
143
+ {dropdownData.map((item) => (
144
+ <option key={item.id} value={item.value}>
145
+ {item.label}
146
+ </option>
147
+ ))}
148
+ </select>
149
+ </div>
150
+ ) : (
151
+ <div className="ff-label-text-field-without-dropdown">
152
+ <input
153
+ type="text"
154
+ value={inputValue}
155
+ onChange={handleTextFieldChange}
156
+ className={`ff-text-field ${
157
+ isTextFieldModified ? 'modified' : ''
158
+ }`}
159
+ placeholder=" "
160
+ style={{ width, height }}
161
+ />
162
+ <label className="ff-textfield-label">{label}</label>
163
+ </div>
164
+ )}
165
+ <div className="ff-icon-container">
166
+ {confirmIcon && (
167
+ <Icon
168
+ color="var(--label-edit-confirm-icon)"
169
+ height={20}
170
+ width={20}
171
+ name={confirmIcon.name}
172
+ className="confirm-icon"
173
+ onClick={handleConfirm}
174
+ />
175
+ )}
176
+ {cancelIcon && (
177
+ <Icon
178
+ color="var(--label-edit-cancel-icon)"
179
+ height={12}
180
+ width={20}
181
+ name={cancelIcon.name}
182
+ className="cancel-icon"
183
+ onClick={handleCancel}
184
+ ref={cancelRef}
185
+ />
186
+ )}
187
+ </div>
188
+ </div>
189
+ ) : (
190
+ <span
191
+ className="display-text"
192
+ onDoubleClick={handleDoubleClick}
193
+ role="button"
194
+ >
195
+ <HighlightText text={inputValue} highlight={highlightText} />
196
+ </span>
197
+ )}
198
+ {showError && isEditing && (
199
+ <Typography as="p" fontSize={8} className="error-text">
200
+ {showError}
201
+ </Typography>
202
+ )}
203
+ </div>
204
+ );
205
+ };
206
+
207
+ export default LabelEditTextField;
@@ -0,0 +1 @@
1
+ export { default } from './LabelEditTextField';
@@ -0,0 +1,38 @@
1
+ export interface IconProps {
2
+ /** Name of the icon to be displayed. */
3
+ name: string;
4
+ /** Optional click handler function for the icon. */
5
+ onClick?: () => void;
6
+ }
7
+ export interface DropdownOption {
8
+ /** Unique identifier for the dropdown option. */
9
+ id: number;
10
+ /** Value associated with the dropdown option. */
11
+ value: string;
12
+ /** Label displayed for the dropdown option. */
13
+ label: string;
14
+ }
15
+ export interface LabelEditTextFieldTypes {
16
+ /** Label text displayed above the input field. */
17
+ label?: string;
18
+ /** Initial text displayed in the input field. */
19
+ text: string;
20
+ /** Text to be highlighted within the displayed text, if provided. */
21
+ highlightText?: string;
22
+ /** Custom error message to be displayed, if applicable. */
23
+ customError?: string;
24
+ /** Confirm icon properties including icon name and click handler. */
25
+ confirmIcon?: IconProps;
26
+ /** Cancel icon properties including icon name and click handler. */
27
+ cancelIcon?: IconProps;
28
+ /** Type of input field - standard text field or text field with a dropdown. */
29
+ variant?: 'textFieldWithDropdown' | 'textField';
30
+ /** Array of dropdown options used if the dropdown variant is selected. */
31
+ dropdownData?: DropdownOption[];
32
+ /** Width of the input field component. */
33
+ width?: string;
34
+ /** Height of the input field component. */
35
+ height?: string;
36
+ /** Function called when confirming input changes, with input and dropdown values as arguments. */
37
+ confirmAction?: (inputValue: string, dropdownValue: string) => void;
38
+ }
@@ -17,10 +17,12 @@ type Story = StoryObj<typeof Select>;
17
17
  export const Primary: Story = {
18
18
  args: {
19
19
  label: 'Select',
20
+ labelAccessor: 'name',
21
+ valueAccessor: 'value',
20
22
  optionsList: [
21
- { label: 'Option 1', value: '1' },
22
- { label: 'Option 2', value: '2' },
23
- { label: 'Option 3', value: '3' },
23
+ { label: 'Option 1', value: '1', name: 'abcd' },
24
+ { label: 'Option 2', value: '2', name: '123' },
25
+ { label: 'Option 3', value: '3', name: '456' },
24
26
  ],
25
27
  },
26
28
  };
@@ -8,6 +8,7 @@ import Icon from '../Icon';
8
8
  import './Select.scss';
9
9
  import usePortalPosition from '../../hooks/usePortalPosition';
10
10
  import Typography from '../Typography';
11
+ import { getValue } from '../../utils/getSelectOptionValue/getSelectOptionValue';
11
12
 
12
13
  const selectReducer = (
13
14
  state: SelectState,
@@ -116,6 +117,8 @@ const Select = ({
116
117
  required = false,
117
118
  optionsRequired = true,
118
119
  selectedOptionColor = 'var(--ff-select-text-color)',
120
+ labelAccessor,
121
+ valueAccessor,
119
122
  }: SelectProps) => {
120
123
  const initialState: SelectState = useMemo(
121
124
  () => ({
@@ -164,7 +167,7 @@ const Select = ({
164
167
  if (actionType === 'SHOW_ERROR' || actionType === 'BLUR_INPUT') {
165
168
  dispatch({
166
169
  type: actionType,
167
- payload: { optionsList, option: selectedOption.value },
170
+ payload: { optionsList, option: getValue(selectedOption) },
168
171
  });
169
172
  } else {
170
173
  dispatch({ type: actionType });
@@ -176,9 +179,10 @@ const Select = ({
176
179
  if (disabled) return;
177
180
  const { value } = e.target;
178
181
  const filteredOptions = optionsList.filter((option) => {
179
- return typeof option.value === 'string'
180
- ? option.value.toLowerCase().includes(value.toLowerCase().trim())
181
- : option.value === Number(value);
182
+ const valueData = getValue(option, valueAccessor);
183
+ return typeof valueData === 'string'
184
+ ? valueData.toLowerCase().includes(value.toLowerCase().trim())
185
+ : valueData === Number(value);
182
186
  });
183
187
  dispatch({ type: 'UPDATE_OPTION_LIST', payload: filteredOptions });
184
188
  dispatch({ type: 'UPDATE_OPTION', payload: value });
@@ -197,7 +201,10 @@ const Select = ({
197
201
  const onSelectOptionSelector = (option: Option) => {
198
202
  if (!disabled) {
199
203
  onSelectBlur();
200
- dispatch({ type: 'UPDATE_OPTION', payload: option.value });
204
+ dispatch({
205
+ type: 'UPDATE_OPTION',
206
+ payload: getValue(option, valueAccessor),
207
+ });
201
208
  if (onChange) {
202
209
  onChange(option);
203
210
  }
@@ -361,6 +368,7 @@ const Select = ({
361
368
  options={options}
362
369
  optionZIndex={optionZIndex}
363
370
  inputRef={InputRef}
371
+ labelAccessor={labelAccessor}
364
372
  />,
365
373
  document.body
366
374
  )}
@@ -7,6 +7,7 @@ import Typography from '../../../Typography';
7
7
  import { ffid } from '../../../../utils/ffID/ffid';
8
8
  import { ThemeContext } from '../../../ThemeProvider/ThemeProvider';
9
9
  import classNames from 'classnames';
10
+ import { getLabel } from '../../../../utils/getSelectOptionValue/getSelectOptionValue';
10
11
 
11
12
  const Dropdown = ({
12
13
  onSelectBlur,
@@ -15,6 +16,7 @@ const Dropdown = ({
15
16
  options = [],
16
17
  optionZIndex = 100,
17
18
  inputRef,
19
+ labelAccessor,
18
20
  }: DropDownListProps) => {
19
21
  const themeContext = useContext(ThemeContext);
20
22
  const currentTheme = themeContext?.currentTheme;
@@ -71,7 +73,7 @@ const Dropdown = ({
71
73
  color="var(--ff-select-text-color)"
72
74
  onClick={() => onSelectOptionSelector(option)}
73
75
  >
74
- {option.label}
76
+ {getLabel(option, labelAccessor)}
75
77
  </Typography>
76
78
  ))
77
79
  ) : (
@@ -7,6 +7,8 @@ export interface DropDownListProps {
7
7
  options?: Option[];
8
8
  optionZIndex?: number;
9
9
  inputRef?: React.RefObject<HTMLInputElement>;
10
+ labelAccessor?: string;
11
+ valueAccessor?: string;
10
12
  }
11
13
 
12
14
  export const dropdownDefaultCSSData = {
@@ -17,3 +19,4 @@ export const dropdownDefaultCSSData = {
17
19
  // Future use case if we provide padding-top, padding-bottom for option wrapper
18
20
  dropDownWrapperPadding: 0,
19
21
  };
22
+
@@ -1,5 +1,3 @@
1
- import { ReactNode } from 'react';
2
-
3
1
  export interface SelectProps {
4
2
  /*
5
3
  * Label for the select dropdown
@@ -69,6 +67,15 @@ export interface SelectProps {
69
67
  * selectedOptionColor prop provides the custom color for the selected option
70
68
  */
71
69
  selectedOptionColor?: string;
70
+
71
+ /**
72
+ * Label accessor
73
+ */
74
+ labelAccessor?: string;
75
+ /**
76
+ * Value accessor
77
+ */
78
+ valueAccessor?: string;
72
79
  }
73
80
 
74
81
  export interface DrowdownPosition {
@@ -124,8 +131,8 @@ export type SelectAction =
124
131
  };
125
132
  };
126
133
 
134
+ type OptionValue = any;
135
+
127
136
  export interface Option {
128
- label: string | ReactNode;
129
- value: string;
130
- disabled?: boolean;
137
+ [key: string]: OptionValue;
131
138
  }
package/src/index.ts CHANGED
@@ -50,6 +50,7 @@ import IconRadioGroup from './components/IconRadioGroup';
50
50
  import MachineInputField from './components/MachineInputField';
51
51
  import SequentialConnectingBranch from './components/SequentialConnectingBranch';
52
52
  import AttachmentButton from './components/AttachmentButton';
53
+ import LabelEditTextField from './components/LabelEditTextField';
53
54
 
54
55
  // Utils imports
55
56
  import { checkEmpty } from './utils/checkEmpty/checkEmpty';
@@ -120,6 +121,7 @@ export {
120
121
  MachineInputField,
121
122
  SequentialConnectingBranch,
122
123
  AttachmentButton,
124
+ LabelEditTextField,
123
125
  IconRadioGroup,
124
126
 
125
127
  // utils exports
@@ -0,0 +1,31 @@
1
+ type DynamicValues = any;
2
+
3
+ interface dynamicObject {
4
+ [key: string]: DynamicValues;
5
+ }
6
+
7
+ type accessorType = string | undefined;
8
+
9
+ export const getLabel = (
10
+ option: dynamicObject,
11
+ accessor: accessorType = ''
12
+ ) => {
13
+ if (!accessor) {
14
+ if (option.hasOwnProperty('label')) {
15
+ return option.label;
16
+ }
17
+ }
18
+ return option[accessor];
19
+ };
20
+
21
+ export const getValue = (
22
+ option: dynamicObject,
23
+ accessor: accessorType = ''
24
+ ) => {
25
+ if (!accessor) {
26
+ if (option.hasOwnProperty('value')) {
27
+ return option.label;
28
+ }
29
+ }
30
+ return option[accessor];
31
+ };