@true-engineering/true-react-common-ui-kit 4.0.0-alpha24 → 4.0.0-alpha26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@true-engineering/true-react-common-ui-kit",
3
- "version": "4.0.0-alpha24",
3
+ "version": "4.0.0-alpha26",
4
4
  "description": "True Engineering React UI Kit with theming support",
5
5
  "author": "True Engineering (https://trueengineering.ru)",
6
6
  "keywords": [
@@ -1,15 +1,16 @@
1
1
  import { ReactNode, RefObject, useCallback, useEffect, useMemo, useRef } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import {
4
- addDataTestId,
4
+ addDataAttributes,
5
5
  applyAction,
6
+ getTestId,
6
7
  indexMap,
7
8
  isArrayNotEmpty,
8
9
  isEmpty,
9
10
  isNotEmpty,
11
+ isReactNodeNotEmpty,
10
12
  } from '@true-engineering/true-react-platform-helpers';
11
- import { addDataAttributes } from '../../helpers';
12
- import { useMergedRefs, useTweakStyles } from '../../hooks';
13
+ import { useIntersectionRef, useMergedRefs, useTweakStyles } from '../../hooks';
13
14
  import { ICommonProps } from '../../types';
14
15
  import { ThemedPreloader } from '../ThemedPreloader';
15
16
  import { FlexibleTableRow, IFlexibleTableRowProps } from './components';
@@ -81,7 +82,7 @@ export function FlexibleTable<
81
82
  isFirstColumnSticky = false,
82
83
  isHorizontallyScrollable = false,
83
84
  isRowFocusable = false,
84
- infinityScrollConfig,
85
+ infinityScrollConfig: infinityScrollConfigDeprecated,
85
86
  renderMode = 'table',
86
87
  refForScroll,
87
88
  nothingFoundContent,
@@ -101,13 +102,10 @@ export function FlexibleTable<
101
102
  currentComponentName: 'FlexibleTable',
102
103
  });
103
104
 
104
- const observer = useRef<IntersectionObserver>();
105
105
  const scrollRef = useRef<HTMLDivElement>(null);
106
106
 
107
107
  const columns = useMemo(() => enabledColumns ?? Object.keys(config), [enabledColumns, config]);
108
108
 
109
- const hasInfiniteScroll = isNotEmpty(infinityScrollConfig);
110
-
111
109
  const getTableRowProps = (
112
110
  item: Row,
113
111
  index: number,
@@ -148,12 +146,6 @@ export function FlexibleTable<
148
146
  [getDataScrollAttributeSetter],
149
147
  );
150
148
 
151
- const shouldShowNothingFound =
152
- !isArrayNotEmpty(content) &&
153
- nothingFoundContent !== undefined &&
154
- !infinityScrollConfig?.isLoading &&
155
- (infinityScrollConfig?.isLastPage === undefined || infinityScrollConfig.isLastPage);
156
-
157
149
  const ref = useMergedRefs([
158
150
  refForScroll,
159
151
  scrollRef,
@@ -161,37 +153,25 @@ export function FlexibleTable<
161
153
  setIsScrolledAttribute,
162
154
  ]);
163
155
 
164
- const initIntersectionObserver = useCallback(
165
- (node: HTMLDivElement | null) => {
166
- if (
167
- !hasInfiniteScroll ||
168
- infinityScrollConfig.isLoading ||
169
- infinityScrollConfig.activePage >= infinityScrollConfig.totalPages
170
- ) {
171
- return;
172
- }
173
-
174
- if (observer.current) {
175
- observer.current.disconnect();
176
- }
177
-
178
- observer.current = new IntersectionObserver((entries) => {
179
- if (entries[0].isIntersecting) {
180
- infinityScrollConfig.onInfinityScroll(infinityScrollConfig.activePage + 1);
181
- }
182
- });
156
+ const infinityScrollConfig = infinityScrollConfigDeprecated && {
157
+ isLoading: infinityScrollConfigDeprecated.isLoading,
158
+ onInfinityScroll: () =>
159
+ infinityScrollConfigDeprecated.onInfinityScroll(
160
+ (infinityScrollConfigDeprecated.activePage ?? 0) + 1,
161
+ ),
162
+ isEnabled:
163
+ infinityScrollConfigDeprecated.isEnabled ?? !infinityScrollConfigDeprecated.isLastPage,
164
+ };
165
+ const intersectionRef = useIntersectionRef({
166
+ isDisabled: !infinityScrollConfig?.isEnabled || infinityScrollConfig.isLoading,
167
+ onIntersection: infinityScrollConfig?.onInfinityScroll,
168
+ });
183
169
 
184
- if (node) {
185
- observer.current.observe(node);
186
- }
187
- },
188
- [
189
- hasInfiniteScroll,
190
- infinityScrollConfig?.activePage,
191
- infinityScrollConfig?.totalPages,
192
- infinityScrollConfig?.onInfinityScroll,
193
- ],
194
- );
170
+ const shouldShowNothingFound =
171
+ isReactNodeNotEmpty(nothingFoundContent) &&
172
+ !isArrayNotEmpty(content) &&
173
+ !infinityScrollConfig?.isLoading &&
174
+ !infinityScrollConfig?.isEnabled;
195
175
 
