pixel-react 1.2.0 → 1.2.1
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/InputWithDropdown/types.d.ts +1 -1
- package/lib/components/LabelEditTextField/LabelEditTextField.d.ts +5 -0
- package/lib/components/LabelEditTextField/LabelEditTextField.stories.d.ts +11 -0
- package/lib/components/LabelEditTextField/index.d.ts +1 -0
- package/lib/components/LabelEditTextField/types.d.ts +38 -0
- package/lib/components/Select/Select.d.ts +1 -1
- package/lib/components/Select/components/Dropdown/Dropdown.d.ts +1 -1
- package/lib/components/Select/components/Dropdown/dropdownTypes.d.ts +2 -0
- package/lib/components/Select/types.d.ts +11 -4
- package/lib/index.d.ts +56 -8
- package/lib/index.esm.js +318 -124
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +318 -123
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/getSelectOptionValue/getSelectOptionValue.d.ts +8 -0
- package/package.json +1 -1
- package/src/assets/Themes/BaseTheme.scss +8 -1
- package/src/assets/Themes/DarkTheme.scss +17 -8
- package/src/components/AppHeader/AppHeader.scss +5 -2
- package/src/components/Drawer/Drawer.scss +0 -1
- package/src/components/Drawer/Drawer.tsx +1 -1
- package/src/components/Icon/Icons.scss +1 -0
- package/src/components/InputWithDropdown/types.ts +1 -1
- package/src/components/LabelEditTextField/LabelEditTextField.scss +85 -0
- package/src/components/LabelEditTextField/LabelEditTextField.stories.tsx +136 -0
- package/src/components/LabelEditTextField/LabelEditTextField.tsx +207 -0
- package/src/components/LabelEditTextField/index.ts +1 -0
- package/src/components/LabelEditTextField/types.ts +38 -0
- package/src/components/Select/Select.stories.tsx +5 -3
- package/src/components/Select/Select.tsx +13 -5
- package/src/components/Select/components/Dropdown/Dropdown.tsx +3 -1
- package/src/components/Select/components/Dropdown/dropdownTypes.ts +3 -0
- package/src/components/Select/types.ts +12 -5
- package/src/index.ts +2 -0
- 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
|
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
|
-
|
180
|
-
|
181
|
-
|
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({
|
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
|
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
|
-
|
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
|
+
};
|