@qoretechnologies/reqore 0.36.8 → 0.37.1

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 (40) hide show
  1. package/dist/components/Collection/item.d.ts.map +1 -1
  2. package/dist/components/Collection/item.js +2 -2
  3. package/dist/components/Collection/item.js.map +1 -1
  4. package/dist/components/Drawer/backdrop.d.ts +10 -0
  5. package/dist/components/Drawer/backdrop.d.ts.map +1 -0
  6. package/dist/components/Drawer/backdrop.js +59 -0
  7. package/dist/components/Drawer/backdrop.js.map +1 -0
  8. package/dist/components/Drawer/index.d.ts +0 -1
  9. package/dist/components/Drawer/index.d.ts.map +1 -1
  10. package/dist/components/Drawer/index.js +4 -20
  11. package/dist/components/Drawer/index.js.map +1 -1
  12. package/dist/components/Navbar/index.d.ts.map +1 -1
  13. package/dist/components/Navbar/index.js +1 -1
  14. package/dist/components/Navbar/index.js.map +1 -1
  15. package/dist/components/Sidebar/index.d.ts.map +1 -1
  16. package/dist/components/Sidebar/index.js +2 -2
  17. package/dist/components/Sidebar/index.js.map +1 -1
  18. package/dist/containers/ReqoreProvider.d.ts +7 -0
  19. package/dist/containers/ReqoreProvider.d.ts.map +1 -1
  20. package/dist/containers/ReqoreProvider.js +93 -36
  21. package/dist/containers/ReqoreProvider.js.map +1 -1
  22. package/dist/containers/UIProvider.d.ts.map +1 -1
  23. package/dist/containers/UIProvider.js +1 -2
  24. package/dist/containers/UIProvider.js.map +1 -1
  25. package/dist/context/ReqoreContext.d.ts +3 -1
  26. package/dist/context/ReqoreContext.d.ts.map +1 -1
  27. package/dist/context/ReqoreContext.js +2 -0
  28. package/dist/context/ReqoreContext.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/components/Collection/item.tsx +4 -5
  31. package/src/components/Drawer/backdrop.tsx +43 -0
  32. package/src/components/Drawer/index.tsx +4 -23
  33. package/src/components/Navbar/index.tsx +1 -0
  34. package/src/components/Sidebar/index.tsx +3 -4
  35. package/src/containers/ReqoreProvider.tsx +131 -66
  36. package/src/containers/UIProvider.tsx +1 -6
  37. package/src/context/ReqoreContext.tsx +10 -1
  38. package/src/stories/Drawer/Drawer.stories.tsx +5 -1
  39. package/src/stories/Modal/Manager.stories.tsx +166 -0
  40. package/tests.json +1 -1
