@spaced-out/ui-design-system 0.3.42 → 0.3.44

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 (65) hide show
  1. package/.cspell/custom-words.txt +1 -0
  2. package/.github/workflows/pages.yml +1 -1
  3. package/.github/workflows/publish_to_npm.yml +2 -12
  4. package/.github/workflows/pull_request_checks.yml +1 -11
  5. package/.storybook/SenseTheme.js +1 -1
  6. package/.storybook/main.js +16 -40
  7. package/.storybook/manager.js +2 -3
  8. package/.storybook/preview.js +22 -1
  9. package/.vscode/settings.json +2 -1
  10. package/CHANGELOG.md +26 -0
  11. package/CONTRIBUTING.md +19 -0
  12. package/dts-generator/package.json +1 -1
  13. package/lib/components/EmptyState/EmptyImages/ChartEmptyImage.d.ts +2 -2
  14. package/lib/components/EmptyState/EmptyImages/ChartEmptyImage.js +2 -2
  15. package/lib/components/EmptyState/EmptyImages/ChartEmptyImage.js.flow +2 -2
  16. package/lib/components/FilterButtonOverlay/FilterButtonOverlay.d.ts +130 -0
  17. package/lib/components/FilterButtonOverlay/FilterButtonOverlay.js +90 -0
  18. package/lib/components/FilterButtonOverlay/FilterButtonOverlay.js.flow +135 -0
  19. package/lib/components/FilterButtonOverlay/FilterButtonOverlay.module.css +35 -0
  20. package/lib/components/FilterButtonOverlay/index.d.ts +1 -0
  21. package/lib/components/FilterButtonOverlay/index.js +16 -0
  22. package/lib/components/FilterButtonOverlay/index.js.flow +3 -0
  23. package/lib/components/Icon/ClickableIcon.d.ts +2 -1
  24. package/lib/components/Icon/ClickableIcon.js +3 -2
  25. package/lib/components/Icon/ClickableIcon.js.flow +2 -1
  26. package/lib/components/Icon/ClickableIcon.module.css +2 -2
  27. package/lib/components/Icon/Icon.docs.d.ts +15 -1
  28. package/lib/components/Icon/Icon.docs.js +15 -1
  29. package/lib/components/Icon/Icon.docs.js.flow +11 -1
  30. package/lib/components/InfinitePagination/InfinitePagination.d.ts +112 -0
  31. package/lib/components/InfinitePagination/InfinitePagination.js +84 -0
  32. package/lib/components/InfinitePagination/InfinitePagination.js.flow +122 -0
  33. package/lib/components/InfinitePagination/InfinitePagination.module.css +18 -0
  34. package/lib/components/InfinitePagination/index.d.ts +1 -0
  35. package/lib/components/InfinitePagination/index.js +16 -0
  36. package/lib/components/InfinitePagination/index.js.flow +3 -0
  37. package/lib/components/SemanticAIIcon/BadgedIcon.d.ts +70 -0
  38. package/lib/components/SemanticAIIcon/BadgedIcon.js +54 -0
  39. package/lib/components/SemanticAIIcon/BadgedIcon.js.flow +73 -0
  40. package/lib/components/SemanticAIIcon/BadgedIcon.module.css +88 -0
  41. package/lib/components/SemanticAIIcon/index.d.ts +1 -0
  42. package/lib/components/SemanticAIIcon/index.js +16 -0
  43. package/lib/components/SemanticAIIcon/index.js.flow +3 -0
  44. package/lib/components/Table/Table.docs.d.ts +1 -1
  45. package/lib/components/Table/Table.docs.js +1 -1
  46. package/lib/components/Table/Table.docs.js.flow +1 -1
  47. package/lib/components/index.d.ts +3 -0
  48. package/lib/components/index.js +33 -0
  49. package/lib/components/index.js.flow +3 -0
  50. package/lib/hooks/index.d.ts +2 -0
  51. package/lib/hooks/index.js +22 -0
  52. package/lib/hooks/index.js.flow +2 -0
  53. package/lib/hooks/useInfiniteScroll/index.d.ts +1 -0
  54. package/lib/hooks/useInfiniteScroll/index.js +16 -0
  55. package/lib/hooks/useInfiniteScroll/index.js.flow +3 -0
  56. package/lib/hooks/useInfiniteScroll/useInfiniteScroll.d.ts +77 -0
  57. package/lib/hooks/useInfiniteScroll/useInfiniteScroll.js +58 -0
  58. package/lib/hooks/useInfiniteScroll/useInfiniteScroll.js.flow +82 -0
  59. package/lib/hooks/useResizeObserver/index.d.ts +1 -0
  60. package/lib/hooks/useResizeObserver/index.js +16 -0
  61. package/lib/hooks/useResizeObserver/index.js.flow +3 -0
  62. package/lib/hooks/useResizeObserver/useResizeObserver.d.ts +22 -0
  63. package/lib/hooks/useResizeObserver/useResizeObserver.js +30 -0
  64. package/lib/hooks/useResizeObserver/useResizeObserver.js.flow +28 -0
  65. package/package.json +9 -16
