@qoretechnologies/reqore 0.32.3 → 0.33.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 (92) hide show
  1. package/__tests__/collection.test.tsx +123 -5
  2. package/__tests__/hooks/useLatestZIndex.test.tsx +10 -0
  3. package/__tests__/hooks/useReqorePaging.test.tsx +251 -0
  4. package/__tests__/hooks/useTheme.test.tsx +31 -0
  5. package/__tests__/pagination.test.tsx +301 -0
  6. package/__tests__/popover.test.tsx +20 -6
  7. package/__tests__/setup.js +8 -0
  8. package/__tests__/tree.test.tsx +0 -1
  9. package/dist/components/Button/index.d.ts +1 -0
  10. package/dist/components/Button/index.d.ts.map +1 -1
  11. package/dist/components/Button/index.js +11 -7
  12. package/dist/components/Button/index.js.map +1 -1
  13. package/dist/components/Collection/index.d.ts +9 -3
  14. package/dist/components/Collection/index.d.ts.map +1 -1
  15. package/dist/components/Collection/index.js +34 -8
  16. package/dist/components/Collection/index.js.map +1 -1
  17. package/dist/components/Dropdown/index.d.ts +5 -2
  18. package/dist/components/Dropdown/index.d.ts.map +1 -1
  19. package/dist/components/Dropdown/index.js +2 -2
  20. package/dist/components/Dropdown/index.js.map +1 -1
  21. package/dist/components/Dropdown/list.d.ts +4 -1
  22. package/dist/components/Dropdown/list.d.ts.map +1 -1
  23. package/dist/components/Dropdown/list.js +3 -3
  24. package/dist/components/Dropdown/list.js.map +1 -1
  25. package/dist/components/Menu/item.d.ts +1 -0
  26. package/dist/components/Menu/item.d.ts.map +1 -1
  27. package/dist/components/Menu/item.js +14 -2
  28. package/dist/components/Menu/item.js.map +1 -1
  29. package/dist/components/MultiSelect/index.d.ts +1 -1
  30. package/dist/components/MultiSelect/index.d.ts.map +1 -1
  31. package/dist/components/Paging/index.d.ts +30 -0
  32. package/dist/components/Paging/index.d.ts.map +1 -0
  33. package/dist/components/Paging/index.js +106 -0
  34. package/dist/components/Paging/index.js.map +1 -0
  35. package/dist/components/Tag/index.d.ts +3 -2
  36. package/dist/components/Tag/index.d.ts.map +1 -1
  37. package/dist/components/Tag/index.js +10 -7
  38. package/dist/components/Tag/index.js.map +1 -1
  39. package/dist/constants/colors.d.ts +2 -9
  40. package/dist/constants/colors.d.ts.map +1 -1
  41. package/dist/constants/colors.js +3 -1
  42. package/dist/constants/colors.js.map +1 -1
  43. package/dist/constants/paging.d.ts +10 -0
  44. package/dist/constants/paging.d.ts.map +1 -0
  45. package/dist/constants/paging.js +41 -0
  46. package/dist/constants/paging.js.map +1 -0
  47. package/dist/constants/theme.d.ts.map +1 -1
  48. package/dist/constants/theme.js +8 -7
  49. package/dist/constants/theme.js.map +1 -1
  50. package/dist/containers/ReqoreProvider.d.ts.map +1 -1
  51. package/dist/containers/ReqoreProvider.js +2 -2
  52. package/dist/containers/ReqoreProvider.js.map +1 -1
  53. package/dist/containers/UIProvider.d.ts.map +1 -1
  54. package/dist/containers/UIProvider.js +9 -3
  55. package/dist/containers/UIProvider.js.map +1 -1
  56. package/dist/hooks/useCombinedRefs.d.ts +2 -2
  57. package/dist/hooks/useCombinedRefs.d.ts.map +1 -1
  58. package/dist/hooks/useCombinedRefs.js.map +1 -1
  59. package/dist/hooks/usePaging.d.ts +27 -0
  60. package/dist/hooks/usePaging.d.ts.map +1 -0
  61. package/dist/hooks/usePaging.js +83 -0
  62. package/dist/hooks/usePaging.js.map +1 -0
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +6 -2
  66. package/dist/index.js.map +1 -1
  67. package/package.json +6 -4
  68. package/src/components/Button/index.tsx +18 -10
  69. package/src/components/Collection/index.tsx +96 -44
  70. package/src/components/ControlGroup/index.tsx +1 -1
  71. package/src/components/Dropdown/index.tsx +8 -2
  72. package/src/components/Dropdown/list.tsx +7 -1
  73. package/src/components/Menu/item.tsx +16 -2
  74. package/src/components/MultiSelect/index.tsx +1 -1
  75. package/src/components/Paging/index.tsx +232 -0
  76. package/src/components/Tag/index.tsx +9 -7
  77. package/src/constants/colors.ts +5 -1
  78. package/src/constants/paging.ts +48 -0
  79. package/src/constants/theme.ts +8 -7
  80. package/src/containers/ReqoreProvider.tsx +3 -3
  81. package/src/containers/UIProvider.tsx +17 -4
  82. package/src/hooks/useCombinedRefs.ts +2 -2
  83. package/src/hooks/usePaging.ts +110 -0
  84. package/src/index.tsx +2 -0
  85. package/src/mock/collectionData.tsx +19 -0
  86. package/src/mock/tableData.ts +1 -0
  87. package/src/stories/Button/Button.stories.tsx +25 -8
  88. package/src/stories/Collection/Collection.stories.tsx +32 -42
  89. package/src/stories/Dropdown/Dropdown.stories.tsx +24 -1
  90. package/src/stories/Paging/Paging.stories.tsx +184 -0
  91. package/src/stories/Tag/Tag.stories.tsx +50 -0
  92. package/tests.json +1 -1