@@ -1,9 +1,11 @@
1
- import { size } from 'lodash';
1
+ import { map, size } from 'lodash';
2
2
  import React, { useCallback, useRef, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
3
4
  import { useMedia } from 'react-use';
4
5
  import shortid from 'shortid';
5
6
  import { useContext } from 'use-context-selector';
6
7
  import { ReqoreModal, ReqoreTextEffect } from '..';
8
+ import { IReqoreModalProps } from '../components/Modal';
7
9
  import ReqoreNotificationsWrapper from '../components/Notifications';
8
10
  import ReqoreNotification, {
9
11
  IReqoreNotificationProps,
@@ -12,6 +14,7 @@ import { IReqoreTheme, TReqoreIntent } from '../constants/theme';
12
14
  import ReqoreContext from '../context/ReqoreContext';
13
15
  import ThemeContext from '../context/ThemeContext';
14
16
  import { IReqoreIconName } from '../types/icons';
17
+ import PopoverProvider from './PopoverProvider';
15
18
  import { IReqoreOptions } from './UIProvider';
16
19
 
17
20
  export interface IReqoreNotificationData extends IReqoreNotificationProps {
@@ -27,6 +30,13 @@ export interface IReqoreNotifications {
27
30
  options?: IReqoreOptions;
28
31
  }
29
32
 
33
+ export interface IReqoreModals {
34
+ [id: string]: IReqoreModalFromProps | TReqoreCustomModal;
35
+ }
36
+
37
+ export interface IReqoreModalFromProps extends IReqoreModalProps {}
38
+ export type TReqoreCustomModal = React.ReactElement<IReqoreModalProps>;
39
+
30
40
  export interface IReqoreConfirmationModal {
31
41
  title?: string;
32
42
  description?: string;
@@ -42,6 +52,7 @@ export interface IReqoreConfirmationModal {
42
52
 
43
53
  const ReqoreProvider: React.FC<IReqoreNotifications> = ({ children, options = {} }) => {
44
54
  const [notifications, setNotifications] = useState<IReqoreNotificationData[] | null>([]);
55
+ const [modals, setModals] = useState<IReqoreModals>({});
45
56
  const [confirmationModal, setConfirmationModal] = useState<IReqoreConfirmationModal>({});
46
57
  const theme: IReqoreTheme = useContext<IReqoreTheme>(ThemeContext);
47
58
  const latestZIndex = useRef<number>(9000);
@@ -74,6 +85,31 @@ const ReqoreProvider: React.FC<IReqoreNotifications> = ({ children, options = {}
74
85
  }));
75
86
  };
76
87
 
88
+ const addModal = useCallback(
89
+ (modal: IReqoreModalFromProps | TReqoreCustomModal, id?: string): string => {
90
+ const _id = id || shortid.generate();
91
+
92
+ setModals((cur) => {
93
+ return {
94
+ ...cur,
95
+ [_id]: modal,
96
+ };
97
+ });
98
+
99
+ return _id;
100
+ },
101
+ []
102
+ );
103
+
104
+ const removeModal = useCallback((id: string): void => {
105
+ setModals((cur) => {
106
+ const newModals = { ...cur };
107
+ delete newModals[id];
108
+
109
+ return newModals;
110
+ });
111
+ }, []);
112
+
77
113
  const addNotification = (data: IReqoreNotificationData) => {
78
114
  setNotifications((cur) => {
79
115
  let newNotifications = [...cur];
@@ -111,6 +147,8 @@ const ReqoreProvider: React.FC<IReqoreNotifications> = ({ children, options = {}
111
147
  theme,
112
148
  addNotification,
113
149
  removeNotification,
150
+ addModal,
151
+ removeModal,
114
152
  confirmAction,
115
153
  isMobile,
116
154
  isTablet,
@@ -123,77 +161,104 @@ const ReqoreProvider: React.FC<IReqoreNotifications> = ({ children, options = {}
123
161
  'closePopoversOnEscPress' in options ? options.closePopoversOnEscPress : true,
124
162
  }}
125
163
  >
126
- {size(notifications) > 0 ? (
127
- <ReqoreNotificationsWrapper position={options.notificationsPosition}>
128
- {notifications.map((notification) => (
129
- <ReqoreNotification
130
- {...notification}
131
- key={notification.id}
132
- onClick={
133
- notification.onClick
134
- ? () => void notification.onClick(notification.id)
135
- : undefined
136
- }
137
- onClose={() => {
138
- if (notification.onClose) {
139
- notification.onClose(notification.id);
164
+ <PopoverProvider uiScale={options?.uiScale}>
165
+ {size(notifications) > 0 ? (
166
+ <ReqoreNotificationsWrapper position={options.notificationsPosition}>
167
+ {notifications.map((notification) => (
168
+ <ReqoreNotification
169
+ {...notification}
170
+ key={notification.id}
171
+ onClick={
172
+ notification.onClick
173
+ ? () => void notification.onClick(notification.id)
174
+ : undefined
140
175
  }
176
+ onClose={() => {
177
+ if (notification.onClose) {
178
+ notification.onClose(notification.id);
179
+ }
141
180
 
142
- removeNotification(notification.id);
143
- }}
144
- onFinish={() => {
145
- if (notification.onFinish) {
146
- notification.onFinish(notification.id);
147
- }
181
+ removeNotification(notification.id);
182
+ }}
183
+ onFinish={() => {
184
+ if (notification.onFinish) {
185
+ notification.onFinish(notification.id);
186
+ }
148
187
 
149
- removeNotification(notification.id);
150
- }}
151
- />
152
- ))}
153
- </ReqoreNotificationsWrapper>
154
- ) : null}
155
- {children}
156
- {confirmationModal.isOpen && (
157
- <ReqoreModal
158
- isOpen
159
- flat
160
- opacity={0.9}
161
- blur={2}
162
- width='500px'
163
- intent={confirmationModal.intent}
164
- label={confirmationModal.title || 'Confirm your action'}
165
- icon='ErrorWarningFill'
166
- className='reqore-confirmation-modal'
167
- bottomActions={[
168
- {
169
- label: confirmationModal.cancelLabel || 'Cancel',
170
- icon: 'CloseLine',
171
- onClick: () => {
172
- confirmationModal?.onCancel?.();
173
- closeConfirmationModal();
188
+ removeNotification(notification.id);
189
+ }}
190
+ />
191
+ ))}
192
+ </ReqoreNotificationsWrapper>
193
+ ) : null}
194
+ {children}
195
+ {confirmationModal.isOpen && (
196
+ <ReqoreModal
197
+ isOpen
198
+ flat
199
+ opacity={0.9}
200
+ blur={2}
201
+ width='500px'
202
+ intent={confirmationModal.intent}
203
+ label={confirmationModal.title || 'Confirm your action'}
204
+ icon='ErrorWarningFill'
205
+ className='reqore-confirmation-modal'
206
+ bottomActions={[
207
+ {
208
+ label: confirmationModal.cancelLabel || 'Cancel',
209
+ icon: 'CloseLine',
210
+ onClick: () => {
211
+ confirmationModal?.onCancel?.();
212
+ closeConfirmationModal();
213
+ },
214
+ position: 'left',
174
215
  },
175
- position: 'left',
176
- },
177
- {
178
- label: confirmationModal.confirmLabel || 'Confirm',
179
- intent: confirmationModal.confirmButtonIntent || 'success',
180
- icon: confirmationModal.confirmIcon || 'CheckLine',
181
- onClick: () => {
182
- confirmationModal?.onConfirm?.();
183
- closeConfirmationModal();
216
+ {
217
+ label: confirmationModal.confirmLabel || 'Confirm',
218
+ intent: confirmationModal.confirmButtonIntent || 'success',
219
+ icon: confirmationModal.confirmIcon || 'CheckLine',
220
+ onClick: () => {
221
+ confirmationModal?.onConfirm?.();
222
+ closeConfirmationModal();
223
+ },
224
+ position: 'right',
184
225
  },
185
- position: 'right',
186
- },
187
- ]}
188
- >
189
- <ReqoreTextEffect
190
- as='p'
191
- effect={{ textAlign: 'center', weight: 'bold', textSize: 'big' }}
226
+ ]}
192
227
  >
193
- {confirmationModal.description || 'Are you sure you want to proceed?'}
194
- </ReqoreTextEffect>
195
- </ReqoreModal>
196
- )}
228
+ <ReqoreTextEffect
229
+ as='p'
230
+ effect={{ textAlign: 'center', weight: 'bold', textSize: 'big' }}
231
+ >
232
+ {confirmationModal.description || 'Are you sure you want to proceed?'}
233
+ </ReqoreTextEffect>
234
+ </ReqoreModal>
235
+ )}
236
+ {map(modals, (modal, key) =>
237
+ React.isValidElement(modal) ? (
238
+ createPortal(
239
+ React.cloneElement(modal, {
240
+ key,
241
+ isOpen: true,
242
+ onClose: () => {
243
+ removeModal(key);
244
+ modal.props.onClose?.();
245
+ },
246
+ }),
247
+ document.querySelector('#reqore-portal')!
248
+ )
249
+ ) : (
250
+ <ReqoreModal
251
+ {...modal}
252
+ key={key}
253
+ isOpen
254
+ onClose={() => {
255
+ removeModal(key);
256
+ modal.onClose?.();
257
+ }}
258
+ />
259
+ )
260
+ )}
261
+ </PopoverProvider>
197
262
  </ReqoreContext.Provider>
198
263
  </>
199
264
  );
@@ -8,7 +8,6 @@ import { IReqoreNotificationsPosition } from '../components/Notifications';
8
8
  import { DEFAULT_THEME, IReqoreTheme } from '../constants/theme';
9
9
  import ThemeContext from '../context/ThemeContext';
10
10
  import { buildTheme, getMainBackgroundColor, getReadableColor } from '../helpers/colors';
11
- import PopoverProvider from './PopoverProvider';
12
11
  import ReqoreProvider from './ReqoreProvider';
13
12
  import ReqoreThemeProvider from './ThemeProvider';
14
13
 
@@ -88,11 +87,7 @@ const ReqoreUIProvider: React.FC<IReqoreUIProviderProps> = ({ children, theme, o
88
87
  <GlobalStyle />
89
88
  </ReqoreThemeProvider>
90
89
  <ReqoreLayoutWrapper withSidebar={options?.withSidebar}>
91
- {modalPortal ? (
92
- <ReqoreProvider options={options}>
93
- <PopoverProvider uiScale={options?.uiScale}>{children}</PopoverProvider>
94
- </ReqoreProvider>
95
- ) : null}
90
+ {modalPortal ? <ReqoreProvider options={options}>{children}</ReqoreProvider> : null}
96
91
  </ReqoreLayoutWrapper>
97
92
  <ReqorePortal ref={setModalPortal} />
98
93
  </ThemeContext.Provider>
@@ -1,12 +1,19 @@
1
1
  import { createContext } from 'use-context-selector';
2
2
  import { DEFAULT_THEME, IReqoreTheme } from '../constants/theme';
3
- import { IReqoreConfirmationModal, IReqoreNotificationData } from '../containers/ReqoreProvider';
3
+ import {
4
+ IReqoreConfirmationModal,
5
+ IReqoreModalFromProps,
6
+ IReqoreNotificationData,
7
+ TReqoreCustomModal,
8
+ } from '../containers/ReqoreProvider';
4
9
  import { IReqoreOptions } from '../containers/UIProvider';
5
10
 
6
11
  export interface IReqoreContext {
7
12
  readonly confirmAction: (data: IReqoreConfirmationModal) => void;
8
13
  readonly notifications?: IReqoreNotificationData[] | null;
9
14
  readonly addNotification?: (data: IReqoreNotificationData) => any;
15
+ readonly addModal?: (modal: IReqoreModalFromProps | TReqoreCustomModal, id?: string) => string;
16
+ readonly removeModal?: (id: string) => void;
10
17
  readonly removeNotification?: (id: string) => any;
11
18
  readonly isMobile?: boolean;
12
19
  readonly isTablet?: boolean;
@@ -24,6 +31,8 @@ export default createContext<IReqoreContext>({
24
31
  notifications: null,
25
32
  addNotification: null,
26
33
  removeNotification: null,
34
+ addModal: null,
35
+ removeModal: null,
27
36
  animations: {
28
37
  buttons: true,
29
38
  dialogs: true,
@@ -4,6 +4,7 @@ import { IReqoreDrawerProps, ReqoreDrawer } from '../../components/Drawer';
4
4
  import { IReqoreInputProps } from '../../components/Input';
5
5
  import {
6
6
  ReqoreButton,
7
+ ReqoreCollection,
7
8
  ReqoreInput,
8
9
  ReqorePanel,
9
10
  ReqoreTabs,
@@ -246,7 +247,10 @@ const Template: StoryFn<typeof ReqoreDrawer> = (args) => {
246
247
  Hello I am a super long button that opens a modal on click
247
248
  </ReqoreButton>
248
249
  </ReqoreTabsContent>
249
- <ReqoreTabsContent tabId='tab2'>Tab 2 here</ReqoreTabsContent>
250
+ <ReqoreTabsContent tabId='tab2'>
251
+ Tab 2 here
252
+ <ReqoreCollection items={[{ label: 'Item 1' }, { label: 'Item 2' }]} />
253
+ </ReqoreTabsContent>
250
254
  </ReqoreTabs>
251
255
  </ReqoreDrawer>
252
256
  </>
@@ -0,0 +1,166 @@
1
+ import { expect } from '@storybook/jest';
2
+ import { StoryObj } from '@storybook/react';
3
+ import { fireEvent, waitFor, within } from '@storybook/testing-library';
4
+ import { noop } from 'lodash';
5
+ import { useEffect, useState } from 'react';
6
+ import { ReqoreBackdrop } from '../../components/Drawer/backdrop';
7
+ import {
8
+ ReqoreButton,
9
+ ReqoreCollection,
10
+ ReqoreControlGroup,
11
+ ReqoreInput,
12
+ ReqoreModal,
13
+ ReqorePanel,
14
+ useReqoreProperty,
15
+ } from '../../index';
16
+ import { StoryMeta } from '../utils';
17
+
18
+ const TIMEOUT = 2000;
19
+
20
+ const meta = {
21
+ title: 'Utilities/Global Modal/Tests',
22
+ component: ReqoreModal,
23
+ render: ({ data, updateToData }) => {
24
+ const addModal = useReqoreProperty('addModal');
25
+ const [modalId, setModalId] = useState<string>(undefined);
26
+
27
+ useEffect(() => {
28
+ if (updateToData && modalId) {
29
+ setTimeout(() => {
30
+ addModal(updateToData, modalId);
31
+ }, TIMEOUT);
32
+ }
33
+ }, [modalId]);
34
+
35
+ return (
36
+ <ReqoreControlGroup>
37
+ <ReqoreButton
38
+ id='modal'
39
+ onClick={() => {
40
+ setModalId(addModal(data, 'modal'));
41
+ }}
42
+ >
43
+ Open new modal
44
+ </ReqoreButton>
45
+ </ReqoreControlGroup>
46
+ );
47
+ },
48
+ } as StoryMeta<any>;
49
+
50
+ export default meta;
51
+ type Story = StoryObj<typeof meta>;
52
+
53
+ export const FromObject: Story = {
54
+ args: {
55
+ data: {
56
+ label: 'Test modal',
57
+ children: (
58
+ <ReqorePanel collapsible>This is a modal from an object with complex children</ReqorePanel>
59
+ ),
60
+ bottomActions: [
61
+ {
62
+ label: 'Cancel',
63
+ onClick: noop,
64
+ icon: 'CloseLine',
65
+ },
66
+ ],
67
+ },
68
+ },
69
+ play: async ({ canvasElement }) => {
70
+ const canvas = within(canvasElement);
71
+
72
+ await fireEvent.click(canvas.getAllByText('Open new modal')[0]);
73
+
74
+ await expect(document.querySelector('.reqore-modal')).toBeTruthy();
75
+ },
76
+ };
77
+
78
+ const ModalWithState = (props) => {
79
+ const [text, setText] = useState('This is a modal with its own state');
80
+ const addModal = useReqoreProperty('addModal');
81
+ const removeModal = useReqoreProperty('removeModal');
82
+
83
+ return (
84
+ <ReqoreModal label={props.label} {...props}>
85
+ <ReqoreControlGroup>
86
+ <ReqoreInput
87
+ value={text}
88
+ intent={props.intent}
89
+ onChange={(e: any) => setText(e.target.value)}
90
+ />
91
+ <ReqoreButton
92
+ onClick={() => addModal(<ModalWithState label='Another modal' />)}
93
+ tooltip='Open sesame'
94
+ >
95
+ Open another modal
96
+ </ReqoreButton>
97
+ <ReqoreButton onClick={() => removeModal('modal')}>Remove modal</ReqoreButton>
98
+ </ReqoreControlGroup>
99
+ <ReqoreCollection items={[{ label: 'Item 1' }, { label: 'Item 2' }]} />
100
+ </ReqoreModal>
101
+ );
102
+ };
103
+
104
+ export const FromElement: Story = {
105
+ args: {
106
+ data: (<ModalWithState label='Test modal' />) as any,
107
+ },
108
+ play: async ({ canvasElement, ...rest }) => {
109
+ await FromObject.play({ canvasElement, ...rest });
110
+ },
111
+ };
112
+
113
+ export const FromCustomElement: Story = {
114
+ args: {
115
+ data: (
116
+ <>
117
+ <ReqoreBackdrop />
118
+ <div
119
+ className='custom-modal reqore-modal'
120
+ style={{ position: 'absolute', left: '50%', top: '50%' }}
121
+ >
122
+ This is a custom modal
123
+ </div>
124
+ </>
125
+ ),
126
+ },
127
+ play: async ({ canvasElement, ...rest }) => {
128
+ await FromObject.play({ canvasElement, ...rest });
129
+ },
130
+ };
131
+
132
+ export const CanBeClosed: Story = {
133
+ ...FromObject,
134
+ play: async ({ canvasElement, ...rest }) => {
135
+ await FromObject.play({ canvasElement, ...rest });
136
+ await fireEvent.click(document.querySelector('.reqore-drawer-close-button'));
137
+
138
+ await expect(document.querySelector('.reqore-modal')).toBeNull();
139
+ },
140
+ };
141
+
142
+ export const CanBeClosedManually: Story = {
143
+ ...FromElement,
144
+ play: async ({ canvasElement, ...rest }) => {
145
+ const canvas = within(canvasElement);
146
+ await FromObject.play({ canvasElement, ...rest });
147
+ await waitFor(async () => await canvas.findAllByText('Remove modal')[0], { timeout: 10000 });
148
+
149
+ await fireEvent.click(canvas.getAllByText('Remove modal')[0]);
150
+ },
151
+ };
152
+
153
+ export const CanBeUpdated: Story = {
154
+ args: {
155
+ data: (<ModalWithState label='Test modal' />) as any,
156
+ updateToData: (<ModalWithState label='Updated modal' intent='info' />) as any,
157
+ },
158
+ play: async ({ canvasElement, ...rest }) => {
159
+ const canvas = within(canvasElement);
160
+ await FromElement.play({ canvasElement, ...rest });
161
+
162
+ await waitFor(async () => await canvas.findByText('Updated modal'), { timeout: 10000 });
163
+
164
+ await expect(canvas.findByText('Updated modal')).toBeTruthy();
165
+ },
166
+ };