@@ -0,0 +1,135 @@
1
+ // @flow strict
2
+
3
+ import * as React from 'react';
4
+ import {
5
+ // $FlowFixMe[untyped-import]
6
+ autoUpdate,
7
+ // $FlowFixMe[untyped-import]
8
+ flip,
9
+ // $FlowFixMe[untyped-import]
10
+ offset,
11
+ // $FlowFixMe[untyped-import]
12
+ shift,
13
+ // $FlowFixMe[untyped-import]
14
+ useFloating,
15
+ } from '@floating-ui/react';
16
+
17
+ import {sizeFluid} from '../../styles/variables/_size';
18
+ import {spaceNone, spaceXXSmall} from '../../styles/variables/_space';
19
+ import type {ClickAwayRefType} from '../../utils';
20
+ import {ClickAway, mergeRefs} from '../../utils';
21
+ import classify from '../../utils/classify';
22
+ import type {ButtonProps, ButtonSize} from '../Button';
23
+ import {Button} from '../Button';
24
+ import type {AnchorType, Strategy} from '../ButtonDropdown';
25
+ import {ANCHOR_POSITION_TYPE, STRATEGY_TYPE} from '../ButtonDropdown';
26
+
27
+ import css from './FilterButtonOverlay.module.css';
28
+
29
+
30
+ type ClassNames = $ReadOnly<{wrapper?: string, overlayContainer?: string}>;
31
+ export type FilterButtonOverlaySizeTypes = 'medium' | 'small';
32
+
33
+ export type NewButtonProps = $Diff<ButtonProps, {children?: React.Node}>;
34
+
35
+ export type FilterButtonOverlayProps = {
36
+ ...NewButtonProps,
37
+ classNames?: ClassNames,
38
+ children: React.Node,
39
+ positionStrategy?: Strategy,
40
+ anchorPosition?: AnchorType,
41
+ clickAwayRef?: ClickAwayRefType,
42
+ isFluid?: boolean,
43
+ size?: FilterButtonOverlaySizeTypes,
44
+ buttonLabel?: React.Node,
45
+ buttonSize?: ButtonSize,
46
+ buttonIsFluid?: boolean,
47
+ ...
48
+ };
49
+
50
+ export const FilterButtonOverlay: React$AbstractComponent<
51
+ FilterButtonOverlayProps,
52
+ HTMLDivElement,
53
+ > = React.forwardRef<FilterButtonOverlayProps, HTMLDivElement>(
54
+ (
55
+ {
56
+ classNames,
57
+ anchorPosition = ANCHOR_POSITION_TYPE.bottomStart,
58
+ positionStrategy = STRATEGY_TYPE.absolute,
59
+ clickAwayRef,
60
+ size,
61
+ children,
62
+ isFluid,
63
+ buttonLabel,
64
+ buttonIsFluid,
65
+ buttonSize,
66
+ ...restProps
67
+ }: FilterButtonOverlayProps,
68
+ ref,
69
+ ) => {
70
+ const {x, y, refs, strategy} = useFloating({
71
+ open: true,
72
+ strategy: positionStrategy,
73
+ placement: anchorPosition,
74
+ whileElementsMounted: autoUpdate,
75
+ middleware: [shift(), flip(), offset(parseInt(spaceXXSmall))],
76
+ });
77
+
78
+ return (
79
+ <ClickAway clickAwayRef={clickAwayRef} closeOnEscapeKeypress>
80
+ {({isOpen, onOpen, boundaryRef, triggerRef}) => (
81
+ <div
82
+ data-testid="FilterButtonOverlay"
83
+ className={classify(
84
+ {
85
+ [css.isFluid]: buttonIsFluid,
86
+ },
87
+ classNames?.wrapper,
88
+ )}
89
+ ref={ref}
90
+ >
91
+ <Button
92
+ {...restProps}
93
+ ref={mergeRefs([refs.setReference, triggerRef])}
94
+ onClick={(e) => {
95
+ e.stopPropagation();
96
+ onOpen();
97
+ }}
98
+ isFluid={buttonIsFluid}
99
+ size={buttonSize}
100
+ >
101
+ {buttonLabel}
102
+ </Button>
103
+
104
+ {isOpen && (
105
+ <div
106
+ ref={mergeRefs([refs.setFloating, boundaryRef])}
107
+ style={{
108
+ display: 'flex',
109
+ position: strategy,
110
+ top: y ?? spaceNone,
111
+ left: x ?? spaceNone,
112
+ ...(isFluid && {width: sizeFluid}),
113
+ }}
114
+ >
115
+ <div
116
+ className={classify(
117
+ css.overlayWrapper,
118
+ {
119
+ [css.fluid]: isFluid,
120
+ [css.medium]: size === 'medium',
121
+ [css.small]: size === 'small',
122
+ },
123
+ classNames?.overlayContainer,
124
+ )}
125
+ >
126
+ {children}
127
+ </div>
128
+ </div>
129
+ )}
130
+ </div>
131
+ )}
132
+ </ClickAway>
133
+ );
134
+ },
135
+ );
@@ -0,0 +1,35 @@
1
+ @value (colorBackgroundTertiary) from '../../styles/variables/_color.css';
2
+ @value (size160, sizeFluid, size300, size240) from '../../styles/variables/_size.css';
3
+ @value (borderRadiusMedium) from '../../styles/variables/_border.css';
4
+ @value (elevationMenu) from '../../styles/variables/_elevation.css';
5
+
6
+ .overlayWrapper {
7
+ position: relative;
8
+ composes: boxShadow2 from '../../styles/shadow.module.css';
9
+ composes: borderPrimary from '../../styles/border.module.css';
10
+ background-color: colorBackgroundTertiary;
11
+ display: flex;
12
+ flex-flow: column;
13
+ min-width: size160;
14
+ border-radius: borderRadiusMedium;
15
+ z-index: elevationMenu;
16
+ }
17
+
18
+ .fluid {
19
+ width: sizeFluid;
20
+ min-width: size160;
21
+ }
22
+
23
+ .medium {
24
+ width: size300;
25
+ min-width: size300;
26
+ }
27
+
28
+ .small {
29
+ width: size240;
30
+ min-width: size240;
31
+ }
32
+
33
+ .isFluid {
34
+ width: sizeFluid;
35
+ }
@@ -0,0 +1 @@
1
+ export * from './FilterButtonOverlay';
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _FilterButtonOverlay = require("./FilterButtonOverlay");
7
+ Object.keys(_FilterButtonOverlay).forEach(function (key) {
8
+ if (key === "default" || key === "__esModule") return;
9
+ if (key in exports && exports[key] === _FilterButtonOverlay[key]) return;
10
+ Object.defineProperty(exports, key, {
11
+ enumerable: true,
12
+ get: function () {
13
+ return _FilterButtonOverlay[key];
14
+ }
15
+ });
16
+ });
@@ -0,0 +1,3 @@
1
+ // @flow strict
2
+
3
+ export * from './FilterButtonOverlay';
@@ -58,9 +58,10 @@ export const ClickableIcon: React$AbstractComponent<
58
58
  ref={ref}