@@ -0,0 +1,232 @@
1
+ import { memo, useEffect, useRef, useState } from 'react';
2
+ import { useUnmount, useUpdateEffect } from 'react-use';
3
+ import styled from 'styled-components';
4
+ import { ReqoreDropdown } from '../..';
5
+ import { IReqorePagingResult } from '../../hooks/usePaging';
6
+ import ReqoreButton, { IReqoreButtonProps, StyledButton } from '../Button';
7
+ import ReqoreControlGroup, { IReqoreControlGroupProps } from '../ControlGroup';
8
+ import { IReqoreDropdownProps } from '../Dropdown';
9
+ import { IReqoreDropdownItemProps } from '../Dropdown/item';
10
+
11
+ export interface IReqorePaginationComponentProps
12
+ extends Omit<IReqoreControlGroupProps, 'children'> {
13
+ pageButtonProps?: IReqoreButtonProps;
14
+ activePageButtonProps?: IReqoreButtonProps;
15
+ listPageButtonProps?: IReqoreDropdownProps;
16
+ controlPageButtonProps?: IReqoreButtonProps;
17
+ loadMoreButtonProps?: IReqoreButtonProps;
18
+ firstPageLabel?: string;
19
+ lastPageLabel?: string;
20
+ nextPageLabel?: string;
21
+ previousPageLabel?: string;
22
+ pageLabel?: string;
23
+ loadMoreLabel?: string;
24
+ showPages?: boolean;
25
+ showControls?: boolean;
26
+ showPagesAs?: 'buttons' | 'list';
27
+ showLabels?: boolean;
28
+ scrollOnLoadMore?: boolean;
29
+ autoLoadMore?: boolean;
30
+ }
31
+ export interface IReqorePaginationProps<T>
32
+ extends Omit<IReqorePagingResult<T>, 'items'>,
33
+ IReqorePaginationComponentProps {}
34
+
35
+ export const StyledPagesWrapper = styled(ReqoreControlGroup)`
36
+ width: unset;
37
+ max-width: 100%;
38
+ justify-content: center;
39
+
40
+ & > ${StyledButton} {
41
+ margin: 0;
42
+ }
43
+ `;
44
+
45
+ function Pagination<T>({
46
+ allPages,
47
+ back,
48
+ first,
49
+ currentPage,
50
+ isLastPage,
51
+ isFirstPage,
52
+ pageCount,
53
+ pages,
54
+ last,
55
+ next,
56
+ setPage,
57
+ infinite,
58
+ itemsLeft,
59
+ showPagesAs = 'buttons',
60
+ pageButtonProps = {},
61
+ activePageButtonProps = {},
62
+ listPageButtonProps = {},
63
+ controlPageButtonProps = {},
64
+ loadMoreButtonProps = {},
65
+ firstPageLabel = 'First',
66
+ lastPageLabel = 'Last',
67
+ nextPageLabel = 'Next',
68
+ previousPageLabel = 'Back',
69
+ pageLabel = 'Page',
70
+ loadMoreLabel = 'Load more',
71
+ showLabels,
72
+ showPages = true,
73
+ showControls = true,
74
+ scrollOnLoadMore,
75
+ autoLoadMore,
76
+ itemsPerPage,
77
+ ...rest
78
+ }: IReqorePaginationProps<T>) {
79
+ const [loadMoreRef, setLoadMoreRef] = useState<HTMLButtonElement>(undefined);
80
+ const [scrollTargetRef, setScrollTargetRef] = useState<HTMLDivElement>(undefined);
81
+ const observer = useRef<IntersectionObserver>(null);
82
+
83
+ useEffect(() => {
84
+ if (scrollTargetRef && scrollOnLoadMore && !isLastPage && !isFirstPage) {
85
+ scrollTargetRef.scrollIntoView({
86
+ behavior: 'smooth',
87
+ block: 'end',
88
+ inline: 'nearest',
89
+ });
90
+ }
91
+ }, [currentPage, scrollTargetRef, scrollOnLoadMore, isLastPage, isFirstPage]);
92
+
93
+ useUpdateEffect(() => {
94
+ if (loadMoreRef && autoLoadMore) {
95
+ observer.current = new IntersectionObserver(
96
+ (entries: IntersectionObserverEntry[]) => {
97
+ entries.forEach((entry) => {
98
+ if (entry.isIntersecting) {
99
+ next();
100
+ }
101
+ });
102
+ },
103
+ {
104
+ threshold: 0.8,
105
+ }
106
+ );
107
+
108
+ observer.current.observe(loadMoreRef);
109
+ }
110
+ }, [loadMoreRef, autoLoadMore, isLastPage]);
111
+
112
+ useUnmount(() => {
113
+ observer.current?.disconnect();
114
+ });
115
+
116
+ if (pageCount === 1 || pageCount === 0) return null;
117
+
118
+ return (
119
+ <ReqoreControlGroup
120
+ {...rest}
121
+ className={`${rest.className || ''} reqore-pagination-wrapper`}
122
+ style={{ justifyContent: 'center' }}
123
+ >
124
+ {infinite && !isLastPage ? (
125
+ <ReqoreControlGroup vertical>
126
+ <div className='reqore-auto-scroll-target' ref={setScrollTargetRef} />
127
+ <ReqoreButton
128
+ icon='ArrowDropDownLine'
129
+ badge={itemsLeft}
130
+ iconsAlign='center'
131
+ {...loadMoreButtonProps}
132
+ onClick={next}
133
+ ref={setLoadMoreRef}
134
+ style={{
135
+ scrollMarginTop: '200px',
136
+ }}
137
+ >
138
+ {showLabels && loadMoreLabel}
139
+ </ReqoreButton>
140
+ </ReqoreControlGroup>
141
+ ) : (
142
+ <>
143
+ {showPagesAs === 'list' || (showControls && !showPages) ? (
144
+ <ReqoreButton
145
+ icon='RewindMiniLine'
146
+ fixed
147
+ {...controlPageButtonProps}
148
+ onClick={first}
149
+ disabled={isFirstPage}
150
+ tooltip={!showLabels && firstPageLabel}
151
+ >
152
+ {showLabels && firstPageLabel}
153
+ </ReqoreButton>
154
+ ) : null}
155
+ {showControls && (
156
+ <ReqoreButton
157
+ icon='ArrowDropLeftLine'
158
+ fixed
159
+ {...controlPageButtonProps}
160
+ onClick={back}
161
+ disabled={isFirstPage}
162
+ tooltip={!showLabels && previousPageLabel}
163
+ >
164
+ {showLabels && previousPageLabel}
165
+ </ReqoreButton>
166
+ )}
167
+ {showPages && (
168
+ <StyledPagesWrapper wrap horizontalAlign='center'>
169
+ {showPagesAs === 'buttons' ? (
170
+ pages.map((page) => (
171
+ <ReqoreButton
172
+ textAlign='center'
173
+ fixed
174
+ key={page}
175
+ onClick={() => setPage(page)}
176
+ active={page === currentPage}
177
+ {...(page === currentPage ? activePageButtonProps : pageButtonProps)}
178
+ >
179
+ {page}
180
+ </ReqoreButton>
181
+ ))
182
+ ) : (
183
+ <ReqoreDropdown
184
+ filterable
185
+ label={`${showLabels ? `${pageLabel} ` : ''}${currentPage} / ${pageCount}`}
186
+ items={pages.map((page) => ({
187
+ label: `${showLabels ? `${pageLabel} ` : ''}${page}`,
188
+ onClick: () => setPage(page),
189
+ selected: page === currentPage,
190
+ ...(page === currentPage
191
+ ? (activePageButtonProps as IReqoreDropdownItemProps)
192
+ : (pageButtonProps as IReqoreDropdownItemProps)),
193
+ }))}
194
+ placement='bottom'
195
+ inputProps={{ focusRules: { type: 'auto', viewportOnly: true } }}
196
+ scrollToSelected
197
+ {...listPageButtonProps}
198
+ />
199
+ )}
200
+ </StyledPagesWrapper>
201
+ )}
202
+ {showControls && (
203
+ <ReqoreButton
204
+ icon='ArrowDropRightLine'
205
+ fixed
206
+ {...controlPageButtonProps}
207
+ onClick={next}
208
+ disabled={isLastPage}
209
+ tooltip={!showLabels && nextPageLabel}
210
+ >
211
+ {showLabels && nextPageLabel}
212
+ </ReqoreButton>
213
+ )}
214
+ {showPagesAs === 'list' || (showControls && !showPages) ? (
215
+ <ReqoreButton
216
+ icon='SpeedMiniLine'
217
+ fixed
218
+ {...controlPageButtonProps}
219
+ onClick={last}
220
+ disabled={isLastPage}
221
+ tooltip={!showLabels && lastPageLabel}
222
+ >
223
+ {showLabels && lastPageLabel}
224
+ </ReqoreButton>
225
+ ) : null}
226
+ </>
227
+ )}
228
+ </ReqoreControlGroup>
229
+ );
230
+ }
231
+
232
+ export const ReqorePagination = memo(Pagination) as typeof Pagination;
@@ -25,7 +25,6 @@ import {
25
25
  IReqoreDisabled,
26
26
  IReqoreIntent,
27
27
  IWithReqoreEffect,
28
- IWithReqoreFixed,
29
28
  IWithReqoreFluid,
30
29
  IWithReqoreMinimal,
31
30
  IWithReqoreTooltip,
@@ -52,8 +51,8 @@ export interface IReqoreTagProps
52
51
  IReqoreDisabled,
53
52
  IWithReqoreMinimal,
54
53
  IWithReqoreFluid,
55
- IWithReqoreFixed,
56
54
  IWithReqoreEffect {
55
+ fixed?: boolean | 'key' | 'label';
57
56
  size?: TSizes;
58
57
  label?: string | number;
59
58
  labelKey?: string | number;
@@ -93,9 +92,10 @@ export const StyledTag = styled(StyledEffect)<IReqoreTagStyle>`
93
92
  font-size: ${({ size }) => TEXT_FROM_SIZE[size]}px;
94
93
 
95
94
  min-width: ${({ size }) => SIZE_TO_PX[size]}px;
96
- max-width: ${({ fixed }) => (!fixed ? '100%' : undefined)};
97
- flex: ${({ fluid, fixed }) => (fixed ? '0 0 auto' : fluid ? '1 auto' : '0 0 auto')};
98
- align-self: ${({ fixed, fluid }) => (fixed ? 'flex-start' : fluid ? 'stretch' : undefined)};
95
+ max-width: ${({ fixed }) => (fixed !== true ? '100%' : undefined)};
96
+ flex: ${({ fluid, fixed }) => (fixed === true ? '0 0 auto' : fluid ? '1 auto' : '0 0 auto')};
97
+ align-self: ${({ fixed, fluid }) =>
98
+ fixed === true ? 'flex-start' : fluid ? 'stretch' : undefined};
99
99
  border-radius: ${({ asBadge, size }) => (asBadge ? 18 : RADIUS_FROM_SIZE[size])}px;
100
100
  width: ${({ width }) => width || undefined};
101
101
  transition: all 0.2s ease-out;
@@ -163,7 +163,7 @@ const StyledTagKeyWrapper = styled.span<{ size: TSizes }>`
163
163
  display: flex;
164
164
  align-items: center;
165
165
  justify-content: center;
166
- flex: ${({ hasKey }) => (hasKey ? 1 : undefined)};
166
+ flex: ${({ hasKey, fixed }) => (hasKey ? (fixed === 'key' ? '0 0 auto' : 1) : undefined)};
167
167
  flex-shrink: 0;
168
168
  min-height: 100%;
169
169
  `;
@@ -172,7 +172,7 @@ const StyledTagContentWrapper = styled.span<{ size: TSizes }>`
172
172
  display: flex;
173
173
  align-items: center;
174
174
  justify-content: center;
175
- flex: 1;
175
+ flex: ${({ fixed }) => (fixed === 'label' ? '0 0 auto' : 1)};
176
176
  flex-shrink: 0;
177
177
  min-height: 100%;
178
178
  `;
@@ -311,6 +311,7 @@ const ReqoreTag = forwardRef<HTMLSpanElement, IReqoreTagProps>(
311
311
  wrap={wrap}
312
312
  hasWidth={!!width}
313
313
  hasKey={!!labelKey}
314
+ fixed={rest.fixed}
314
315
  >
315
316
  {icon && (
316
317
  <ReqoreIcon
@@ -345,6 +346,7 @@ const ReqoreTag = forwardRef<HTMLSpanElement, IReqoreTagProps>(
345
346
  wrap={wrap}
346
347
  hasWidth={!!width}
347
348
  hasKey={!!labelKey}
349
+ fixed={rest.fixed}
348
350
  >
349
351
  {label || label === 0 ? (
350
352
  <StyledTagContent
@@ -1,9 +1,13 @@
1
- export const Colors = {
1
+ import { TReqoreHexColor } from '../components/Effect';
2
+
3
+ export const Colors: Record<string, TReqoreHexColor> = {
2
4
  DARK: '#000000',
5
+ DARK_THEME: '#333333',
3
6
  LIGHT: '#ffffff',
4
7
  BLUE: '#0E5A8A',
5
8
  GREEN: '#0A6640',
6
9
  YELLOW: '#d1a036',
7
10
  ORANGE: '#A66321',
8
11
  RED: '#A82A2A',
12
+ GRAY: '#444444',
9
13
  };
@@ -0,0 +1,48 @@
1
+ import { IReqoreCollectionPagingOptions } from '../components/Collection';
2
+ import { IReqorePaginationProps } from '../components/Paging';
3
+ import { IReqorePagingOptions } from '../hooks/usePaging';
4
+
5
+ export type TReqorePaginationType =
6
+ | true
7
+ | 'list'
8
+ | 'infinite'
9
+ | 'auto-load'
10
+ | IReqoreCollectionPagingOptions;
11
+
12
+ export function getPaginationOptionsFromType<T>(type: TReqorePaginationType): {
13
+ pagingOptions?: Omit<IReqorePagingOptions<T>, 'items'>;
14
+ componentOptions?: Partial<Omit<IReqorePaginationProps<T>, 'items'>>;
15
+ pageControlsPosition?: 'top' | 'bottom' | 'both';
16
+ } {
17
+ if (!type) return {};
18
+
19
+ if (typeof type === 'object') {
20
+ const {
21
+ infinite,
22
+ itemsPerPage,
23
+ pagesToShow,
24
+ startPage,
25
+ pageControlsPosition = 'bottom',
26
+ ...componentOptions
27
+ } = type;
28
+ return {
29
+ pagingOptions: { infinite, itemsPerPage, pagesToShow, startPage },
30
+ pageControlsPosition,
31
+ componentOptions,
32
+ };
33
+ }
34
+
35
+ switch (type) {
36
+ case true:
37
+ return {};
38
+ case 'list':
39
+ return {
40
+ componentOptions: {
41
+ showPagesAs: 'list',
42
+ },
43
+ };
44
+ case 'infinite':
45
+ case 'auto-load':
46
+ return { pagingOptions: { infinite: true } };
47
+ }
48
+ }
@@ -3,6 +3,7 @@ import {
3
3
  TReqoreEffectGradientAnimationTrigger,
4
4
  TReqoreHexColor,
5
5
  } from '../components/Effect';
6
+ import { Colors } from './colors';
6
7
 
7
8
  export interface IReqoreSidebarTheme {
8
9
  main?: TReqoreHexColor;
@@ -94,16 +95,16 @@ export interface IReqoreIntents {
94
95
  export type TReqoreIntent = 'info' | 'success' | 'pending' | 'warning' | 'danger' | 'muted';
95
96
 
96
97
  export const DEFAULT_INTENTS: IReqoreIntents = {
97
- info: '#0E5A8A',
98
- success: '#0A6640',
99
- pending: '#d1a036',
100
- warning: '#A66321',
101
- danger: '#A82A2A',
102
- muted: '#444444',
98
+ info: Colors.BLUE,
99
+ success: Colors.GREEN,
100
+ pending: Colors.YELLOW,
101
+ warning: Colors.ORANGE,
102
+ danger: Colors.RED,
103
+ muted: Colors.GRAY,
103
104
  };
104
105
 
105
106
  export const DEFAULT_THEME: IReqoreTheme = {
106
- main: '#333333',
107
+ main: Colors.DARK_THEME,
107
108
  text: {
108
109
  dim: true,
109
110
  },
@@ -1,5 +1,5 @@
1
1
  import { size } from 'lodash';
2
- import React, { useRef, useState } from 'react';
2
+ import React, { useCallback, useRef, useState } from 'react';
3
3
  import { useMedia } from 'react-use';
4
4
  import shortid from 'shortid';
5
5
  import { useContext } from 'use-context-selector';
@@ -62,11 +62,11 @@ const ReqoreProvider: React.FC<IReqoreNotifications> = ({ children, options = {}
62
62
  : useMedia('(min-width: 480px) and (max-width: 1200px)');
63
63
  const isMobileOrTablet = isMobile || isTablet;
64
64
 
65
- const getAndIncreaseZIndex = (): number => {
65
+ const getAndIncreaseZIndex = useCallback((): number => {
66
66
  latestZIndex.current += 1;
67
67
 
68
68
  return latestZIndex.current;
69
- };
69
+ }, [latestZIndex.current]);
70
70
 
71
71
  const confirmAction = (data: IReqoreConfirmationModal): void => {
72
72
  setConfirmationModal({
@@ -1,13 +1,13 @@
1
1
  import { cloneDeep } from 'lodash';
2
2
  import merge from 'lodash/merge';
3
3
  import { rgba } from 'polished';
4
- import React, { useState } from 'react';
5
- import styled, { createGlobalStyle } from 'styled-components';
4
+ import React, { forwardRef, useState } from 'react';
5
+ import styled, { createGlobalStyle, css } from 'styled-components';
6
6
  import ReqoreLayoutWrapper from '../components/Layout';
7
7
  import { IReqoreNotificationsPosition } from '../components/Notifications';
8
8
  import { DEFAULT_THEME, IReqoreTheme } from '../constants/theme';
9
9
  import ThemeContext from '../context/ThemeContext';
10
- import { buildTheme, getMainBackgroundColor } from '../helpers/colors';
10
+ import { buildTheme, getMainBackgroundColor, getReadableColor } from '../helpers/colors';
11
11
  import PopoverProvider from './PopoverProvider';
12
12
  import ReqoreProvider from './ReqoreProvider';
13
13
  import ReqoreThemeProvider from './ThemeProvider';
@@ -56,11 +56,24 @@ const GlobalStyle = createGlobalStyle`
56
56
 
57
57
  const StyledPortal = styled.div`
58
58
  z-index: 9999;
59
+
59
60
  * {
60
61
  box-sizing: border-box;
61
62
  }
63
+
64
+ ${({ theme }) => css`
65
+ color: ${getReadableColor(theme, undefined, undefined, true)};
66
+ `}
62
67
  `;
63
68
 
69
+ const ReqorePortal = forwardRef<HTMLDivElement, {}>((_props, ref) => {
70
+ return (
71
+ <ReqoreThemeProvider>
72
+ <StyledPortal id='reqore-portal' ref={ref} />
73
+ </ReqoreThemeProvider>
74
+ );
75
+ });
76
+
64
77
  const ReqoreUIProvider: React.FC<IReqoreUIProviderProps> = ({ children, theme, options }) => {
65
78
  const [modalPortal, setModalPortal] = useState<any>(false);
66
79
 
@@ -81,8 +94,8 @@ const ReqoreUIProvider: React.FC<IReqoreUIProviderProps> = ({ children, theme, o
81
94
  </ReqoreProvider>
82
95
  ) : null}
83
96
  </ReqoreLayoutWrapper>
97
+ <ReqorePortal ref={setModalPortal} />
84
98
  </ThemeContext.Provider>
85
- <StyledPortal id='reqore-portal' ref={setModalPortal} />
86
99
  </>
87
100
  );
88
101
  };
@@ -6,9 +6,9 @@ import React from 'react';
6
6
  * @param refs - An array of refs to combine.
7
7
  * @returns A ref object.
8
8
  */
9
- export function useCombinedRefs(...refs) {
9
+ export function useCombinedRefs<T = any>(...refs) {
10
10
  const [_innerRef, setInnerRef] = React.useState(null);
11
- const targetRef = React.useRef();
11
+ const targetRef = React.useRef<T>();
12
12
 
13
13
  React.useEffect(() => {
14
14
  [...refs, setInnerRef].forEach((ref) => {
@@ -0,0 +1,110 @@
1
+ import { merge, size } from 'lodash';
2
+ import { useEffect, useMemo } from 'react';
3
+ import { useUpdateEffect } from 'react-use';
4
+ import usePagination, { usePaginationReturn } from 'react-use-pagination-hook';
5
+
6
+ export interface IReqorePagingOptions<T> {
7
+ items: T[];
8
+ startPage?: number;
9
+ pagesToShow?: number;
10
+ itemsPerPage?: number;
11
+ infinite?: boolean;
12
+ enabled?: boolean;
13
+ }
14
+
15
+ export interface IReqorePagingResult<T>
16
+ extends Pick<usePaginationReturn, 'setPage' | 'currentPage'> {
17
+ pages: number[];
18
+ allPages: number[];
19
+ items: T[];
20
+ itemsPerPage: number;
21
+ itemsLeft: number;
22
+ isLastPage: boolean;
23
+ isFirstPage: boolean;
24
+ next: usePaginationReturn['goNext'];
25
+ back: usePaginationReturn['goBefore'];
26
+ first: () => void;
27
+ last: () => void;
28
+ pageCount: number;
29
+ infinite: boolean;
30
+ }
31
+
32
+ export const defaultPagingOptions: IReqorePagingOptions<any> = {
33
+ items: [],
34
+ itemsPerPage: 10,
35
+ infinite: false,
36
+ };
37
+
38
+ export const useReqorePaging = <T>(
39
+ options: IReqorePagingOptions<T> = defaultPagingOptions
40
+ ): IReqorePagingResult<T> => {
41
+ const {
42
+ items,
43
+ itemsPerPage,
44
+ infinite,
45
+ pagesToShow,
46
+ startPage,
47
+ enabled = true,
48
+ }: IReqorePagingOptions<T> = merge({}, defaultPagingOptions, options);
49
+ const allPageCount = useMemo(() => Math.ceil(size(items) / itemsPerPage), [items, itemsPerPage]);
50
+ const { pagelist, currentPage, setPage, setTotalPage, goNext, goBefore } = usePagination({
51
+ numOfPage: allPageCount,
52
+ totalPage: allPageCount,
53
+ });
54
+
55
+ useEffect(() => {
56
+ setPage(startPage || 1);
57
+ }, [startPage]);
58
+
59
+ useUpdateEffect(() => {
60
+ setPage(1);
61
+ setTotalPage(Math.ceil(size(items) / itemsPerPage));
62
+ }, [size(items), itemsPerPage]);
63
+
64
+ const slicedItems: T[] = useMemo(() => {
65
+ return items.slice(infinite ? 0 : (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
66
+ }, [items, currentPage, itemsPerPage, infinite]);
67
+
68
+ const pages: number[] = useMemo(() => {
69
+ if (!pagesToShow || pagesToShow >= allPageCount) {
70
+ return pagelist;
71
+ }
72
+ // Get the middle of the desired page count
73
+ const middle = Math.ceil(pagesToShow / 2);
74
+ // Get the start number of the pages to show
75
+ // If the current page is the last page, we need to subtract the number of pages to show from the total number of pages
76
+ const start =
77
+ currentPage === allPageCount ? allPageCount - pagesToShow : Math.max(currentPage - middle, 0);
78
+ // Get the end number of the pages to show
79
+ // If the start number is 0, we need to add the number of pages to show to the total number of pages
80
+ const end = Math.min(start + pagesToShow, allPageCount);
81
+
82
+ let newPages = pagelist.slice(start, end);
83
+ // Always remove the first page
84
+ newPages = newPages.filter((page) => page !== 1);
85
+ // Always remove the last page
86
+ newPages = newPages.filter((page) => page !== allPageCount);
87
+ // Add the first and last page
88
+ newPages = [1, ...newPages, allPageCount];
89
+
90
+ return newPages;
91
+ }, [pagesToShow, allPageCount, currentPage, pagelist]);
92
+
93
+ return {
94
+ pages,
95
+ allPages: pagelist,
96
+ pageCount: allPageCount,
97
+ items: enabled ? slicedItems : items,
98
+ itemsPerPage,
99
+ itemsLeft: items.length - slicedItems.length,
100
+ infinite,
101
+ setPage,
102
+ currentPage,
103
+ next: goNext,
104
+ back: goBefore,
105
+ isLastPage: currentPage === allPageCount,
106
+ isFirstPage: currentPage === 1,
107
+ first: () => setPage(1),
108
+ last: () => setPage(allPageCount),
109
+ };
110
+ };
package/src/index.tsx CHANGED
@@ -37,6 +37,7 @@ export { default as ReqoreNavbarGroup } from './components/Navbar/group';
37
37
  export { default as ReqoreNavbarItem } from './components/Navbar/item';
38
38
  export { default as ReqoreNotificationsWrapper } from './components/Notifications';
39
39
  export { default as ReqoreNotification } from './components/Notifications/notification';
40
+ export { ReqorePagination } from './components/Paging';
40
41
  export { ReqorePanel } from './components/Panel';
41
42
  export { ReqoreP, ReqoreP as ReqoreParagraph } from './components/Paragraph';
42
43
  export { default as ReqorePopover } from './components/Popover';
@@ -62,6 +63,7 @@ export { default as ReqoreUIProvider } from './containers/UIProvider';
62
63
  export { default as ReqoreContext } from './context/ReqoreContext';
63
64
  export { default as ReqoreThemeContext } from './context/ThemeContext';
64
65
  export { default as useLatestZIndex } from './hooks/useLatestZIndex';
66
+ export { useReqorePaging } from './hooks/usePaging';
65
67
  export { useReqore } from './hooks/useReqore';
66
68
  export { useReqoreProperty } from './hooks/useReqoreContext';
67
69
  export { useReqoreTheme } from './hooks/useTheme';
@@ -5,6 +5,25 @@ import ReqoreMessage from '../components/Message';
5
5
  import { ReqoreP } from '../components/Paragraph';
6
6
  import { ReqoreSpacer } from '../components/Spacer';
7
7
  import ReqoreTag from '../components/Tag';
8
+ import data from './data.json';
9
+
10
+ export const bigCollection = data.slice(0, 100).map((datum) => ({
11
+ label: `${datum.firstName} ${datum.lastName}`,
12
+ badge: datum.id,
13
+ size: 'small',
14
+ expandable: true,
15
+ content: datum.address,
16
+ tags: [
17
+ {
18
+ labelKey: 'Age',
19
+ label: datum.age,
20
+ },
21
+ {
22
+ labelKey: 'Occupation',
23
+ label: datum.occupation,
24
+ },
25
+ ],
26
+ })) as IReqoreCollectionItemProps[];
8
27
 
9
28
  export default [
10
29
  {
@@ -1,6 +1,7 @@
1
1
  import { IReqoreTableColumn } from '../components/Table';
2
2
  import data from './data.json';
3
3
 
4
+ export const tableData = data;
4
5
  export default {
5
6
  columns: [
6
7
  {