@true-engineering/true-react-common-ui-kit 3.8.1 → 3.9.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/LICENSE +201 -201
- package/README.md +10 -0
- package/dist/components/Checkbox/Checkbox.d.ts +2 -2
- package/dist/components/NewMoreMenu/NewMoreMenu.styles.d.ts +3 -1
- package/dist/components/ScrollIntoViewIfNeeded/ScrollIntoViewIfNeeded.d.ts +1 -1
- package/dist/components/Select/Select.d.ts +4 -4
- package/dist/components/Select/components/SelectList/SelectList.d.ts +5 -6
- package/dist/components/Select/components/SelectListItem/SelectListItem.d.ts +2 -2
- package/dist/components/WithPopup/WithPopup.styles.d.ts +1 -1
- package/dist/true-react-common-ui-kit.js +111 -86
- package/dist/true-react-common-ui-kit.js.map +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs +111 -86
- package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/components/AccountInfo/AccountInfo.stories.tsx +32 -32
- package/src/components/AccountInfo/AccountInfo.tsx +80 -80
- package/src/components/AddButton/AddButton.stories.tsx +21 -21
- package/src/components/AddButton/AddButton.tsx +52 -52
- package/src/components/Button/Button.stories.tsx +56 -56
- package/src/components/Button/Button.tsx +129 -129
- package/src/components/Checkbox/Checkbox.stories.tsx +28 -28
- package/src/components/Checkbox/Checkbox.tsx +7 -4
- package/src/components/CloseButton/CloseButton.tsx +34 -34
- package/src/components/Colors/Colors.stories.tsx +7 -7
- package/src/components/DateInput/DateInput.tsx +90 -90
- package/src/components/DateInput/constants.ts +2 -2
- package/src/components/DatePicker/DatePicker.tsx +308 -308
- package/src/components/Description/Description.stories.tsx +27 -27
- package/src/components/Description/Description.tsx +61 -61
- package/src/components/FiltersPane/FiltersPane.tsx +158 -158
- package/src/components/FiltersPane/components/Filter/Filter.tsx +203 -203
- package/src/components/FiltersPane/components/FilterValueView/FilterValueView.tsx +166 -166
- package/src/components/FiltersPane/components/FilterWithDates/FilterWithDates.tsx +210 -210
- package/src/components/FiltersPane/components/FilterWithPeriod/FilterWithPeriod.tsx +177 -177
- package/src/components/FiltersPane/components/FilterWrapper/FilterWrapper.tsx +167 -167
- package/src/components/Flag/Flag.stories.tsx +29 -29
- package/src/components/Flag/Flag.tsx +26 -26
- package/src/components/Flag/augment.d.ts +1 -1
- package/src/components/FlexibleTable/FlexibleTable.stories.tsx +267 -267
- package/src/components/FlexibleTable/FlexibleTable.styles.ts +110 -110
- package/src/components/FlexibleTable/FlexibleTable.tsx +271 -271
- package/src/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.styles.ts +38 -38
- package/src/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.tsx +83 -83
- package/src/components/FlexibleTable/components/FlexibleTableRow/FlexibleTableRow.styles.ts +25 -25
- package/src/components/FlexibleTable/components/FlexibleTableRow/FlexibleTableRow.tsx +196 -196
- package/src/components/FlexibleTable/helpers.ts +13 -13
- package/src/components/FlexibleTable/types.ts +52 -52
- package/src/components/Icon/Icon.stories.tsx +86 -86
- package/src/components/Icon/complexIcons/augment.d.ts +1 -1
- package/src/components/Icon/complexIcons/avatarGreen.svg +57 -57
- package/src/components/Icon/complexIcons/index.ts +1 -1
- package/src/components/IncrementInput/IncrementInput.tsx +105 -105
- package/src/components/Input/Input.tsx +297 -297
- package/src/components/Input/types.ts +32 -32
- package/src/components/List/List.stories.tsx +70 -70
- package/src/components/List/List.tsx +33 -33
- package/src/components/List/components/ListItem/ListItem.tsx +57 -57
- package/src/components/Modal/Modal.stories.tsx +105 -105
- package/src/components/Modal/Modal.tsx +196 -196
- package/src/components/MoreMenu/MoreMenu.styles.ts +68 -68
- package/src/components/MultiSelect/MultiSelect.stories.tsx +46 -46
- package/src/components/MultiSelect/MultiSelect.tsx +106 -106
- package/src/components/MultiSelect/components/MultiSelectInput/MultiSelectInput.tsx +53 -53
- package/src/components/MultiSelectList/MultiSelectList.tsx +461 -461
- package/src/components/NewMoreMenu/NewMoreMenu.styles.ts +5 -5
- package/src/components/NewMoreMenu/NewMoreMenu.tsx +15 -1
- package/src/components/Notification/Notification.stories.tsx +46 -46
- package/src/components/Notification/Notification.tsx +69 -69
- package/src/components/NumberInput/NumberInput.tsx +137 -137
- package/src/components/NumberInput/index.ts +1 -1
- package/src/components/PhoneInput/PhoneInput.tsx +214 -214
- package/src/components/PhoneInput/components/PhoneInputCountryList/PhoneInputCountryList.tsx +155 -155
- package/src/components/PhoneInput/types.ts +16 -16
- package/src/components/RadioButton/RadioButton.stories.tsx +46 -46
- package/src/components/RadioButton/RadioButton.tsx +57 -57
- package/src/components/ScrollIntoViewIfNeeded/index.ts +1 -1
- package/src/components/Select/CustomSelect.stories.tsx +217 -217
- package/src/components/Select/MultiSelect.stories.tsx +240 -240
- package/src/components/Select/Select.stories.tsx +235 -235
- package/src/components/Select/Select.tsx +57 -28
- package/src/components/Select/components/SelectList/SelectList.tsx +8 -9
- package/src/components/Select/components/SelectListItem/SelectListItem.tsx +7 -3
- package/src/components/Select/constants.ts +2 -2
- package/src/components/Select/types.ts +1 -1
- package/src/components/Selector/Selector.stories.tsx +62 -62
- package/src/components/Selector/Selector.styles.ts +164 -164
- package/src/components/Selector/Selector.tsx +115 -115
- package/src/components/Selector/index.ts +2 -2
- package/src/components/Selector/types.ts +12 -12
- package/src/components/Skeleton/Skeleton.stories.tsx +19 -19
- package/src/components/SmartInput/SmartInput.tsx +134 -134
- package/src/components/Status/Status.stories.tsx +73 -73
- package/src/components/Status/Status.styles.ts +143 -143
- package/src/components/Status/Status.tsx +49 -49
- package/src/components/Status/constants.ts +11 -11
- package/src/components/Status/index.ts +3 -3
- package/src/components/Status/types.ts +5 -5
- package/src/components/Switch/Switch.stories.tsx +40 -40
- package/src/components/Switch/Switch.tsx +75 -75
- package/src/components/TextArea/TextArea.tsx +180 -180
- package/src/components/TextButton/TextButton.stories.tsx +46 -46
- package/src/components/TextButton/TextButton.styles.ts +129 -129
- package/src/components/TextButton/TextButton.tsx +103 -103
- package/src/components/TextButton/index.ts +4 -4
- package/src/components/TextWithInfo/TextWithInfo.stories.tsx +53 -53
- package/src/components/TextWithInfo/TextWithInfo.tsx +62 -62
- package/src/components/TextWithTooltip/TextWithTooltip.stories.tsx +58 -58
- package/src/components/ThemedPreloader/ThemedPreloader.stories.tsx +41 -41
- package/src/components/ThemedPreloader/ThemedPreloader.tsx +54 -54
- package/src/components/ThemedPreloader/components/DefaultPreloader/index.ts +1 -1
- package/src/components/Toaster/Toaster.stories.tsx +30 -30
- package/src/components/Toaster/Toaster.tsx +108 -108
- package/src/components/Tooltip/Tooltip.stories.tsx +19 -19
- package/src/components/Tooltip/Tooltip.tsx +35 -35
- package/src/components/Tooltip/types.ts +1 -1
- package/src/components/WithPopup/WithPopup.styles.ts +4 -0
- package/src/components/WithPopup/WithPopup.tsx +6 -1
- package/src/helpers/popper-helpers.ts +17 -17
- package/src/hooks/use-dropdown.ts +84 -84
- package/src/hooks/use-is-mounted.ts +15 -15
- package/src/theme/helpers.ts +76 -76
- package/src/vite-env.d.ts +1 -1
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
import { ReactNode, useEffect, useState } from 'react';
|
|
2
|
-
import { isStringNotEmpty } from '@true-engineering/true-react-platform-helpers';
|
|
3
|
-
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
4
|
-
import { Select, ISelectProps } from './Select';
|
|
5
|
-
|
|
6
|
-
interface ObjectValue {
|
|
7
|
-
name: string;
|
|
8
|
-
age: number;
|
|
9
|
-
isDisabled?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const inlineStyles = [undefined, 'left', 'right', 'middle'];
|
|
13
|
-
const borders: Array<ISelectProps<any>['border']> = [undefined, 'left', 'top', 'right', 'bottom'];
|
|
14
|
-
const errorPositions: Array<ISelectProps<any>['errorPosition']> = ['bottom', 'top'];
|
|
15
|
-
|
|
16
|
-
const genLetters = (qnt = 1): string =>
|
|
17
|
-
Math.random()
|
|
18
|
-
.toString(36)
|
|
19
|
-
.replace(/[^a-z]+/g, '')
|
|
20
|
-
.substr(0, qnt);
|
|
21
|
-
|
|
22
|
-
const convertObjectToString = (v: ObjectValue): string => v.name;
|
|
23
|
-
|
|
24
|
-
const convertObjectToId = (v: ObjectValue): string => `${v.name}${v.age}`;
|
|
25
|
-
|
|
26
|
-
const convertObjectToReactNode = (v: ObjectValue, isDisabled: boolean): ReactNode => (
|
|
27
|
-
<span style={{ color: isDisabled ? 'red' : undefined }}>
|
|
28
|
-
<i>{v.name}</i>, {v.age}
|
|
29
|
-
</span>
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const convertStringToReactNode = (v: string): ReactNode => <i>{v}</i>;
|
|
33
|
-
|
|
34
|
-
const isOptionDisabled = (option: string) => option.startsWith('Опция');
|
|
35
|
-
const isObjectOptionDisabled = (option: ObjectValue) => option.age > 30;
|
|
36
|
-
|
|
37
|
-
const stringOptions = [
|
|
38
|
-
'Опция 1',
|
|
39
|
-
'Опция 11',
|
|
40
|
-
'Еще одна опция',
|
|
41
|
-
'Еще выбор',
|
|
42
|
-
'Очень длинная опция вот такой текст',
|
|
43
|
-
'1',
|
|
44
|
-
'2',
|
|
45
|
-
'3',
|
|
46
|
-
'4',
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
const objectOptions: ObjectValue[] = [
|
|
50
|
-
{ name: 'Ivan', age: 34 },
|
|
51
|
-
{ name: 'Ivan', age: 42 },
|
|
52
|
-
{ name: 'Konstantin', age: 11 },
|
|
53
|
-
{ name: 'Mikhail', age: 24 },
|
|
54
|
-
{ name: 'Maria', age: 45, isDisabled: true },
|
|
55
|
-
{ name: 'Elena', age: 14 },
|
|
56
|
-
{ name: 'Artem', age: 23 },
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
// Максимум не включается, минимум включается
|
|
60
|
-
const getRandomInt = (min: number, max: number) =>
|
|
61
|
-
Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min))) + Math.ceil(min);
|
|
62
|
-
|
|
63
|
-
interface ISelectWithCustomProps<T> extends ISelectProps<T> {
|
|
64
|
-
valuesType: 'strings' | 'objects';
|
|
65
|
-
shouldUseReactNodes?: boolean;
|
|
66
|
-
shouldUsePopper?: boolean;
|
|
67
|
-
shouldRenderInBody?: boolean;
|
|
68
|
-
shouldHideOnScroll?: boolean;
|
|
69
|
-
shouldUseCustomIsDisabledFunction?: boolean;
|
|
70
|
-
shouldRenderSearchInputInList?: boolean;
|
|
71
|
-
canBeFlipped?: boolean;
|
|
72
|
-
scrollParent?: 'document' | 'auto';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function SelectWithCustomProps<T>({
|
|
76
|
-
valuesType,
|
|
77
|
-
optionsMode,
|
|
78
|
-
shouldUseReactNodes,
|
|
79
|
-
shouldUsePopper,
|
|
80
|
-
shouldRenderInBody,
|
|
81
|
-
shouldHideOnScroll,
|
|
82
|
-
shouldUseCustomIsDisabledFunction,
|
|
83
|
-
shouldRenderSearchInputInList,
|
|
84
|
-
canBeFlipped,
|
|
85
|
-
scrollParent,
|
|
86
|
-
noMatchesLabel,
|
|
87
|
-
...rest
|
|
88
|
-
}: ISelectWithCustomProps<T>) {
|
|
89
|
-
const [stringValue, setStringValue] = useState<string>();
|
|
90
|
-
const stringHandler = (newValue?: string) => {
|
|
91
|
-
console.log('change');
|
|
92
|
-
setStringValue(newValue);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const [objectValue, setObjectValue] = useState<ObjectValue>();
|
|
96
|
-
const objectHandler = (newValue?: ObjectValue) => {
|
|
97
|
-
console.log('change');
|
|
98
|
-
setObjectValue(newValue);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const selectValuesType = valuesType;
|
|
102
|
-
const shouldRenderAsReactNodes = shouldUseReactNodes;
|
|
103
|
-
|
|
104
|
-
const getOptions = async (): Promise<Array<string | ObjectValue>> =>
|
|
105
|
-
new Promise((resolve) =>
|
|
106
|
-
setTimeout(() => {
|
|
107
|
-
resolve(
|
|
108
|
-
[...Array(10)].map((_) => {
|
|
109
|
-
if (selectValuesType === 'strings') {
|
|
110
|
-
return genLetters(getRandomInt(3, 10));
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
name: genLetters(getRandomInt(3, 10)),
|
|
114
|
-
age: getRandomInt(10, 70),
|
|
115
|
-
} as ObjectValue;
|
|
116
|
-
}),
|
|
117
|
-
);
|
|
118
|
-
}, 1000),
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
const [dynamicOptions, setDynamicOptions] = useState<Array<string | ObjectValue>>([]);
|
|
122
|
-
|
|
123
|
-
const handleOpen = () => {
|
|
124
|
-
console.log('isOpen');
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const handleBlur = () => {
|
|
128
|
-
console.log('blur');
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
const api = async () => {
|
|
133
|
-
setDynamicOptions(await getOptions());
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
api();
|
|
137
|
-
}, [selectValuesType]);
|
|
138
|
-
|
|
139
|
-
const props =
|
|
140
|
-
selectValuesType === 'strings'
|
|
141
|
-
? {
|
|
142
|
-
onChange: stringHandler,
|
|
143
|
-
value: stringValue,
|
|
144
|
-
options: optionsMode === 'dynamic' ? dynamicOptions : stringOptions,
|
|
145
|
-
convertValueToReactNode: shouldRenderAsReactNodes ? convertStringToReactNode : undefined,
|
|
146
|
-
isOptionDisabled: shouldUseCustomIsDisabledFunction ? isOptionDisabled : undefined,
|
|
147
|
-
}
|
|
148
|
-
: {
|
|
149
|
-
onChange: objectHandler,
|
|
150
|
-
value: objectValue,
|
|
151
|
-
options: optionsMode === 'dynamic' ? dynamicOptions : objectOptions,
|
|
152
|
-
convertValueToString: convertObjectToString,
|
|
153
|
-
convertValueToId: convertObjectToId,
|
|
154
|
-
convertValueToReactNode: shouldRenderAsReactNodes ? convertObjectToReactNode : undefined,
|
|
155
|
-
isOptionDisabled: shouldUseCustomIsDisabledFunction ? isObjectOptionDisabled : undefined,
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<Select
|
|
160
|
-
{...rest}
|
|
161
|
-
{...(props as unknown as ISelectProps<any>)}
|
|
162
|
-
{...(shouldRenderSearchInputInList && {
|
|
163
|
-
searchInput: { shouldRenderInList: true },
|
|
164
|
-
})}
|
|
165
|
-
noMatchesLabel={isStringNotEmpty(noMatchesLabel) ? noMatchesLabel : undefined}
|
|
166
|
-
optionsMode={optionsMode}
|
|
167
|
-
onType={async () => setDynamicOptions(await getOptions())}
|
|
168
|
-
onOpen={handleOpen}
|
|
169
|
-
onBlur={handleBlur}
|
|
170
|
-
dropdownOptions={{
|
|
171
|
-
shouldUsePopper,
|
|
172
|
-
shouldRenderInBody,
|
|
173
|
-
shouldHideOnScroll,
|
|
174
|
-
canBeFlipped,
|
|
175
|
-
scrollParent,
|
|
176
|
-
}}
|
|
177
|
-
/>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export default {
|
|
182
|
-
title: 'Controls/Select',
|
|
183
|
-
component: SelectWithCustomProps,
|
|
184
|
-
argTypes: {
|
|
185
|
-
debounceTime: {
|
|
186
|
-
control: { type: 'range', min: 0, max: 1000, step: 100 },
|
|
187
|
-
},
|
|
188
|
-
errorPosition: { control: 'inline-radio', options: errorPositions },
|
|
189
|
-
border: { control: 'inline-radio', options: borders },
|
|
190
|
-
inlineStyle: { control: 'select', options: inlineStyles },
|
|
191
|
-
optionsMode: {
|
|
192
|
-
control: 'inline-radio',
|
|
193
|
-
options: ['normal', 'search', 'dynamic'],
|
|
194
|
-
},
|
|
195
|
-
valuesType: {
|
|
196
|
-
control: 'inline-radio',
|
|
197
|
-
options: ['strings', 'objects'],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
} as ComponentMeta<typeof SelectWithCustomProps>;
|
|
201
|
-
|
|
202
|
-
const Template: ComponentStory<typeof SelectWithCustomProps> = (args) => (
|
|
203
|
-
<SelectWithCustomProps {...args} />
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
export const Default = Template.bind({});
|
|
207
|
-
|
|
208
|
-
Default.args = {
|
|
209
|
-
label: 'Dropdown',
|
|
210
|
-
defaultOptionLabel: 'Default Option',
|
|
211
|
-
noMatchesLabel: 'No matches',
|
|
212
|
-
border: undefined,
|
|
213
|
-
isInvalid: false,
|
|
214
|
-
errorMessage: 'Error Text',
|
|
215
|
-
errorPosition: 'bottom',
|
|
216
|
-
hasFloatingLabel: true,
|
|
217
|
-
hasRequiredLabel: true,
|
|
218
|
-
isDisabled: false,
|
|
219
|
-
isRequired: false,
|
|
220
|
-
isClearable: false,
|
|
221
|
-
isLoading: false,
|
|
222
|
-
debounceTime: 400,
|
|
223
|
-
// custom options
|
|
224
|
-
shouldUseReactNodes: false,
|
|
225
|
-
valuesType: 'strings',
|
|
226
|
-
optionsMode: 'normal',
|
|
227
|
-
shouldUsePopper: false,
|
|
228
|
-
shouldRenderInBody: false,
|
|
229
|
-
shouldHideOnScroll: false,
|
|
230
|
-
shouldUseCustomIsDisabledFunction: false,
|
|
231
|
-
shouldRenderSearchInputInList: false,
|
|
232
|
-
shouldScrollToList: true,
|
|
233
|
-
canBeFlipped: false,
|
|
234
|
-
scrollParent: 'document',
|
|
235
|
-
};
|
|
1
|
+
import { ReactNode, useEffect, useState } from 'react';
|
|
2
|
+
import { isStringNotEmpty } from '@true-engineering/true-react-platform-helpers';
|
|
3
|
+
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
4
|
+
import { Select, ISelectProps } from './Select';
|
|
5
|
+
|
|
6
|
+
interface ObjectValue {
|
|
7
|
+
name: string;
|
|
8
|
+
age: number;
|
|
9
|
+
isDisabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const inlineStyles = [undefined, 'left', 'right', 'middle'];
|
|
13
|
+
const borders: Array<ISelectProps<any>['border']> = [undefined, 'left', 'top', 'right', 'bottom'];
|
|
14
|
+
const errorPositions: Array<ISelectProps<any>['errorPosition']> = ['bottom', 'top'];
|
|
15
|
+
|
|
16
|
+
const genLetters = (qnt = 1): string =>
|
|
17
|
+
Math.random()
|
|
18
|
+
.toString(36)
|
|
19
|
+
.replace(/[^a-z]+/g, '')
|
|
20
|
+
.substr(0, qnt);
|
|
21
|
+
|
|
22
|
+
const convertObjectToString = (v: ObjectValue): string => v.name;
|
|
23
|
+
|
|
24
|
+
const convertObjectToId = (v: ObjectValue): string => `${v.name}${v.age}`;
|
|
25
|
+
|
|
26
|
+
const convertObjectToReactNode = (v: ObjectValue, isDisabled: boolean): ReactNode => (
|
|
27
|
+
<span style={{ color: isDisabled ? 'red' : undefined }}>
|
|
28
|
+
<i>{v.name}</i>, {v.age}
|
|
29
|
+
</span>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const convertStringToReactNode = (v: string): ReactNode => <i>{v}</i>;
|
|
33
|
+
|
|
34
|
+
const isOptionDisabled = (option: string) => option.startsWith('Опция');
|
|
35
|
+
const isObjectOptionDisabled = (option: ObjectValue) => option.age > 30;
|
|
36
|
+
|
|
37
|
+
const stringOptions = [
|
|
38
|
+
'Опция 1',
|
|
39
|
+
'Опция 11',
|
|
40
|
+
'Еще одна опция',
|
|
41
|
+
'Еще выбор',
|
|
42
|
+
'Очень длинная опция вот такой текст',
|
|
43
|
+
'1',
|
|
44
|
+
'2',
|
|
45
|
+
'3',
|
|
46
|
+
'4',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const objectOptions: ObjectValue[] = [
|
|
50
|
+
{ name: 'Ivan', age: 34 },
|
|
51
|
+
{ name: 'Ivan', age: 42 },
|
|
52
|
+
{ name: 'Konstantin', age: 11 },
|
|
53
|
+
{ name: 'Mikhail', age: 24 },
|
|
54
|
+
{ name: 'Maria', age: 45, isDisabled: true },
|
|
55
|
+
{ name: 'Elena', age: 14 },
|
|
56
|
+
{ name: 'Artem', age: 23 },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// Максимум не включается, минимум включается
|
|
60
|
+
const getRandomInt = (min: number, max: number) =>
|
|
61
|
+
Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min))) + Math.ceil(min);
|
|
62
|
+
|
|
63
|
+
interface ISelectWithCustomProps<T> extends ISelectProps<T> {
|
|
64
|
+
valuesType: 'strings' | 'objects';
|
|
65
|
+
shouldUseReactNodes?: boolean;
|
|
66
|
+
shouldUsePopper?: boolean;
|
|
67
|
+
shouldRenderInBody?: boolean;
|
|
68
|
+
shouldHideOnScroll?: boolean;
|
|
69
|
+
shouldUseCustomIsDisabledFunction?: boolean;
|
|
70
|
+
shouldRenderSearchInputInList?: boolean;
|
|
71
|
+
canBeFlipped?: boolean;
|
|
72
|
+
scrollParent?: 'document' | 'auto';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function SelectWithCustomProps<T>({
|
|
76
|
+
valuesType,
|
|
77
|
+
optionsMode,
|
|
78
|
+
shouldUseReactNodes,
|
|
79
|
+
shouldUsePopper,
|
|
80
|
+
shouldRenderInBody,
|
|
81
|
+
shouldHideOnScroll,
|
|
82
|
+
shouldUseCustomIsDisabledFunction,
|
|
83
|
+
shouldRenderSearchInputInList,
|
|
84
|
+
canBeFlipped,
|
|
85
|
+
scrollParent,
|
|
86
|
+
noMatchesLabel,
|
|
87
|
+
...rest
|
|
88
|
+
}: ISelectWithCustomProps<T>) {
|
|
89
|
+
const [stringValue, setStringValue] = useState<string>();
|
|
90
|
+
const stringHandler = (newValue?: string) => {
|
|
91
|
+
console.log('change');
|
|
92
|
+
setStringValue(newValue);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const [objectValue, setObjectValue] = useState<ObjectValue>();
|
|
96
|
+
const objectHandler = (newValue?: ObjectValue) => {
|
|
97
|
+
console.log('change');
|
|
98
|
+
setObjectValue(newValue);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const selectValuesType = valuesType;
|
|
102
|
+
const shouldRenderAsReactNodes = shouldUseReactNodes;
|
|
103
|
+
|
|
104
|
+
const getOptions = async (): Promise<Array<string | ObjectValue>> =>
|
|
105
|
+
new Promise((resolve) =>
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
resolve(
|
|
108
|
+
[...Array(10)].map((_) => {
|
|
109
|
+
if (selectValuesType === 'strings') {
|
|
110
|
+
return genLetters(getRandomInt(3, 10));
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
name: genLetters(getRandomInt(3, 10)),
|
|
114
|
+
age: getRandomInt(10, 70),
|
|
115
|
+
} as ObjectValue;
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
}, 1000),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const [dynamicOptions, setDynamicOptions] = useState<Array<string | ObjectValue>>([]);
|
|
122
|
+
|
|
123
|
+
const handleOpen = () => {
|
|
124
|
+
console.log('isOpen');
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const handleBlur = () => {
|
|
128
|
+
console.log('blur');
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const api = async () => {
|
|
133
|
+
setDynamicOptions(await getOptions());
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
api();
|
|
137
|
+
}, [selectValuesType]);
|
|
138
|
+
|
|
139
|
+
const props =
|
|
140
|
+
selectValuesType === 'strings'
|
|
141
|
+
? {
|
|
142
|
+
onChange: stringHandler,
|
|
143
|
+
value: stringValue,
|
|
144
|
+
options: optionsMode === 'dynamic' ? dynamicOptions : stringOptions,
|
|
145
|
+
convertValueToReactNode: shouldRenderAsReactNodes ? convertStringToReactNode : undefined,
|
|
146
|
+
isOptionDisabled: shouldUseCustomIsDisabledFunction ? isOptionDisabled : undefined,
|
|
147
|
+
}
|
|
148
|
+
: {
|
|
149
|
+
onChange: objectHandler,
|
|
150
|
+
value: objectValue,
|
|
151
|
+
options: optionsMode === 'dynamic' ? dynamicOptions : objectOptions,
|
|
152
|
+
convertValueToString: convertObjectToString,
|
|
153
|
+
convertValueToId: convertObjectToId,
|
|
154
|
+
convertValueToReactNode: shouldRenderAsReactNodes ? convertObjectToReactNode : undefined,
|
|
155
|
+
isOptionDisabled: shouldUseCustomIsDisabledFunction ? isObjectOptionDisabled : undefined,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<Select
|
|
160
|
+
{...rest}
|
|
161
|
+
{...(props as unknown as ISelectProps<any>)}
|
|
162
|
+
{...(shouldRenderSearchInputInList && {
|
|
163
|
+
searchInput: { shouldRenderInList: true },
|
|
164
|
+
})}
|
|
165
|
+
noMatchesLabel={isStringNotEmpty(noMatchesLabel) ? noMatchesLabel : undefined}
|
|
166
|
+
optionsMode={optionsMode}
|
|
167
|
+
onType={async () => setDynamicOptions(await getOptions())}
|
|
168
|
+
onOpen={handleOpen}
|
|
169
|
+
onBlur={handleBlur}
|
|
170
|
+
dropdownOptions={{
|
|
171
|
+
shouldUsePopper,
|
|
172
|
+
shouldRenderInBody,
|
|
173
|
+
shouldHideOnScroll,
|
|
174
|
+
canBeFlipped,
|
|
175
|
+
scrollParent,
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default {
|
|
182
|
+
title: 'Controls/Select',
|
|
183
|
+
component: SelectWithCustomProps,
|
|
184
|
+
argTypes: {
|
|
185
|
+
debounceTime: {
|
|
186
|
+
control: { type: 'range', min: 0, max: 1000, step: 100 },
|
|
187
|
+
},
|
|
188
|
+
errorPosition: { control: 'inline-radio', options: errorPositions },
|
|
189
|
+
border: { control: 'inline-radio', options: borders },
|
|
190
|
+
inlineStyle: { control: 'select', options: inlineStyles },
|
|
191
|
+
optionsMode: {
|
|
192
|
+
control: 'inline-radio',
|
|
193
|
+
options: ['normal', 'search', 'dynamic'],
|
|
194
|
+
},
|
|
195
|
+
valuesType: {
|
|
196
|
+
control: 'inline-radio',
|
|
197
|
+
options: ['strings', 'objects'],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
} as ComponentMeta<typeof SelectWithCustomProps>;
|
|
201
|
+
|
|
202
|
+
const Template: ComponentStory<typeof SelectWithCustomProps> = (args) => (
|
|
203
|
+
<SelectWithCustomProps {...args} />
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export const Default = Template.bind({});
|
|
207
|
+
|
|
208
|
+
Default.args = {
|
|
209
|
+
label: 'Dropdown',
|
|
210
|
+
defaultOptionLabel: 'Default Option',
|
|
211
|
+
noMatchesLabel: 'No matches',
|
|
212
|
+
border: undefined,
|
|
213
|
+
isInvalid: false,
|
|
214
|
+
errorMessage: 'Error Text',
|
|
215
|
+
errorPosition: 'bottom',
|
|
216
|
+
hasFloatingLabel: true,
|
|
217
|
+
hasRequiredLabel: true,
|
|
218
|
+
isDisabled: false,
|
|
219
|
+
isRequired: false,
|
|
220
|
+
isClearable: false,
|
|
221
|
+
isLoading: false,
|
|
222
|
+
debounceTime: 400,
|
|
223
|
+
// custom options
|
|
224
|
+
shouldUseReactNodes: false,
|
|
225
|
+
valuesType: 'strings',
|
|
226
|
+
optionsMode: 'normal',
|
|
227
|
+
shouldUsePopper: false,
|
|
228
|
+
shouldRenderInBody: false,
|
|
229
|
+
shouldHideOnScroll: false,
|
|
230
|
+
shouldUseCustomIsDisabledFunction: false,
|
|
231
|
+
shouldRenderSearchInputInList: false,
|
|
232
|
+
shouldScrollToList: true,
|
|
233
|
+
canBeFlipped: false,
|
|
234
|
+
scrollParent: 'document',
|
|
235
|
+
};
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
11
|
SyntheticEvent,
|
|
12
|
+
ChangeEvent,
|
|
13
|
+
FormEvent,
|
|
12
14
|
} from 'react';
|
|
13
15
|
import { Portal } from 'react-overlays';
|
|
14
16
|
import clsx from 'clsx';
|
|
@@ -55,14 +57,21 @@ export interface ISelectProps<Value>
|
|
|
55
57
|
dropdownOptions?: IDropdownWithPopperOptions;
|
|
56
58
|
/** @default 'chevron-down' */
|
|
57
59
|
dropdownIcon?: IIcon;
|
|
58
|
-
options: Value[]
|
|
60
|
+
options: Value[] | Readonly<Value[]>;
|
|
59
61
|
value: Value | undefined;
|
|
60
62
|
/** @default true */
|
|
61
63
|
shouldScrollToList?: boolean;
|
|
62
64
|
isMultiSelect?: boolean;
|
|
63
65
|
searchInput?: { shouldRenderInList: true } & Pick<ISearchInputProps, 'placeholder'>;
|
|
64
66
|
isOptionDisabled?: (option: Value) => boolean;
|
|
65
|
-
onChange: (
|
|
67
|
+
onChange: (
|
|
68
|
+
value: Value | undefined,
|
|
69
|
+
event:
|
|
70
|
+
| MouseEvent<HTMLElement>
|
|
71
|
+
| KeyboardEvent
|
|
72
|
+
| ChangeEvent<HTMLElement>
|
|
73
|
+
| FormEvent<HTMLElement>,
|
|
74
|
+
) => void; // подумать как возвращать индекс
|
|
66
75
|
onBlur?: (event: Event | SyntheticEvent) => void;
|
|
67
76
|
onType?: (value: string) => Promise<void>;
|
|
68
77
|
optionsFilter?: (options: Value[], query: string) => Value[];
|
|
@@ -79,7 +88,14 @@ export interface IMultipleSelectProps<Value>
|
|
|
79
88
|
extends Omit<ISelectProps<Value>, 'value' | 'onChange' | 'compareValuesOnChange'> {
|
|
80
89
|
isMultiSelect: true;
|
|
81
90
|
value: IMultipleSelectValue<Value> | undefined;
|
|
82
|
-
onChange: (
|
|
91
|
+
onChange: (
|
|
92
|
+
value: IMultipleSelectValue<Value> | undefined,
|
|
93
|
+
event:
|
|
94
|
+
| MouseEvent<HTMLElement>
|
|
95
|
+
| KeyboardEvent
|
|
96
|
+
| ChangeEvent<HTMLElement>
|
|
97
|
+
| FormEvent<HTMLElement>,
|
|
98
|
+
) => void;
|
|
83
99
|
compareValuesOnChange?: (
|
|
84
100
|
v1?: IMultipleSelectValue<Value>,
|
|
85
101
|
v2?: IMultipleSelectValue<Value>,
|
|
@@ -123,7 +139,8 @@ export function Select<Value>(
|
|
|
123
139
|
} = props;
|
|
124
140
|
const classes = useStyles({ theme: tweakStyles });
|
|
125
141
|
|
|
126
|
-
const shouldRenderSearchInputInList =
|
|
142
|
+
const { shouldRenderInList: shouldRenderSearchInputInList = false, ...searchInputProps } =
|
|
143
|
+
searchInput ?? {};
|
|
127
144
|
const hasSearchInputInList = optionsMode !== 'normal' && shouldRenderSearchInputInList;
|
|
128
145
|
const isMultiSelect = isMultiSelectValue(props, value);
|
|
129
146
|
const hasReadonlyInput = isReadonly || optionsMode === 'normal' || shouldRenderSearchInputInList;
|
|
@@ -175,7 +192,7 @@ export function Select<Value>(
|
|
|
175
192
|
const filter =
|
|
176
193
|
optionsFilter ?? createFilter<Value>((option) => [convertValueToString(option) ?? '']);
|
|
177
194
|
|
|
178
|
-
return filter(options, searchValue);
|
|
195
|
+
return filter(options as Value[], searchValue);
|
|
179
196
|
}, [optionsFilter, options, convertValueToString, searchValue, optionsMode]);
|
|
180
197
|
|
|
181
198
|
const availableOptions = useMemo(
|
|
@@ -203,7 +220,13 @@ export function Select<Value>(
|
|
|
203
220
|
return acc;
|
|
204
221
|
}, [] as number[]),
|
|
205
222
|
);
|
|
206
|
-
}, [
|
|
223
|
+
}, [
|
|
224
|
+
filteredOptions,
|
|
225
|
+
hasDefaultOption,
|
|
226
|
+
isOptionDisabled,
|
|
227
|
+
shouldShowAllOption,
|
|
228
|
+
shouldShowDefaultOption,
|
|
229
|
+
]);
|
|
207
230
|
|
|
208
231
|
const stringValue = isNotEmpty(strValue) ? convertValueToString(strValue) : undefined;
|
|
209
232
|
// Для мультиселекта пытаемся показать "Все опции" если выбраны все опции
|
|
@@ -267,10 +290,17 @@ export function Select<Value>(
|
|
|
267
290
|
};
|
|
268
291
|
|
|
269
292
|
const handleOnChange = useCallback(
|
|
270
|
-
(
|
|
293
|
+
(
|
|
294
|
+
newValue: Value | IMultipleSelectValue<Value> | undefined,
|
|
295
|
+
event:
|
|
296
|
+
| MouseEvent<HTMLElement>
|
|
297
|
+
| KeyboardEvent
|
|
298
|
+
| ChangeEvent<HTMLElement>
|
|
299
|
+
| FormEvent<HTMLElement>,
|
|
300
|
+
) => {
|
|
271
301
|
// Тут беда с типами, сорри
|
|
272
302
|
if (!compareValuesOnChange(value as never, newValue as never)) {
|
|
273
|
-
onChange(newValue as (Value & IMultipleSelectValue<Value>) | undefined);
|
|
303
|
+
onChange(newValue as (Value & IMultipleSelectValue<Value>) | undefined, event);
|
|
274
304
|
}
|
|
275
305
|
},
|
|
276
306
|
[value, compareValuesOnChange, onChange],
|
|
@@ -278,7 +308,7 @@ export function Select<Value>(
|
|
|
278
308
|
|
|
279
309
|
const handleOptionSelect = useCallback(
|
|
280
310
|
(index: number, event: MouseEvent<HTMLElement> | KeyboardEvent) => {
|
|
281
|
-
handleOnChange(index === DEFAULT_OPTION_INDEX ? undefined : filteredOptions[index]);
|
|
311
|
+
handleOnChange(index === DEFAULT_OPTION_INDEX ? undefined : filteredOptions[index], event);
|
|
282
312
|
handleListClose(event);
|
|
283
313
|
input.current?.blur();
|
|
284
314
|
},
|
|
@@ -287,18 +317,18 @@ export function Select<Value>(
|
|
|
287
317
|
|
|
288
318
|
// MultiSelect
|
|
289
319
|
const handleToggleOptionCheckbox = useCallback(
|
|
290
|
-
(index: number, isSelected: boolean) => {
|
|
320
|
+
(index: number, isSelected: boolean, event: ChangeEvent<HTMLElement> | KeyboardEvent) => {
|
|
291
321
|
if (!isMultiSelect) {
|
|
292
322
|
return;
|
|
293
323
|
}
|
|
294
324
|
|
|
295
325
|
// Если выбрана не дефолтная опция, которая сетит андеф
|
|
296
326
|
if (index === DEFAULT_OPTION_INDEX || (index === ALL_OPTION_INDEX && !isSelected)) {
|
|
297
|
-
handleOnChange(undefined);
|
|
327
|
+
handleOnChange(undefined, event);
|
|
298
328
|
return;
|
|
299
329
|
}
|
|
300
330
|
if (index === ALL_OPTION_INDEX && isSelected) {
|
|
301
|
-
handleOnChange(availableOptions as IMultipleSelectValue<Value
|
|
331
|
+
handleOnChange(availableOptions as IMultipleSelectValue<Value>, event);
|
|
302
332
|
return;
|
|
303
333
|
}
|
|
304
334
|
const option = filteredOptions[index];
|
|
@@ -308,9 +338,10 @@ export function Select<Value>(
|
|
|
308
338
|
([...(value ?? []), option] as IMultipleSelectValue<Value>)
|
|
309
339
|
: // Убираем
|
|
310
340
|
value?.filter((o) => convertToId(o) !== convertToId(option)),
|
|
341
|
+
event,
|
|
311
342
|
);
|
|
312
343
|
},
|
|
313
|
-
[
|
|
344
|
+
[isMultiSelect, filteredOptions, handleOnChange, value, availableOptions, convertToId],
|
|
314
345
|
);
|
|
315
346
|
|
|
316
347
|
const handleOnType = useCallback(
|
|
@@ -329,15 +360,15 @@ export function Select<Value>(
|
|
|
329
360
|
setShouldShowDefaultOption(v === '');
|
|
330
361
|
}
|
|
331
362
|
},
|
|
332
|
-
[onType, optionsMode],
|
|
363
|
+
[isMounted, onType, optionsMode],
|
|
333
364
|
);
|
|
334
365
|
|
|
335
|
-
const debounceHandleOnType =
|
|
336
|
-
handleOnType,
|
|
337
|
-
debounceTime,
|
|
338
|
-
|
|
366
|
+
const debounceHandleOnType = useMemo(
|
|
367
|
+
() => debounce(handleOnType, debounceTime),
|
|
368
|
+
[handleOnType, debounceTime],
|
|
369
|
+
);
|
|
339
370
|
|
|
340
|
-
const handleInputChange = (v: string) => {
|
|
371
|
+
const handleInputChange = (v: string, event: FormEvent<HTMLElement>) => {
|
|
341
372
|
if (onType !== undefined) {
|
|
342
373
|
debounceHandleOnType(v);
|
|
343
374
|
}
|
|
@@ -347,7 +378,7 @@ export function Select<Value>(
|
|
|
347
378
|
}
|
|
348
379
|
|
|
349
380
|
if (v === '' && !hasSearchInputInList) {
|
|
350
|
-
handleOnChange(undefined);
|
|
381
|
+
handleOnChange(undefined, event);
|
|
351
382
|
}
|
|
352
383
|
|
|
353
384
|
setSearchValue(v);
|
|
@@ -384,7 +415,7 @@ export function Select<Value>(
|
|
|
384
415
|
isThisValueAlreadySelected =
|
|
385
416
|
value?.some((opt) => convertToId(opt) === valueIdToSelect) ?? false;
|
|
386
417
|
}
|
|
387
|
-
handleToggleOptionCheckbox(indexToSelect, !isThisValueAlreadySelected);
|
|
418
|
+
handleToggleOptionCheckbox(indexToSelect, !isThisValueAlreadySelected, event);
|
|
388
419
|
} else {
|
|
389
420
|
handleOptionSelect(indexToSelect, event);
|
|
390
421
|
}
|
|
@@ -488,21 +519,19 @@ export function Select<Value>(
|
|
|
488
519
|
{isOpen && (
|
|
489
520
|
<SelectList
|
|
490
521
|
options={filteredOptions}
|
|
491
|
-
defaultOptionLabel={
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
allOptionsLabel={shouldShowAllOption ? allOptionsLabel : undefined}
|
|
522
|
+
defaultOptionLabel={hasDefaultOption && shouldShowDefaultOption && defaultOptionLabel}
|
|
523
|
+
allOptionsLabel={shouldShowAllOption && allOptionsLabel}
|
|
495
524
|
areAllOptionsSelected={areAllOptionsSelected}
|
|
496
525
|
customListHeader={
|
|
497
|
-
hasSearchInputInList
|
|
526
|
+
hasSearchInputInList && (
|
|
498
527
|
<SearchInput
|
|
499
528
|
value={searchValue}
|
|
500
529
|
onChange={handleInputChange}
|
|
501
530
|
tweakStyles={tweakSearchInputStyles}
|
|
502
531
|
placeholder="Поиск"
|
|
503
|
-
{...
|
|
532
|
+
{...searchInputProps}
|
|
504
533
|
/>
|
|
505
|
-
)
|
|
534
|
+
)
|
|
506
535
|
}
|
|
507
536
|
noMatchesLabel={noMatchesLabel}
|
|
508
537
|
focusedIndex={focusedListCellIndex}
|