59
59
  >
60
60
  <Icon
61
+ {...props}
61
62
  size={size}
63
+ color={disabled ? TEXT_COLORS.disabled : props.color}
62
64
  className={classNames?.icon || className}
63
- {...props}
64
65
  />
65
66
  </UnstyledButton>
66
67
  );
@@ -42,10 +42,11 @@ const ClickableIcon = /*#__PURE__*/React.forwardRef((_ref, ref) => {
42
42
  [_ClickableIconModule.default.large]: size === 'large'
43
43
  }, classNames?.button),
44
44
  ref: ref
45
- }), /*#__PURE__*/React.createElement(_.Icon, _extends({
45
+ }), /*#__PURE__*/React.createElement(_.Icon, _extends({}, props, {
46
46
  size: size,
47
+ color: disabled ? _Text.TEXT_COLORS.disabled : props.color,
47
48
  className: classNames?.icon || className
48
- }, props)));
49
+ })));
49
50
  });
50
51
  exports.ClickableIcon = ClickableIcon;
51
52
  const CloseIcon = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
@@ -65,9 +65,10 @@ export const ClickableIcon: React$AbstractComponent<
65
65
  ref={ref}
66
66
  >
67
67
  <Icon
68
+ {...props}
68
69
  size={size}
70
+ color={disabled ? TEXT_COLORS.disabled : props.color}
69
71
  className={classNames?.icon || className}
