Skip to content

Commit ddde2b0

Browse files
authored
[@mantine/modals] Improve types of context modals (#8439)
* Correctly infer MantineModalsOverride types to openContextModal, closeContextModal and updateContextModal * Add proper closeContextModal, remove unecessary typings * Export closeContextModal * Properly infer modals in Provider too, remove unecessary typings, add closeContextModal, add proper logic to updateContextModal * Remove unecessary typing * Add closeContextModal to test * Update demos and stories
1 parent c3a53eb commit ddde2b0

9 files changed

Lines changed: 99 additions & 107 deletions

File tree

packages/@docs/demos/src/demos/modals/Modals.demo.context.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function Demo() {
3131
<Button
3232
onClick={() =>
3333
modals.openContextModal({
34-
modal: 'demonstration',
34+
modalKey: 'demonstration',
3535
title: 'Test modal from context',
3636
innerProps: {
3737
modalBody:

packages/@docs/demos/src/demos/modals/Modals.demo.updateContextModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function Demo() {
7777
<Button
7878
onClick={() => {
7979
const modalId = modals.openContextModal({
80-
modal: 'asyncDemonstration',
80+
modalKey: 'asyncDemonstration',
8181
title: 'Processing...',
8282
closeOnEscape: false,
8383
closeOnClickOutside: false,
@@ -90,6 +90,7 @@ function Demo() {
9090

9191
setTimeout(() => {
9292
modals.updateContextModal({
93+
modalKey: 'asyncDemonstration',
9394
modalId,
9495
title: 'Processing Complete!',
9596
closeOnEscape: true,

packages/@mantine/modals/src/Modals.story.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function Usage() {
2727
const showContextModal = () =>
2828
openContextModal({
2929
modalId: 'context-modal',
30-
modal: 'hello',
30+
modalKey: 'hello',
3131
title: 'Context modal',
3232
centered: true,
3333
onClose: () => console.log('context modal closed'),
@@ -160,7 +160,7 @@ function CloseAllApp() {
160160
centered: true,
161161
onClose: () => {
162162
console.log('Close modal', modalId);
163-
modals.closeAll();
163+
modals.closeAllModals();
164164
},
165165
});
166166
};
@@ -195,7 +195,8 @@ function UpdateContextModal() {
195195
const modals = useModals();
196196

197197
const handleOpenAsyncConfirmModal = () => {
198-
const modalId = modals.openContextModal('asyncProcessing', {
198+
const modalId = modals.openContextModal({
199+
modalKey: 'asyncProcessing',
199200
title: 'Processing...',
200201
innerProps: {
201202
modalBody: 'You cannot close the modal during this operation.',
@@ -209,6 +210,7 @@ function UpdateContextModal() {
209210

210211
setTimeout(() => {
211212
modals.updateContextModal({
213+
modalKey: 'asyncProcessing',
212214
modalId,
213215
title: 'Processing Complete!',
214216
closeButtonProps: { disabled: false },

packages/@mantine/modals/src/ModalsProvider.tsx

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import { randomId } from '@mantine/hooks';
44
import { ConfirmModal } from './ConfirmModal';
55
import {
66
ConfirmLabels,
7-
ContextModalProps,
7+
MantineModals,
88
ModalsContext,
99
ModalsContextProps,
1010
ModalSettings,
1111
OpenConfirmModal,
12-
OpenContextModal,
1312
} from './context';
1413
import { useModalsEvents } from './events';
1514
import { modalsReducer } from './reducer';
@@ -19,7 +18,7 @@ export interface ModalsProviderProps {
1918
children?: React.ReactNode;
2019

2120
/** Predefined modals */
22-
modals?: Record<string, React.FC<ContextModalProps<any>>>;
21+
modals?: MantineModals;
2322

2423
/** Shared Modal component props, applied for every modal */
2524
modalProps?: ModalSettings;
@@ -72,17 +71,9 @@ export function ModalsProvider({ children, modalProps, labels, modals }: ModalsP
7271
const stateRef = useRef(state);
7372
stateRef.current = state;
7473

75-
const closeAll = useCallback(
76-
(canceled?: boolean) => {
77-
dispatch({ type: 'CLOSE_ALL', canceled });
78-
},
79-
[stateRef, dispatch]
80-
);
81-
82-
const openModal = useCallback(
83-
({ modalId, ...props }: ModalSettings) => {
74+
const openModal: ModalsContextProps['openModal'] = useCallback(
75+
({ modalId, ...props }) => {
8476
const id = modalId || randomId();
85-
8677
dispatch({
8778
type: 'OPEN',
8879
modal: {
@@ -96,8 +87,8 @@ export function ModalsProvider({ children, modalProps, labels, modals }: ModalsP
9687
[dispatch]
9788
);
9889

99-
const openConfirmModal = useCallback(
100-
({ modalId, ...props }: OpenConfirmModal) => {
90+
const openConfirmModal: ModalsContextProps['openConfirmModal'] = useCallback(
91+
({ modalId, ...props }) => {
10192
const id = modalId || randomId();
10293
dispatch({
10394
type: 'OPEN',
@@ -112,55 +103,72 @@ export function ModalsProvider({ children, modalProps, labels, modals }: ModalsP
112103
[dispatch]
113104
);
114105

115-
const openContextModal = useCallback(
116-
(modal: string, { modalId, ...props }: OpenContextModal) => {
106+
const openContextModal: ModalsContextProps['openContextModal'] = useCallback(
107+
({ modalId, modalKey, ...props }) => {
117108
const id = modalId || randomId();
118109
dispatch({
119110
type: 'OPEN',
120111
modal: {
121112
id,
122113
type: 'context',
123114
props,
124-
ctx: modal,
115+
ctx: modalKey,
125116
},
126117
});
127118
return id;
128119
},
129120
[dispatch]
130121
);
131122

132-
const closeModal = useCallback(
133-
(id: string, canceled?: boolean) => {
123+
const closeModal: ModalsContextProps['closeModal'] = useCallback(
124+
(modalId: string, canceled?: boolean) => {
125+
dispatch({ type: 'CLOSE', modalId, canceled });
126+
},
127+
[stateRef, dispatch]
128+
);
129+
130+
const closeContextModal: ModalsContextProps['closeContextModal'] = useCallback(
131+
(modalKey: string, canceled?: boolean) => {
132+
const id =
133+
stateRef.current.modals.find((m) => m.type === 'context' && m.ctx === modalKey)?.id ??
134+
modalKey;
134135
dispatch({ type: 'CLOSE', modalId: id, canceled });
135136
},
136137
[stateRef, dispatch]
137138
);
138139

139-
const updateModal = useCallback(
140-
({ modalId, ...newProps }: Partial<ModalSettings> & { modalId: string }) => {
141-
dispatch({
142-
type: 'UPDATE',
143-
modalId,
144-
newProps,
145-
});
140+
const closeAllModals: ModalsContextProps['closeAllModals'] = useCallback(
141+
(canceled?: boolean) => {
142+
dispatch({ type: 'CLOSE_ALL', canceled });
146143
},
147-
[dispatch]
144+
[stateRef, dispatch]
148145
);
149146

150-
const updateContextModal = useCallback(
151-
({ modalId, ...newProps }: { modalId: string } & Partial<OpenContextModal<any>>) => {
147+
const updateModal: ModalsContextProps['updateModal'] = useCallback(
148+
({ modalId, ...newProps }) => {
152149
dispatch({ type: 'UPDATE', modalId, newProps });
153150
},
154151
[dispatch]
155152
);
156153

154+
const updateContextModal: ModalsContextProps['updateContextModal'] = useCallback(
155+
({ modalKey, modalId, ...newProps }) => {
156+
const id =
157+
modalId ??
158+
stateRef.current.modals.find((m) => m.type === 'context' && m.ctx === modalKey)?.id ??
159+
modalKey;
160+
dispatch({ type: 'UPDATE', newProps, modalId: id });
161+
},
162+
[stateRef, dispatch]
163+
);
164+
157165
useModalsEvents({
158166
openModal,
159167
openConfirmModal,
160-
openContextModal: ({ modal, ...payload }: any) => openContextModal(modal, payload),
168+
openContextModal,
161169
closeModal,
162-
closeContextModal: closeModal,
163-
closeAllModals: closeAll,
170+
closeContextModal,
171+
closeAllModals,
164172
updateModal,
165173
updateContextModal,
166174
});
@@ -172,8 +180,8 @@ export function ModalsProvider({ children, modalProps, labels, modals }: ModalsP
172180
openConfirmModal,
173181
openContextModal,
174182
closeModal,
175-
closeContextModal: closeModal,
176-
closeAll,
183+
closeContextModal,
184+
closeAllModals,
177185
updateModal,
178186
updateContextModal,
179187
};
@@ -231,7 +239,7 @@ export function ModalsProvider({ children, modalProps, labels, modals }: ModalsP
231239
{...modalProps}
232240
{...currentModalProps}
233241
opened={state.modals.length > 0}
234-
onClose={() => closeModal(state.current?.id as any)}
242+
onClose={() => closeModal(state.current?.id as string)}
235243
>
236244
{content}
237245
</Modal>

packages/@mantine/modals/src/context.ts

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { createContext, ReactNode } from 'react';
2-
import { ModalProps } from '@mantine/core';
2+
import { DataAttributes, ModalProps } from '@mantine/core';
33
import type { ConfirmModalProps } from './ConfirmModal';
44

55
export type ModalSettings = Partial<Omit<ModalProps, 'opened'>> & { modalId?: string };
66

77
export type ConfirmLabels = Record<'confirm' | 'cancel', ReactNode>;
88

99
export interface OpenConfirmModal extends ModalSettings, ConfirmModalProps {}
10-
export interface OpenContextModal<CustomProps extends Record<string, any> = {}>
11-
extends ModalSettings {
12-
innerProps: CustomProps;
13-
}
10+
11+
export type ContextModalInnerProps<
12+
TKey extends MantineModal,
13+
P = Parameters<MantineModals[TKey]>[0]['innerProps'],
14+
> = keyof NonNullable<P> extends never ? { innerProps?: never } : { innerProps: P };
15+
16+
export type OpenContextModal<TKey extends MantineModal = string> = ModalSettings &
17+
ContextModalInnerProps<TKey>;
1418

1519
export interface ContextModalProps<T extends Record<string, any> = {}> {
1620
context: ModalsContextProps;
@@ -29,27 +33,24 @@ export interface ModalsContextProps {
2933
openModal: (props: ModalSettings) => string;
3034
openConfirmModal: (props: OpenConfirmModal) => string;
3135
openContextModal: <TKey extends MantineModal>(
32-
modal: TKey,
33-
props: OpenContextModal<Parameters<MantineModals[TKey]>[0]['innerProps']>
36+
props: { modalKey: TKey } & OpenContextModal<TKey> & DataAttributes
3437
) => string;
35-
closeModal: (id: string, canceled?: boolean) => void;
36-
closeContextModal: <TKey extends MantineModal>(id: TKey, canceled?: boolean) => void;
37-
closeAll: () => void;
38-
updateModal: (payload: { modalId: string } & Partial<OpenConfirmModal>) => void;
39-
updateContextModal: (payload: { modalId: string } & Partial<OpenContextModal<any>>) => void;
38+
closeModal: (modalId: string, canceled?: boolean) => void;
39+
closeContextModal: <TKey extends MantineModal>(modalKey: TKey, canceled?: boolean) => void;
40+
closeAllModals: () => void;
41+
updateModal: (props: { modalId: string } & Partial<OpenConfirmModal>) => void;
42+
updateContextModal: <TKey extends MantineModal>(
43+
props: { modalKey: TKey } & Partial<OpenContextModal<TKey>>
44+
) => void;
4045
}
4146

4247
export interface MantineModalsOverride {}
4348

44-
export type MantineModalsOverwritten = MantineModalsOverride extends {
45-
modals: Record<string, React.FC<ContextModalProps<any>>>;
49+
export type MantineModals = MantineModalsOverride extends {
50+
modals: infer CustomModals;
4651
}
47-
? MantineModalsOverride
48-
: {
49-
modals: Record<string, React.FC<ContextModalProps<any>>>;
50-
};
51-
52-
export type MantineModals = MantineModalsOverwritten['modals'];
52+
? CustomModals
53+
: Record<string, React.FC<ContextModalProps<any>>>;
5354

5455
export type MantineModal = keyof MantineModals;
5556

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,8 @@
1-
import { createUseExternalEvents, DataAttributes } from '@mantine/core';
1+
import { createUseExternalEvents } from '@mantine/core';
22
import { randomId } from '@mantine/hooks';
3-
import {
4-
MantineModal,
5-
MantineModals,
6-
ModalSettings,
7-
OpenConfirmModal,
8-
OpenContextModal,
9-
} from './context';
3+
import { ModalsContextProps } from './context';
104

11-
type ModalsEvents = {
12-
openModal: (payload: ModalSettings) => string;
13-
openConfirmModal: (payload: OpenConfirmModal) => string;
14-
openContextModal: <TKey extends MantineModal>(
15-
payload: OpenContextModal<Parameters<MantineModals[TKey]>[0]['innerProps']> & {
16-
modal: TKey;
17-
} & DataAttributes
18-
) => string;
19-
closeModal: (id: string) => void;
20-
closeContextModal: <TKey extends MantineModal>(id: TKey) => void;
21-
closeAllModals: () => void;
22-
updateModal: (
23-
payload: { modalId: string } & Partial<ModalSettings> & Partial<OpenConfirmModal>
24-
) => void;
25-
updateContextModal: (payload: { modalId: string } & Partial<OpenContextModal<any>>) => void;
26-
};
5+
type ModalsEvents = Omit<ModalsContextProps, 'modals' | 'modalProps'>;
276

287
export const [useModalsEvents, createEvent] =
298
createUseExternalEvents<ModalsEvents>('mantine-modals');
@@ -40,44 +19,41 @@ export const openConfirmModal: ModalsEvents['openConfirmModal'] = (payload) => {
4019
return id;
4120
};
4221

43-
export const openContextModal: ModalsEvents['openContextModal'] = <TKey extends MantineModal>(
44-
payload: OpenContextModal<Parameters<MantineModals[TKey]>[0]['innerProps']> & {
45-
modal: TKey;
46-
} & DataAttributes
47-
) => {
22+
export const openContextModal: ModalsEvents['openContextModal'] = (payload) => {
4823
const id = payload.modalId || randomId();
4924
createEvent('openContextModal')({ ...payload, modalId: id });
5025
return id;
5126
};
5227

53-
export const closeModal = createEvent('closeModal');
28+
export const closeModal: ModalsEvents['closeModal'] = createEvent('closeModal');
5429

55-
export const closeContextModal: ModalsEvents['closeContextModal'] = <TKey extends MantineModal>(
56-
id: TKey
57-
) => createEvent('closeContextModal')(id);
30+
export const closeContextModal: ModalsEvents['closeContextModal'] = (id) =>
31+
createEvent('closeContextModal')(id);
5832

59-
export const closeAllModals = createEvent('closeAllModals');
33+
export const closeAllModals: ModalsEvents['closeAllModals'] = createEvent('closeAllModals');
6034

61-
export const updateModal = (payload: { modalId: string } & Partial<ModalSettings>) =>
35+
export const updateModal: ModalsEvents['updateModal'] = (payload) =>
6236
createEvent('updateModal')(payload);
6337

64-
export const updateContextModal = (payload: { modalId: string } & Partial<OpenContextModal<any>>) =>
38+
export const updateContextModal: ModalsEvents['updateContextModal'] = (payload) =>
6539
createEvent('updateContextModal')(payload);
6640

6741
export const modals: {
6842
open: ModalsEvents['openModal'];
69-
close: ModalsEvents['closeModal'];
70-
closeAll: ModalsEvents['closeAllModals'];
7143
openConfirmModal: ModalsEvents['openConfirmModal'];
7244
openContextModal: ModalsEvents['openContextModal'];
45+
close: ModalsEvents['closeModal'];
46+
closeContext: ModalsEvents['closeContextModal'];
47+
closeAll: ModalsEvents['closeAllModals'];
7348
updateModal: ModalsEvents['updateModal'];
7449
updateContextModal: ModalsEvents['updateContextModal'];
7550
} = {
7651
open: openModal,
77-
close: closeModal,
78-
closeAll: closeAllModals,
7952
openConfirmModal,
8053
openContextModal,
54+
close: closeModal,
55+
closeContext: closeContextModal,
56+
closeAll: closeAllModals,
8157
updateModal,
8258
updateContextModal,
8359
};

packages/@mantine/modals/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { useModals } from './use-modals/use-modals.js';
33
export {
44
openModal,
55
closeModal,
6+
closeContextModal,
67
closeAllModals,
78
openConfirmModal,
89
openContextModal,

0 commit comments

Comments
 (0)