@true-engineering/true-react-common-ui-kit 1.9.0 → 1.10.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 (31) hide show
  1. package/dist/components/Input/Input.styles.d.ts +1 -0
  2. package/dist/components/Select/Select.d.ts +12 -3
  3. package/dist/components/Select/Select.styles.d.ts +10 -0
  4. package/dist/components/Select/SelectList/SelectList.d.ts +6 -4
  5. package/dist/components/Select/SelectList/SelectList.styles.d.ts +5 -0
  6. package/dist/components/Select/SelectListItem/SelectListItem.d.ts +14 -0
  7. package/dist/components/Select/SelectListItem/SelectListItem.styles.d.ts +2 -0
  8. package/dist/components/Select/constants.d.ts +2 -0
  9. package/dist/components/Select/helpers.d.ts +4 -1
  10. package/dist/components/Select/index.d.ts +1 -0
  11. package/dist/components/Select/types.d.ts +1 -0
  12. package/dist/helpers/utils.d.ts +2 -0
  13. package/dist/true-react-common-ui-kit.js +340 -163
  14. package/dist/true-react-common-ui-kit.js.map +1 -1
  15. package/dist/true-react-common-ui-kit.umd.cjs +340 -163
  16. package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
  17. package/package.json +1 -1
  18. package/src/components/Input/Input.styles.ts +2 -0
  19. package/src/components/Input/Input.tsx +4 -1
  20. package/src/components/Select/MultiSelect.stories.tsx +262 -0
  21. package/src/components/Select/Select.styles.ts +13 -0
  22. package/src/components/Select/Select.tsx +215 -114
  23. package/src/components/Select/SelectList/SelectList.styles.ts +6 -2
  24. package/src/components/Select/SelectList/SelectList.tsx +64 -39
  25. package/src/components/Select/SelectListItem/SelectListItem.styles.ts +14 -0
  26. package/src/components/Select/SelectListItem/SelectListItem.tsx +73 -0
  27. package/src/components/Select/constants.ts +2 -0
  28. package/src/components/Select/helpers.ts +16 -8
  29. package/src/components/Select/index.ts +1 -0
  30. package/src/components/Select/types.ts +1 -0
  31. package/src/helpers/utils.ts +29 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@true-engineering/true-react-common-ui-kit",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "True Engineering React UI Kit with theming support",
5
5
  "author": "True Engineering (https://trueengineering.ru)",
6
6
  "keywords": [
@@ -299,6 +299,8 @@ export const styles = {
299
299
 
300
300
  loading: {},
301
301
 
302
+ withUnits: {},
303
+
302
304
  tweakPreloader: {},
303
305
  };
304
306
 
@@ -175,12 +175,15 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
175
175
  const hasLabel = isNotEmpty(label);
176
176
  const hasPlaceholder =
177
177
  (!hasLabel || (hasFocus && !isReadonly)) && isNotEmpty(placeholder);
178
+ const shouldShowUnits =
179
+ (hasValue || (isFocused && !hasPlaceholder)) && hasUnits;
178
180
 
179
181
  const props: InputHTMLAttributes<HTMLInputElement> = {
180
182
  className: clsx(classes.input, {
181
183
  [classes.withFloatingLabel]: hasFloatingLabel && hasLabel,
182
184
  [classes.withIcons]: hasControls,
183
185
  [classes.withControls]: hasControls,
186
+ [classes.withUnits]: shouldShowUnits,
184
187
  [classes.floatingLabelWithoutPadding]:
185
188
  hasFloatingLabel && hasLabel && border === 'bottom',
186
189
  }),
@@ -257,7 +260,7 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
257
260
  </span>
258
261
  )}
259
262
 