70
- {...props}
71
72
  />
72
73
  </UnstyledButton>
73
74
  );
@@ -54,12 +54,12 @@ button {
54
54
  border-radius: borderRadiusXSmall;
55
55
  }
56
56
 
57
- .button:focus {
57
+ .button:focus-visible:not(:disabled) {
58
58
  box-shadow: borderWidthNone borderWidthNone borderWidthNone
59
59
  borderWidthTertiary colorFocusPrimary;
60
60
  }
61
61
 
62
- .button:hover {
62
+ .button:hover:not(:disabled) {
63
63
  background-color: colorButtonFillGhostPressed;
64
64
  color: colorTextPrimary;
65
65
  }
@@ -100,6 +100,20 @@ export const ICON_DOCS = {
100
100
  },
101
101
  },
102
102
  },
103
+ disabled: {
104
+ description: 'If **true**, the clickable icon is disabled',
105
+ control: {
106
+ type: 'boolean',
107
+ },
108
+ table: {
109
+ type: {
110
+ summary: 'boolean',
111
+ },
112
+ defaultValue: {
113
+ summary: 'false',
114
+ },
115
+ },
116
+ },
103
117
  ariaLabel: {
104
118
  control: {
105
119
  type: 'text',
@@ -114,8 +128,8 @@ export const ICON_DOCS = {
114
128
  },
115
129
  },
116
130
  parameters: {
117
- componentSubtitle: 'Generates a Icon component',
118
131
  docs: {
132
+ subtitle: 'Generates a Icon component',
119
133
  description: {
120
134
  component: `
121
135
  \`\`\`js
@@ -104,6 +104,20 @@ const ICON_DOCS = {
104
104
  }
105
105
  }
106
106
  },
107
+ disabled: {
108
+ description: 'If **true**, the clickable icon is disabled',
109
+ control: {
110
+ type: 'boolean'
111
+ },
112
+ table: {
113
+ type: {
114
+ summary: 'boolean'
115
+ },
116
+ defaultValue: {
117
+ summary: 'false'
118
+ }
119
+ }
120
+ },
107
121
  ariaLabel: {
108
122
  control: {
109
123
  type: 'text'
@@ -117,8 +131,8 @@ const ICON_DOCS = {
117
131
  }
118
132
  },
119
133
  parameters: {
120
- componentSubtitle: 'Generates a Icon component',
121
134
  docs: {
135
+ subtitle: 'Generates a Icon component',
122
136
  description: {
123
137
  component: `
124
138
  \`\`\`js
@@ -74,6 +74,16 @@ export const ICON_DOCS = {
74
74
  type: {summary: '(SyntheticEvent<HTMLElement>) => mixed'},
75
75
  },
76
76
  },
77
+ disabled: {
78
+ description: 'If **true**, the clickable icon is disabled',
79
+ control: {
80
+ type: 'boolean',
81
+ },
82
+ table: {
83
+ type: {summary: 'boolean'},
84
+ defaultValue: {summary: 'false'},
85
+ },
86
+ },
77
87
  ariaLabel: {
78
88
  control: {
79
89
  type: 'text',
@@ -86,8 +96,8 @@ export const ICON_DOCS = {
86
96
  },
87
97
  },
88
98
  parameters: {
89
- componentSubtitle: 'Generates a Icon component',
90
99
  docs: {
100
+ subtitle: 'Generates a Icon component',
91
101
  description: {
92
102
  component: `
93
103
  \`\`\`js
@@ -0,0 +1,112 @@
1
+ import {$ReadOnly} from 'utility-types';
2
+ import * as React from 'react';
3
+ // @ts-expect-error[nonstrict-import]
4
+ import {FixedSizeList, VariableSizeList} from 'react-window';
5
+ import {useInfiniteScroll, useResizeObserver} from '../../hooks';
6
+ import {spaceFluid} from '../../styles/variables/_space';
7
+ import classify from '../../utils/classify';
8
+ import {CircularLoader} from '../CircularLoader';
9
+ import type {GenericObject} from '../Table';
10
+ import css from './InfinitePagination.module.css';
11
+ type ClassNames = $ReadOnly<{
12
+ wrapper?: string;
13
+ }>;
14
+ const DEFAULT_THRESHOLD = 200;
15
+ export type InfinitePaginationProps = (
16
+ | {
17
+ itemSize: (index: number) => number;
18
+ isVariableSizeList: true;
19
+ }
20
+ | {
21
+ itemSize: number;
22
+ isVariableSizeList?: false;
23
+ }
24
+ ) & {
25
+ items: Array<GenericObject>;
26
+ width: string;
27
+ threshold?: number;
28
+ classNames?: ClassNames;
29
+ renderItem: (item: GenericObject, index: number) => React.ReactNode;
30
+ getItemKey?: (item: GenericObject, index: number) => string | number;
31
+ hasNextPage: boolean;
32
+ loadMoreItems: () => Promise<void>;
33
+ };
34
+ export const InfinitePagination: React$AbstractComponent<
35
+ InfinitePaginationProps,
36
+ HTMLDivElement
37
+ > = React.forwardRef<InfinitePaginationProps, HTMLDivElement>(
38
+ (props: InfinitePaginationProps, ref) => {
39
+ const {
40
+ items,
41
+ width = spaceFluid,
42
+ itemSize,
43
+ renderItem,
44
+ threshold = DEFAULT_THRESHOLD,
45
+ getItemKey,
46
+ classNames,
47
+ hasNextPage,
48
+ loadMoreItems,
49
+ isVariableSizeList,
50
+ } = props;
51
+ const listRef = React.useRef(null);
52
+ const itemsLength = items.length;
53
+ const totalItemsCount = itemsLength + (hasNextPage ? 1 : 0);
54
+ const [containerRef, containerHeight] = useResizeObserver();
55
+ React.useImperativeHandle(ref, () => listRef.current);
56
+ React.useEffect(() => {
57
+ if (listRef.current) {
58
+ listRef.current.scrollToItem(0);
59
+ }
60
+ }, [itemsLength === 0]);
61
+ const handleScroll = useInfiniteScroll({
62
+ itemSize,
63
+ threshold,
64
+ itemsLength,
65
+ hasNextPage,
66
+ loadMoreItems,
67
+ containerHeight,
68
+ isVariableSizeList,
69
+ });
70
+
71
+ const Row = ({index, style}) =>
72
+ index === itemsLength ? (
73
+ <div style={style} className={css.loader}>
74
+ <CircularLoader size="large" />
75
+ </div>
76
+ ) : (
77
+ <div style={style} className={css.listRow}>
78
+ {renderItem(items[index], index)}
79
+ </div>
80
+ );
81
+
82
+ const itemKey = React.useCallback(
83
+ (index) =>
84
+ index === itemsLength
85
+ ? 'loading-indicator'
86
+ : getItemKey
87
+ ? getItemKey(items[index], index)
88
+ : index,
89
+ [items, getItemKey],
90
+ );
91
+ const ListComponent = isVariableSizeList ? VariableSizeList : FixedSizeList;
92
+ return (
93
+ <div
94
+ ref={containerRef}
95
+ data-testid="InfinitePagination"
96
+ className={classify(css.wrapper, classNames?.wrapper)}
97
+ >
98
+ <ListComponent
99
+ ref={listRef}
100
+ width={width}
101
+ height={containerHeight}
102
+ itemKey={itemKey}
103
+ itemSize={itemSize}
104
+ onScroll={handleScroll}
105
+ itemCount={totalItemsCount}
106
+ >
107
+ {Row}
108
+ </ListComponent>
109
+ </div>
110
+ );
111
+ },
112
+ );
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.InfinitePagination = void 0;
7
+ var React = _interopRequireWildcard(require("react"));
8
+ var _reactWindow = require("react-window");
9
+ var _hooks = require("../../hooks");
10
+ var _space = require("../../styles/variables/_space");
11
+ var _classify = _interopRequireDefault(require("../../utils/classify"));
12
+ var _CircularLoader = require("../CircularLoader");
13
+ var _InfinitePaginationModule = _interopRequireDefault(require("./InfinitePagination.module.css"));
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
16
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
17
+
18
+ // $FlowFixMe[nonstrict-import]
19
+
20
+ const DEFAULT_THRESHOLD = 200;
21
+ const InfinitePagination = /*#__PURE__*/React.forwardRef((props, ref) => {
22
+ const {
23
+ items,
24
+ width = _space.spaceFluid,
25
+ itemSize,
26
+ renderItem,
27
+ threshold = DEFAULT_THRESHOLD,
28
+ getItemKey,
29
+ classNames,
30
+ hasNextPage,
31
+ loadMoreItems,
32
+ isVariableSizeList
33
+ } = props;
34
+ const listRef = React.useRef(null);
35
+ const itemsLength = items.length;
36
+ const totalItemsCount = itemsLength + (hasNextPage ? 1 : 0);
37
+ const [containerRef, containerHeight] = (0, _hooks.useResizeObserver)();
38
+ React.useImperativeHandle(ref, () => listRef.current);
39
+ React.useEffect(() => {
40
+ if (listRef.current) {
41
+ listRef.current.scrollToItem(0);
42
+ }
43
+ }, [itemsLength === 0]);
44
+ const handleScroll = (0, _hooks.useInfiniteScroll)({
45
+ itemSize,
46
+ threshold,
47
+ itemsLength,
48
+ hasNextPage,
49
+ loadMoreItems,
50
+ containerHeight,
51
+ isVariableSizeList
52
+ });
53
+ const Row = _ref => {
54
+ let {
55
+ index,
56
+ style
57
+ } = _ref;
58
+ return index === itemsLength ? /*#__PURE__*/React.createElement("div", {
59
+ style: style,
60
+ className: _InfinitePaginationModule.default.loader
61
+ }, /*#__PURE__*/React.createElement(_CircularLoader.CircularLoader, {
62
+ size: "large"
63
+ })) : /*#__PURE__*/React.createElement("div", {
64
+ style: style,
65
+ className: _InfinitePaginationModule.default.listRow
66
+ }, renderItem(items[index], index));
67
+ };
68
+ const itemKey = React.useCallback(index => index === itemsLength ? 'loading-indicator' : getItemKey ? getItemKey(items[index], index) : index, [items, getItemKey]);
69
+ const ListComponent = isVariableSizeList ? _reactWindow.VariableSizeList : _reactWindow.FixedSizeList;
70
+ return /*#__PURE__*/React.createElement("div", {
71
+ ref: containerRef,
72
+ "data-testid": "InfinitePagination",
73
+ className: (0, _classify.default)(_InfinitePaginationModule.default.wrapper, classNames?.wrapper)
74
+ }, /*#__PURE__*/React.createElement(ListComponent, {
75
+ ref: listRef,
76
+ width: width,
77
+ height: containerHeight,
78
+ itemKey: itemKey,
79
+ itemSize: itemSize,
80
+ onScroll: handleScroll,
81
+ itemCount: totalItemsCount
82
+ }, Row));
83
+ });
84
+ exports.InfinitePagination = InfinitePagination;
@@ -0,0 +1,122 @@
1
+ // @flow strict
2
+ import * as React from 'react';
3
+ // $FlowFixMe[nonstrict-import]
4
+ import {FixedSizeList, VariableSizeList} from 'react-window';
5
+
6
+ import {useInfiniteScroll, useResizeObserver} from '../../hooks';
7
+ import {spaceFluid} from '../../styles/variables/_space';
8
+ import classify from '../../utils/classify';
9
+ import {CircularLoader} from '../CircularLoader';
10
+ import type {GenericObject} from '../Table';
11
+
12
+ import css from './InfinitePagination.module.css';
13
+
14
+
15
+ type ClassNames = $ReadOnly<{wrapper?: string}>;
16
+
17
+ const DEFAULT_THRESHOLD = 200;
18
+
19
+ export type InfinitePaginationProps = {
20
+ items: Array<GenericObject>,
21
+ width: string,
22
+ threshold?: number,
23
+ classNames?: ClassNames,
24
+ renderItem: (item: GenericObject, index: number) => React.Node,
25
+ getItemKey?: (item: GenericObject, index: number) => string | number,
26
+ hasNextPage: boolean,
27
+ loadMoreItems: () => Promise<void>,
28
+ // to make Flow happy with the disjoint union isVariableSizeList is a mandatory prop
29
+ ...
30
+ | {
31
+ itemSize: (index: number) => number,
32
+ isVariableSizeList: true,
33
+ }
34
+ | {
35
+ itemSize: number,
36
+ isVariableSizeList?: false,
37
+ },
38
+ };
39
+
40
+ export const InfinitePagination: React$AbstractComponent<
41
+ InfinitePaginationProps,
42
+ HTMLDivElement,
43
+ > = React.forwardRef<InfinitePaginationProps, HTMLDivElement>(
44
+ (props: InfinitePaginationProps, ref) => {
45
+ const {
46
+ items,
47
+ width = spaceFluid,
48
+ itemSize,
49
+ renderItem,
50
+ threshold = DEFAULT_THRESHOLD,
51
+ getItemKey,
52
+ classNames,
53
+ hasNextPage,
54
+ loadMoreItems,
55
+ isVariableSizeList,
56
+ } = props;
57
+ const listRef = React.useRef(null);
58
+ const itemsLength = items.length;
59
+ const totalItemsCount = itemsLength + (hasNextPage ? 1 : 0);
60
+ const [containerRef, containerHeight] = useResizeObserver();
61
+
62
+ React.useImperativeHandle(ref, () => listRef.current);
63
+ React.useEffect(() => {
64
+ if (listRef.current) {
65
+ listRef.current.scrollToItem(0);
66
+ }
67
+ }, [itemsLength === 0]);
68
+
69
+ const handleScroll = useInfiniteScroll({
70
+ itemSize,
71
+ threshold,
72
+ itemsLength,
73
+ hasNextPage,
74
+ loadMoreItems,
75
+ containerHeight,
76
+ isVariableSizeList,
77
+ });
78
+
79
+ const Row = ({index, style}) =>
80
+ index === itemsLength ? (
81
+ <div style={style} className={css.loader}>
82
+ <CircularLoader size="large" />
83
+ </div>
84
+ ) : (
85
+ <div style={style} className={css.listRow}>
86
+ {renderItem(items[index], index)}
87
+ </div>
88
+ );
89
+
90
+ const itemKey = React.useCallback(
91
+ (index) =>
92
+ index === itemsLength
93
+ ? 'loading-indicator'
94
+ : getItemKey
95
+ ? getItemKey(items[index], index)
96
+ : index,
97
+ [items, getItemKey],
98
+ );
99
+
100
+ const ListComponent = isVariableSizeList ? VariableSizeList : FixedSizeList;
101
+
102
+ return (
103
+ <div
104
+ ref={containerRef}
105
+ data-testid="InfinitePagination"
106
+ className={classify(css.wrapper, classNames?.wrapper)}
107
+ >
108
+ <ListComponent
109
+ ref={listRef}
110
+ width={width}
111
+ height={containerHeight}
112
+ itemKey={itemKey}
113
+ itemSize={itemSize}
114
+ onScroll={handleScroll}
115
+ itemCount={totalItemsCount}
116
+ >
117
+ {Row}
118
+ </ListComponent>
119
+ </div>
120
+ );
121
+ },
122
+ );
@@ -0,0 +1,18 @@
1
+ @value (
2
+ spaceFluid
3
+ ) from '../../styles/variables/_space.css';
4
+
5
+ .wrapper {
6
+ display: flex;
7
+ height: spaceFluid;
8
+ }
9
+
10
+ .loader {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ }
15
+
16
+ .listRow {
17
+ box-sizing: border-box;
18
+ }
@@ -0,0 +1 @@
1
+ export * from './InfinitePagination';