196
176
  useEffect(() => {
197
177
  const scrollContainer = scrollRef.current;
@@ -222,11 +202,7 @@ export function FlexibleTable<
222
202
 
223
203
  return (
224
204
  <div ref={ref} className={clsx({ [classes.scroll]: isHorizontallyScrollable })}>
225
- <Table.Root
226
- className={classes.root}
227
- {...addDataTestId(testId)}
228
- {...addDataAttributes({ ...data, isLoading })}
229
- >
205
+ <Table.Root className={classes.root} {...addDataAttributes({ ...data, isLoading }, testId)}>
230
206
  {shouldRenderHeader && (
231
207
  <Table.Head className={classes.head}>
232
208
  <Table.Row className={classes.headerRow}>
@@ -291,11 +267,11 @@ export function FlexibleTable<
291
267
  />
292
268
  ))}
293
269
 
294
- {hasInfiniteScroll && !infinityScrollConfig.isLastPage && (
270
+ {infinityScrollConfig?.isEnabled && (
295
271
  <Table.Row className={classes.loaderRow}>
296
272
  <Table.Cell className={classes.loaderCell} colSpan={columns.length}>
297
- <div ref={initIntersectionObserver} className={classes.loader}>
298
- <ThemedPreloader type="dots" />
273
+ <div ref={intersectionRef} className={classes.loader}>
274
+ <ThemedPreloader type="dots" testId={getTestId(testId, 'loader')} />
299
275
  </div>
300
276
  </Table.Cell>
301
277
  </Table.Row>
@@ -64,11 +64,19 @@ export type IFlexibleTableConfigType<
64
64
  };
65
65
 
66
66
  export interface IInfinityScrollConfig {
67
- activePage: number;
68
- totalPages: number;
67
+ isEnabled?: boolean;
69
68
  isLoading: boolean;
70
- isLastPage: boolean;
71
- onInfinityScroll: (skip: number) => void;
69
+ onInfinityScroll: {
70
+ (_: never): void;
71
+ /** @deprecated use activePage directly */
72
+ (skip: number): void; // eslint-disable-line @typescript-eslint/unified-signatures
73
+ };
74
+ /** @deprecated use activePage in onInfinityScroll */
75
+ activePage?: number;
76
+ /** @deprecated use isEnabled */
77
+ isLastPage?: boolean;
78
+ /** @deprecated use isEnabled */
79
+ totalPages?: number;
72
80
  }
73
81
 
74
82
  export interface INestedComponent<T extends PropertyKey = string> {
@@ -1,8 +1,13 @@
1
1
  import { useEffect, useState, useMemo, useRef, useCallback, ReactNode } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { debounce } from 'ts-debounce';
4
- import { isArrayNotEmpty, isNotEmpty } from '@true-engineering/true-react-platform-helpers';
5
- import { addDataAttributes } from '../../helpers';
4
+ import {
5
+ addDataAttributes,
6
+ addDataTestId,
7
+ getTestId,
8
+ isArrayNotEmpty,
9
+ isNotEmpty,
10
+ } from '@true-engineering/true-react-platform-helpers';
6
11
  import { useIsMounted, useTweakStyles } from '../../hooks';
7
12
  import { ICommonProps } from '../../types';
8
13
  import { Button } from '../Button';
@@ -330,7 +335,7 @@ export function MultiSelectList<Value extends IMultiSelectListValues<Option>, Op
330
335
  !isLoading && (isArrayNotEmpty(allOptions) || isArrayNotEmpty(chosenValues));
331
336
 
332
337
  return (
333
- <div className={classes.root} {...addDataAttributes(data)}>
338
+ <div className={classes.root} {...addDataAttributes(data, testId)}>
334
339
  {isSearchEnabled && (
335
340
  <div className={classes.dropdownInput}>
336
341
  <SearchInput
@@ -339,16 +344,13 @@ export function MultiSelectList<Value extends IMultiSelectListValues<Option>, Op
339
344
  onChange={handleOnChange}
340
345
  tweakStyles={tweakSearchInputStyles}
341
346
  maxLength={searchMaxLength}
342
- testId={testId !== undefined ? `${testId}-search` : undefined}
347
+ testId={getTestId(testId, 'search')}
343
348
  shouldFocusOnMount
344
349
  />
345
350
  </div>
346
351
  )}
347
352
  {shouldShowOptionsList && (
348
- <div
349
- className={classes.list}
350
- data-testid={testId !== undefined ? `${testId}-list` : undefined}
351
- >
353
+ <div className={classes.list} {...addDataTestId(testId, 'list')}>
352
354
  {/* Выбранные */}
353
355
  {hasSelectedOptionsGroup && (
354
356
  <>
@@ -379,8 +381,9 @@ export function MultiSelectList<Value extends IMultiSelectListValues<Option>, Op
379
381
  value={val}
380
382
  tweakStyles={tweakCheckboxStyles}
381
383
  labelPosition={checkboxPosition === 'left' ? 'right' : 'left'}
384
+ data={{ id }}
382
385
  >
383
- <div className={classes.option} data-option={id}>
386
+ <div className={classes.option} {...addDataAttributes({ option: id })}>
384
387
  {view}
385
388
  </div>
386
389
  </Checkbox>
@@ -417,8 +420,9 @@ export function MultiSelectList<Value extends IMultiSelectListValues<Option>, Op
417
420
  value={val}
418
421
  tweakStyles={tweakCheckboxStyles}
419
422
  labelPosition={checkboxPosition === 'left' ? 'right' : 'left'}
423
+ data={{ id }}
420
424
  >
421
- <div className={classes.option} data-option={id}>
425
+ <div className={classes.option} {...addDataAttributes({ option: id })}>
422
426
  {view}
423
427
  </div>
424
428
  </Checkbox>
@@ -449,7 +453,7 @@ export function MultiSelectList<Value extends IMultiSelectListValues<Option>, Op
449
453
  onClick={handleClear}
450
454
  size="s"
451
455
  view="text"
452
- testId={testId !== undefined ? `${testId}-clear-button` : undefined}
456
+ testId={getTestId(testId, 'clear-button')}
453
457
  tweakStyles={tweakClearButtonStyles}
454
458
  >
455
459
  {translates.clear}
@@ -121,10 +121,8 @@ export const WithPopup: FC<IWithPopupProps> = ({
121
121
 
122
122
  const handleToggle = (isActive: boolean, event?: IWithPopupToggleEvent) => {
123
123
  event?.stopPropagation();
124
- if (!isDisabled) {
125
- onToggle?.(isActive, event);
126
- setIsOpen(isActive);
127
- }
124
+ onToggle?.(isActive, event);
125
+ setIsOpen(isActive);
128
126
  };
129
127
 
130
128
  const handleClose = (event?: IWithPopupToggleEvent) => {
@@ -132,7 +130,7 @@ export const WithPopup: FC<IWithPopupProps> = ({
132
130
  };
133
131
 
134
132
  const { refs, floatingStyles, context } = useFloating({
135
- open: isOpen,
133
+ open: isOpen && !isDisabled,
136
134
  middleware: [
137
135
  offset(popupOffset),
138
136
  canBeFlipped && flip({ fallbackAxisSideDirection: 'start' }),
@@ -6,3 +6,4 @@ export * from './use-did-mount-effect';
6
6
  export * from './use-mixed-styles';
7
7
  export * from './use-merged-refs';
8
8
  export * from './use-merge';
9
+ export * from './use-intersection-ref';
@@ -0,0 +1,30 @@
1
+ import { useRef, useMemo, RefCallback } from 'react';
2
+ import { isNotEmpty } from '@true-engineering/true-react-platform-helpers';
3
+
4
+ export interface IInsertionRefOptions {
5
+ /** @default false */
6
+ isDisabled?: boolean;
7
+ onIntersection?: VoidFunction;
8
+ onIntersectionEnd?: VoidFunction;
9
+ }
10
+
11
+ export const useIntersectionRef = (options?: IInsertionRefOptions): RefCallback<Element> => {
12
+ const optionsRef = useRef(options);
13
+ optionsRef.current = options;
14
+
15
+ return useMemo(() => {
16
+ const observer = new IntersectionObserver(([{ isIntersecting }]) => {
17
+ const { current } = optionsRef;
18
+ if (current?.isDisabled) {
19
+ return;
20
+ }
21
+ if (isIntersecting) {
22
+ current?.onIntersection?.();
23
+ } else {
24
+ current?.onIntersectionEnd?.();
25
+ }
26
+ });
27
+
28
+ return (node) => (isNotEmpty(node) ? observer.observe(node) : observer.disconnect());
29
+ }, []);
30
+ };