260
- {(hasValue || (isFocused && !hasPlaceholder)) && hasUnits && (
263
+ {shouldShowUnits && (
261
264
  <div
262
265
  className={clsx(classes.unitsWrapper, {
263
266
  [classes.withFloatingLabel]: hasFloatingLabel && hasLabel,
@@ -0,0 +1,262 @@
1
+ import { ReactNode, useEffect, useState } from 'react';
2
+ import { Select, ISelectProps } from './Select';
3
+ import { ComponentMeta, ComponentStory } from '@storybook/react';
4
+ import { isNotEmpty } from '../../helpers';
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']> = [
14
+ undefined,
15
+ 'left',
16
+ 'top',
17
+ 'right',
18
+ 'bottom',
19
+ ];
20
+ const errorPositions: Array<ISelectProps<any>['errorPosition']> = [
21
+ 'bottom',
22
+ 'top',
23
+ ];
24
+
25
+ const genLetters = (qnt = 1): string =>
26
+ Math.random()
27
+ .toString(36)
28
+ .replace(/[^a-z]+/g, '')
29
+ .substr(0, qnt);
30
+
31
+ const convertObjectToString = (v: ObjectValue): string => v.name;
32
+
33
+ const convertObjectToId = (v: ObjectValue): string => `${v.name}${v.age}`;
34
+
35
+ const convertObjectToReactNode = (
36
+ v: ObjectValue,
37
+ isDisabled: boolean,
38
+ ): ReactNode => (
39
+ <span style={{ color: isDisabled ? 'red' : undefined }}>
40
+ <i>{v.name}</i>, {v.age}
41
+ </span>
42
+ );
43
+
44
+ const convertStringToReactNode = (v: string): ReactNode => <i>{v}</i>;
45
+
46
+ const isOptionDisabled = (option: string) => option.startsWith('Опция');
47
+ const isObjectOptionDisabled = (option: ObjectValue) => option.age > 30;
48
+
49
+ const stringOptions = [
50
+ 'Опция 1',
51
+ 'Опция 11',
52
+ 'Еще одна опция',
53
+ 'Еще выбор',
54
+ 'Очень длинная опция вот такой текст',
55
+ '1',
56
+ '2',
57
+ '3',
58
+ '4',
59
+ ];
60
+
61
+ const objectOptions: ObjectValue[] = [
62
+ { name: 'Ivan', age: 34 },
63
+ { name: 'Ivan', age: 42 },
64
+ { name: 'Konstantin', age: 11 },
65
+ { name: 'Mikhail', age: 24 },
66
+ { name: 'Maria', age: 45, isDisabled: true },
67
+ { name: 'Elena', age: 14 },
68
+ { name: 'Artem', age: 23 },
69
+ ];
70
+
71
+ // Максимум не включается, минимум включается
72
+ const getRandomInt = (min: number, max: number) =>
73
+ Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min))) +
74
+ Math.ceil(min);
75
+
76
+ interface ISelectWithCustomProps<T> extends ISelectProps<T> {
77
+ valuesType: 'strings' | 'objects';
78
+ shouldUseReactNodes?: boolean;
79
+ shouldUsePopper?: boolean;
80
+ shouldRenderInBody?: boolean;
81
+ shouldHideOnScroll?: boolean;
82
+ shouldUseCustomIsDisabledFunction?: boolean;
83
+ shouldRenderSearchInputInList?: boolean;
84
+ canBeFlipped?: boolean;
85
+ scrollParent?: 'document' | 'auto';
86
+ }
87
+
88
+ function SelectWithCustomProps<T>({
89
+ valuesType,
90
+ optionsMode,
91
+ shouldUseReactNodes,
92
+ shouldUsePopper,
93
+ shouldRenderInBody,
94
+ shouldHideOnScroll,
95
+ shouldUseCustomIsDisabledFunction,
96
+ shouldRenderSearchInputInList,
97
+ canBeFlipped,
98
+ scrollParent,
99
+ noMatchesLabel,
100
+ ...rest
101
+ }: ISelectWithCustomProps<T>) {
102
+ const [stringValue, setStringValue] = useState<string[]>();
103
+
104
+ const stringHandler = (newValue?: string[]) => {
105
+ console.log('change');
106
+ setStringValue(newValue);
107
+ };
108
+
109
+ const [objectValue, setObjectValue] = useState<ObjectValue[]>();
110
+
111
+ const objectHandler = (newValue?: ObjectValue[]) => {
112
+ console.log('change');
113
+ setObjectValue(newValue);
114
+ };
115
+
116
+ const selectValuesType = valuesType;
117
+ const shouldRenderAsReactNodes = shouldUseReactNodes;
118
+
119
+ const getOptions = async (): Promise<Array<string | ObjectValue>> =>
120
+ new Promise((resolve) =>
121
+ setTimeout(() => {
122
+ resolve(
123
+ [...Array(10)].map((_) => {
124
+ if (selectValuesType === 'strings') {
125
+ return genLetters(getRandomInt(3, 10));
126
+ }
127
+ return {
128
+ name: genLetters(getRandomInt(3, 10)),
129
+ age: getRandomInt(10, 70),
130
+ } as ObjectValue;
131
+ }),
132
+ );
133
+ }, 1000),
134
+ );
135
+
136
+ const [dynamicOptions, setDynamicOptions] = useState<
137
+ Array<string | ObjectValue>
138
+ >([]);
139
+
140
+ const handleOpen = () => {
141
+ console.log('isOpen');
142
+ };
143
+
144
+ const handleBlur = () => {
145
+ console.log('blur');
146
+ };
147
+
148
+ useEffect(() => {
149
+ const api = async () => {
150
+ setDynamicOptions(await getOptions());
151
+ };
152
+
153
+ api();
154
+ }, [selectValuesType]);
155
+
156
+ const props =
157
+ selectValuesType === 'strings'
158
+ ? {
159
+ onChange: stringHandler,
160
+ value: stringValue,
161
+ options: optionsMode === 'dynamic' ? dynamicOptions : stringOptions,
162
+ convertValueToReactNode: shouldRenderAsReactNodes
163
+ ? convertStringToReactNode
164
+ : undefined,
165
+ isOptionDisabled: shouldUseCustomIsDisabledFunction
166
+ ? isOptionDisabled
167
+ : undefined,
168
+ }
169
+ : {
170
+ onChange: objectHandler,
171
+ value: objectValue,
172
+ options: optionsMode === 'dynamic' ? dynamicOptions : objectOptions,
173
+ convertValueToString: convertObjectToString,
174
+ convertValueToId: convertObjectToId,
175
+ convertValueToReactNode: shouldRenderAsReactNodes
176
+ ? convertObjectToReactNode
177
+ : undefined,
178
+ isOptionDisabled: shouldUseCustomIsDisabledFunction
179
+ ? isObjectOptionDisabled
180
+ : undefined,
181
+ };
182
+
183
+ return (
184
+ <Select
185
+ {...rest}
186
+ {...(props as unknown as ISelectProps<any>)}
187
+ {...(shouldRenderSearchInputInList && {
188
+ searchInput: { shouldRenderInList: true },
189
+ })}
190
+ isMultiSelect
191
+ noMatchesLabel={isNotEmpty(noMatchesLabel) ? noMatchesLabel : undefined}
192
+ optionsMode={optionsMode}
193
+ onType={async () => setDynamicOptions(await getOptions())}
194
+ onOpen={handleOpen}
195
+ onBlur={handleBlur}
196
+ dropdownOptions={{
197
+ shouldUsePopper,
198
+ shouldRenderInBody,
199
+ shouldHideOnScroll,
200
+ canBeFlipped,
201
+ scrollParent,
202
+ }}
203
+ />
204
+ );
205
+ }
206
+
207
+ export default {
208
+ title: 'Select',
209
+ component: SelectWithCustomProps,
210
+ argTypes: {
211
+ debounceTime: {
212
+ control: { type: 'range', min: 0, max: 1000, step: 100 },
213
+ },
214
+ errorPosition: { control: 'inline-radio', options: errorPositions },
215
+ border: { control: 'inline-radio', options: borders },
216
+ inlineStyle: { control: 'select', options: inlineStyles },
217
+ optionsMode: {
218
+ control: 'inline-radio',
219
+ options: ['normal', 'search', 'dynamic'],
220
+ },
221
+ valuesType: {
222
+ control: 'inline-radio',
223
+ options: ['strings', 'objects'],
224
+ },
225
+ },
226
+ } as ComponentMeta<typeof SelectWithCustomProps>;
227
+
228
+ const Template: ComponentStory<typeof SelectWithCustomProps> = (args) => (
229
+ <SelectWithCustomProps {...args} />
230
+ );
231
+
232
+ export const MultiSelect = Template.bind({});
233
+
234
+ MultiSelect.args = {
235
+ label: 'Dropdown',
236
+ noMatchesLabel: 'No matches',
237
+ border: undefined,
238
+ isInvalid: false,
239
+ errorMessage: 'Error Text',
240
+ errorPosition: 'bottom',
241
+ hasFloatingLabel: true,
242
+ hasRequiredLabel: true,
243
+ defaultOptionLabel: '',
244
+ allOptionsLabel: 'Все опции',
245
+ isDisabled: false,
246
+ isRequired: false,
247
+ isClearable: false,
248
+ isLoading: false,
249
+ debounceTime: 400,
250
+ // custom options
251
+ shouldUseReactNodes: false,
252
+ valuesType: 'strings',
253
+ optionsMode: 'normal',
254
+ shouldUsePopper: false,
255
+ shouldRenderInBody: false,
256
+ shouldHideOnScroll: false,
257
+ shouldUseCustomIsDisabledFunction: false,
258
+ shouldRenderSearchInputInList: false,
259
+ shouldScrollToList: true,
260
+ canBeFlipped: false,
261
+ scrollParent: 'document',
262
+ };
@@ -57,6 +57,19 @@ export const styles = {
57
57
  paddingRight: 32,
58
58
  },
59
59
 
60
+ withUnits: {
61
+ paddingRight: 8,
62
+ },
63
+
64
+ unitsWrapper: {
65
+ position: 'unset',
66
+ padding: [0, 32, 0, 0],
67
+ },
68
+
69
+ fakeValue: {
70
+ display: 'none',
71
+ },
72
+
60
73
  disabled: {
61
74
  '& $input': {
62
75
  cursor: 'default',