@saas-ui/modals 0.1.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.
@@ -0,0 +1,6 @@
1
+ @saas-ui/modals:build: cache hit, replaying output f5b977178f13472a
2
+ @saas-ui/modals:build: Build "@saas-ui/modals" to dist:
3
+ @saas-ui/modals:build:  1777 B: index.js.gz
4
+ @saas-ui/modals:build:  1596 B: index.js.br
5
+ @saas-ui/modals:build:  1760 B: index.esm.js.gz
6
+ @saas-ui/modals:build:  1587 B: index.esm.js.br
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @saas-ui/modals
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial release of modals manager
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @saas-ui/modal
2
+
3
+ A modal manager for Chakra UI
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ $ yarn add @saas-ui/modals
9
+
10
+ #or
11
+
12
+ $ npm i @saas-ui/modals --save
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Add the ModalProvider to your app.
18
+
19
+ ```ts
20
+ import { ModalsProvider } from '@saas-ui/modals'
21
+
22
+ export default App() {
23
+ return (
24
+ <ModalsProvider>
25
+ {children}
26
+ </ModalsProvider>
27
+ )
28
+ }
29
+ ```
30
+
31
+ ### Open a modal
32
+
33
+ ```ts
34
+ import { useModals } from '@saas-ui/modals'
35
+
36
+ function MyComponent() {
37
+ const modals = useModals()
38
+
39
+ modals.open({
40
+ title: 'My modal',
41
+ body: <>My modal body</>,
42
+ })
43
+ }
44
+ ```
45
+
46
+ ### Open a drawer
47
+
48
+ ```ts
49
+ import { useModals } from '@saas-ui/modals'
50
+
51
+ function MyComponent() {
52
+ const modals = useModals()
53
+
54
+ modals.drawer({
55
+ title: 'My drawer',
56
+ body: <>My drawer body</>,
57
+ })
58
+ }
59
+ ```
60
+
61
+ ### Open a confirm dialog
62
+
63
+ ```ts
64
+ import { useModals } from '@saas-ui/modals'
65
+
66
+ function MyComponent() {
67
+ const modals = useModals()
68
+
69
+ modals.confirm({
70
+ title: 'Delete user',
71
+ body: 'Are you sure you want to delete this user?'
72
+ onConfirm: () => //delete user
73
+ })
74
+ }
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT - Appulse Software
@@ -0,0 +1,54 @@
1
+ import * as React from 'react';
2
+ import { AlertDialogProps } from '@chakra-ui/react';
3
+ import { ButtonGroupProps, ButtonProps } from '@saas-ui/button';
4
+ export interface ConfirmDialogProps extends Omit<AlertDialogProps, 'leastDestructiveRef'> {
5
+ /**
6
+ * The dialog title
7
+ */
8
+ title?: React.ReactNode;
9
+ /**
10
+ * The cancel button label
11
+ */
12
+ cancelLabel?: React.ReactNode;
13
+ /**
14
+ * The confirm button label
15
+ */
16
+ confirmLabel?: React.ReactNode;
17
+ /**
18
+ * The cancel button props
19
+ */
20
+ cancelProps?: ButtonProps;
21
+ /**
22
+ * The confirm button props
23
+ */
24
+ confirmProps?: ButtonProps;
25
+ /**
26
+ * The button group props
27
+ */
28
+ buttonGroupProps?: ButtonGroupProps;
29
+ /**
30
+ * Close the dialog on cancel
31
+ * @default true
32
+ */
33
+ closeOnCancel?: boolean;
34
+ /**
35
+ * Close the dialog on confirm
36
+ * @default true
37
+ */
38
+ closeOnConfirm?: boolean;
39
+ /**
40
+ * Defines which button gets initial focus
41
+ * https://www.w3.org/TR/wai-aria-practices/#alertdialog
42
+ */
43
+ leastDestructiveFocus?: 'cancel' | 'confirm';
44
+ /**
45
+ * Function that's called when cancel is clicked
46
+ */
47
+ onCancel?: () => void;
48
+ /**
49
+ * Function that's called when confirm is clicked
50
+ */
51
+ onConfirm?: () => void;
52
+ }
53
+ export declare const ConfirmDialog: React.FC<ConfirmDialogProps>;
54
+ //# sourceMappingURL=dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../src/dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAOL,gBAAgB,EACjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACZ,MAAM,iBAAiB,CAAA;AAExB,MAAM,WAAW,kBACf,SAAQ,IAAI,CAAC,gBAAgB,EAAE,qBAAqB,CAAC;IACrD;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;OAEG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;OAEG;IACH,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC5C;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAmEtD,CAAA"}
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+ import { DrawerProps as ChakraDrawerProps } from '@chakra-ui/react';
3
+ export interface BaseDrawerProps extends Omit<ChakraDrawerProps, 'children'> {
4
+ /**
5
+ * The drawer title
6
+ */
7
+ title: React.ReactNode;
8
+ /**
9
+ * Hide the close button
10
+ */
11
+ hideCloseButton?: boolean;
12
+ /**
13
+ * Hide the overflow
14
+ */
15
+ hideOverlay?: boolean;
16
+ children?: React.ReactNode;
17
+ }
18
+ export declare const BaseDrawer: React.FC<BaseDrawerProps>;
19
+ export interface DrawerProps extends BaseDrawerProps {
20
+ /**
21
+ * Drawer footer content, wrapped with `DrawerFooter`
22
+ */
23
+ footer?: React.ReactNode;
24
+ }
25
+ export declare const Drawer: React.FC<DrawerProps>;
26
+ //# sourceMappingURL=drawer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../src/drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAQL,WAAW,IAAI,iBAAiB,EACjC,MAAM,kBAAkB,CAAA;AAEzB,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC;IAC1E;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAoBhD,CAAA;AAED,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD;;OAEG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAED,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CASxC,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './dialog';
2
+ export * from './drawer';
3
+ export * from './modal';
4
+ export * from './provider';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ import*as e from"react";import{AlertDialog as n,AlertDialogOverlay as r,AlertDialogContent as o,AlertDialogHeader as l,AlertDialogBody as t,AlertDialogFooter as i,Drawer as u,DrawerOverlay as c,DrawerContent as s,DrawerHeader as a,DrawerCloseButton as f,DrawerBody as p,DrawerFooter as d,Modal as v,ModalOverlay as m,ModalContent as C,ModalHeader as O,ModalCloseButton as y,ModalBody as P,ModalFooter as b}from"@chakra-ui/react";import{ButtonGroup as w,Button as j}from"@saas-ui/button";function k(){return k=Object.assign||function(e){for(var n=1;n<arguments.length;n++){var r=arguments[n];for(var o in r)Object.prototype.hasOwnProperty.call(r,o)&&(e[o]=r[o])}return e},k.apply(this,arguments)}function D(e,n){if(null==e)return{};var r,o,l={},t=Object.keys(e);for(o=0;o<t.length;o++)n.indexOf(r=t[o])>=0||(l[r]=e[r]);return l}var g=["title","cancelLabel","confirmLabel","cancelProps","confirmProps","buttonGroupProps","isOpen","closeOnCancel","closeOnConfirm","leastDestructiveFocus","onClose","onCancel","onConfirm","children"],x=function(u){var c=u.title,s=u.cancelProps,a=u.confirmProps,f=u.buttonGroupProps,p=u.isOpen,d=u.closeOnCancel,v=void 0===d||d,m=u.closeOnConfirm,C=void 0===m||m,O=u.leastDestructiveFocus,y=void 0===O?"cancel":O,P=u.onClose,b=u.onCancel,x=u.onConfirm,B=u.children,F=D(u,g),M=e.useRef(null),R=e.useRef(null);return h(n,k({isOpen:p,onClose:P},F,{leastDestructiveRef:"cancel"===y?M:R}),h(r,null,h(o,null,h(l,null,c),h(t,null,B),h(i,null,h(w,f,h(j,k({ref:M},s,{onClick:function(){null==b||b(),v&&P()}}),"Cancel"),h(j,k({ref:R},a,{onClick:function(){null==x||x(),C&&P()}}),"Confirm"))))))},B=["title","children","isOpen","onClose","hideCloseButton","hideOverlay"],F=["footer","children"],M=function(e){var n=e.title,r=e.children,o=e.isOpen,l=e.onClose,t=e.hideCloseButton,i=e.hideOverlay,p=D(e,B);return h(u,k({isOpen:o,onClose:l},p),!i&&h(c,null),h(s,null,h(a,null,n),!t&&h(f,null),r))},R=function(e){var n=e.footer,r=e.children,o=D(e,F);return h(M,o,h(p,null,r),n&&h(d,null,n))},z=["title","children","isOpen","onClose","hideCloseButton","hideOverlay"],G=["children","footer"],L=function(e){var n=e.title,r=e.children,o=e.isOpen,l=e.onClose,t=e.hideCloseButton,i=e.hideOverlay,u=D(e,z);return h(v,k({isOpen:o,onClose:l},u),!i&&h(m,null),h(C,null,h(O,null,n),!t&&h(y,null),r))},S=function(e){var n=e.children,r=e.footer,o=D(e,G);return h(L,o,h(P,null,n),r&&h(b,null,r))},A=["id","type","scope"],E=["title","children"],K=e.createContext({}),q={id:null,props:null,type:"modal"},H={alert:x,confirm:x,drawer:R,modal:S};function I(n){var r=n.children,o=n.modals,l=e.useMemo(function(){return new Set},[]),t=e.useState({modal:q}),i=t[0],u=t[1],c=e.useMemo(function(){var e=k({},H,o);return function(n){return void 0===n&&(n="modal"),e[n]||e.modal}},[o]),s=function(e,n){if(!n)return u({modal:e});u(function(r){var o;return k({},r,((o={})[n]=e,o))})},a=function(e){var n=e.id,r=void 0===n?l.size+1:n,o=e.type,t=void 0===o?"modal":o,i=e.scope,u=void 0===i?"modal":i,c=D(e,A),a={id:r,props:k({},c,{children:c.body||c.children}),type:t,scope:u};return l.add(a),s(a,u),r},f=function(e,n){try{var r,o=[].concat(l),t=o.filter(function(n){return n.id===e})[0];return t?Promise.resolve(null==(r=t.props)||null==r.onClose?void 0:r.onClose({force:n})).then(function(e){if(!1!==e){l.delete(t);var n=o.filter(function(e){return e.scope===t.scope});s(n[n.length-2]||{id:null,props:null,type:t.type},t.scope)}}):Promise.resolve()}catch(e){return Promise.reject(e)}},p={open:a,drawer:function(e){return a(k({},e,{type:"drawer"}))},alert:function(e){return a(k({},e,{scope:"alert",type:"alert",cancelProps:{display:"none"},confirmProps:{label:"OK"},leastDestructiveFocus:"confirm"}))},confirm:function(e){return a(k({},e,{scope:"alert",type:"confirm"}))},close:f,closeAll:function(){l.forEach(function(e){var n;return null==(n=e.props)||null==n.onClose?void 0:n.onClose({force:!0})}),l.clear(),s(q)}},d=Object.entries(i).map(function(e){var n=e[0],r=e[1],o=c(r.type),t=r.props||{},i=t.title,u=t.children,s=D(t,E);return h(o,k({key:n,title:i,children:u},s,{isOpen:!(!r.id||!l.size),onClose:function(){return f(r.id)}}))});return h(K.Provider,{value:p},d,r)}var J=function(){return e.useContext(K)},N=function(){return J()};export{M as BaseDrawer,L as BaseModal,x as ConfirmDialog,R as Drawer,S as Modal,K as ModalsContext,I as ModalsProvider,N as useModals,J as useModalsContext};
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/dialog.tsx","../src/drawer.tsx","../src/modal.tsx","../src/provider.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {\n AlertDialog,\n AlertDialogBody,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogContent,\n AlertDialogOverlay,\n AlertDialogProps,\n} from '@chakra-ui/react'\n\nimport {\n ButtonGroup,\n ButtonGroupProps,\n Button,\n ButtonProps,\n} from '@saas-ui/button'\n\nexport interface ConfirmDialogProps\n extends Omit<AlertDialogProps, 'leastDestructiveRef'> {\n /**\n * The dialog title\n */\n title?: React.ReactNode\n /**\n * The cancel button label\n */\n cancelLabel?: React.ReactNode\n /**\n * The confirm button label\n */\n confirmLabel?: React.ReactNode\n /**\n * The cancel button props\n */\n cancelProps?: ButtonProps\n /**\n * The confirm button props\n */\n confirmProps?: ButtonProps\n /**\n * The button group props\n */\n buttonGroupProps?: ButtonGroupProps\n /**\n * Close the dialog on cancel\n * @default true\n */\n closeOnCancel?: boolean\n /**\n * Close the dialog on confirm\n * @default true\n */\n closeOnConfirm?: boolean\n /**\n * Defines which button gets initial focus\n * https://www.w3.org/TR/wai-aria-practices/#alertdialog\n */\n leastDestructiveFocus?: 'cancel' | 'confirm'\n /**\n * Function that's called when cancel is clicked\n */\n onCancel?: () => void\n /**\n * Function that's called when confirm is clicked\n */\n onConfirm?: () => void\n}\n\nexport const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {\n const {\n title,\n cancelLabel = 'Cancel',\n confirmLabel = 'Confirm',\n cancelProps,\n confirmProps,\n buttonGroupProps,\n isOpen,\n closeOnCancel = true,\n closeOnConfirm = true,\n leastDestructiveFocus = 'cancel',\n onClose,\n onCancel,\n onConfirm,\n children,\n ...rest\n } = props\n\n const cancelRef = React.useRef(null)\n const confirmRef = React.useRef(null)\n\n return (\n <AlertDialog\n isOpen={isOpen}\n onClose={onClose}\n {...rest}\n leastDestructiveRef={\n leastDestructiveFocus === 'cancel' ? cancelRef : confirmRef\n }\n >\n <AlertDialogOverlay>\n <AlertDialogContent>\n <AlertDialogHeader>{title}</AlertDialogHeader>\n\n <AlertDialogBody>{children}</AlertDialogBody>\n\n <AlertDialogFooter>\n <ButtonGroup {...buttonGroupProps}>\n <Button\n ref={cancelRef}\n {...cancelProps}\n onClick={() => {\n onCancel?.()\n\n closeOnCancel && onClose()\n }}\n >\n Cancel\n </Button>\n <Button\n ref={confirmRef}\n {...confirmProps}\n onClick={() => {\n onConfirm?.()\n\n closeOnConfirm && onClose()\n }}\n >\n Confirm\n </Button>\n </ButtonGroup>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialogOverlay>\n </AlertDialog>\n )\n}\n","import * as React from 'react'\n\nimport {\n Drawer as ChakraDrawer,\n DrawerOverlay,\n DrawerContent,\n DrawerHeader,\n DrawerFooter,\n DrawerBody,\n DrawerCloseButton,\n DrawerProps as ChakraDrawerProps,\n} from '@chakra-ui/react'\n\nexport interface BaseDrawerProps extends Omit<ChakraDrawerProps, 'children'> {\n /**\n * The drawer title\n */\n title: React.ReactNode\n /**\n * Hide the close button\n */\n hideCloseButton?: boolean\n /**\n * Hide the overflow\n */\n hideOverlay?: boolean\n children?: React.ReactNode\n}\n\nexport const BaseDrawer: React.FC<BaseDrawerProps> = (props) => {\n const {\n title,\n children,\n isOpen,\n onClose,\n hideCloseButton,\n hideOverlay,\n ...rest\n } = props\n return (\n <ChakraDrawer isOpen={isOpen} onClose={onClose} {...rest}>\n {!hideOverlay && <DrawerOverlay />}\n <DrawerContent>\n <DrawerHeader>{title}</DrawerHeader>\n {!hideCloseButton && <DrawerCloseButton />}\n {children}\n </DrawerContent>\n </ChakraDrawer>\n )\n}\n\nexport interface DrawerProps extends BaseDrawerProps {\n /**\n * Drawer footer content, wrapped with `DrawerFooter`\n */\n footer?: React.ReactNode\n}\n\nexport const Drawer: React.FC<DrawerProps> = (props) => {\n const { footer, children, ...rest } = props\n return (\n <BaseDrawer {...rest}>\n <DrawerBody>{children}</DrawerBody>\n\n {footer && <DrawerFooter>{footer}</DrawerFooter>}\n </BaseDrawer>\n )\n}\n","import * as React from 'react'\n\nimport {\n Modal as ChakraModal,\n ModalOverlay,\n ModalContent,\n ModalHeader,\n ModalFooter,\n ModalBody,\n ModalCloseButton,\n ModalProps as ChakraModalProps,\n} from '@chakra-ui/react'\n\nexport interface BaseModalProps extends ChakraModalProps {\n /**\n * The modal title\n */\n title: React.ReactNode\n /**\n * Hide the close button\n */\n hideCloseButton?: boolean\n /**\n * Hide the overlay\n */\n hideOverlay?: boolean\n}\n\nexport const BaseModal: React.FC<BaseModalProps> = (props) => {\n const {\n title,\n children,\n isOpen,\n onClose,\n hideCloseButton,\n hideOverlay,\n ...rest\n } = props\n return (\n <ChakraModal isOpen={isOpen} onClose={onClose} {...rest}>\n {!hideOverlay && <ModalOverlay />}\n <ModalContent>\n <ModalHeader>{title}</ModalHeader>\n {!hideCloseButton && <ModalCloseButton />}\n {children}\n </ModalContent>\n </ChakraModal>\n )\n}\n\nexport interface ModalProps extends BaseModalProps {\n /**\n * The modal footer, wrapped with `ModalFooter`\n */\n footer?: React.ReactNode\n}\n\nexport const Modal: React.FC<ModalProps> = (props) => {\n const { children, footer, ...rest } = props\n return (\n <BaseModal {...rest}>\n <ModalBody>{children}</ModalBody>\n\n {footer && <ModalFooter>{footer}</ModalFooter>}\n </BaseModal>\n )\n}\n","import * as React from 'react'\n\nimport { Modal, ModalProps } from './modal'\nimport { Drawer, DrawerProps } from './drawer'\nimport { ConfirmDialog, ConfirmDialogProps } from './dialog'\n\nexport interface ModalsContextValue {\n open?: (options: OpenOptions) => ModalId\n drawer?: (options: ModalOptions) => ModalId\n alert?: (options: ModalOptions) => ModalId\n confirm?: (options: ModalOptions) => ModalId\n close?: (id: ModalId) => void\n closeAll?: () => void\n}\n\nexport const ModalsContext = React.createContext<ModalsContextValue>({})\n\ninterface ModalsProviderProps {\n children: React.ReactNode\n modals?: Record<string, React.FC<any>>\n}\n\nexport type ModalId = string | number\n\ninterface ModalOptions\n extends Omit<\n (ModalProps & DrawerProps & ConfirmDialogProps) & {\n body?: React.ReactNode\n },\n 'onClose' | 'isOpen' | 'children'\n > {\n onClose?: (args: { force?: boolean }) => Promise<boolean | undefined> | void\n children?: React.ReactNode\n}\n\nexport interface OpenOptions extends ModalOptions {\n type?: ModalTypes | string\n scope?: ModalScopes\n}\n\nexport type ModalScopes = 'modal' | 'alert'\n\nexport type ModalTypes = 'modal' | 'drawer' | 'alert' | 'confirm'\n\nexport interface ModalConfig {\n /**\n * The modal id, autogenerated when not set.\n * Can be used to close modals.\n */\n id?: ModalId | null\n /**\n * The modal props\n */\n props?: ModalOptions | null\n /**\n * The modal scope\n * Modals can only have one level per scope.\n * The default scopes are 'modal' and 'alert', alerts can be openend above modals.\n */\n scope?: ModalScopes | string\n /**\n * The modal type to open.\n * Build in types are 'modal', 'drawer', 'alert', 'confirm'\n *\n * Custom types can be configured using the `modals` prop of `ModalProvider`\n */\n type?: ModalTypes | string\n}\n\nconst initialModalState: ModalConfig = {\n id: null,\n props: null,\n type: 'modal',\n}\n\nconst defaultModals = {\n alert: ConfirmDialog,\n confirm: ConfirmDialog,\n drawer: Drawer,\n modal: Modal,\n}\n\nexport function ModalsProvider({ children, modals }: ModalsProviderProps) {\n // Note that updating the Set doesn't trigger a re-render,\n // use in conjuction with setActiveModals\n const _instances = React.useMemo(() => new Set<ModalConfig>(), [])\n\n const [activeModals, setActiveModals] = React.useState<\n Record<string, ModalConfig>\n >({\n modal: initialModalState,\n })\n\n const getModalComponent = React.useMemo(() => {\n const _modals = {\n ...defaultModals,\n ...modals,\n }\n\n return (type = 'modal') => {\n const component = _modals[type] || _modals.modal\n\n return component\n }\n }, [modals])\n\n const setActiveModal = (modal: ModalConfig, scope?: string) => {\n if (!scope) {\n return setActiveModals({\n modal,\n })\n }\n setActiveModals((prevState) => ({\n ...prevState,\n [scope]: modal,\n }))\n }\n\n const open = (options: OpenOptions): ModalId => {\n const {\n id = _instances.size + 1,\n type = 'modal',\n scope = 'modal',\n ...props\n } = options\n\n const modal: ModalConfig = {\n id,\n props: {\n ...props,\n children: props.body || props.children,\n },\n type,\n scope,\n }\n\n _instances.add(modal)\n setActiveModal(modal, scope)\n\n return id\n }\n\n const drawer = (options: ModalOptions): ModalId => {\n return open({\n ...options,\n type: 'drawer',\n })\n }\n\n const alert = (options: ModalOptions): ModalId => {\n return open({\n ...options,\n scope: 'alert',\n type: 'alert',\n cancelProps: {\n display: 'none',\n },\n confirmProps: {\n label: 'OK',\n },\n leastDestructiveFocus: 'confirm',\n })\n }\n\n const confirm = (options: ModalOptions): ModalId => {\n return open({\n ...options,\n scope: 'alert',\n type: 'confirm',\n })\n }\n\n const close = async (id?: ModalId | null, force?: boolean) => {\n const modals = [..._instances]\n const modal = modals.filter((modal) => modal.id === id)[0]\n\n if (!modal) {\n return\n }\n\n const shouldClose = await modal.props?.onClose?.({ force })\n if (shouldClose === false) {\n return\n }\n\n _instances.delete(modal)\n\n const scoped = modals.filter(({ scope }) => scope === modal.scope)\n\n setActiveModal(\n scoped[scoped.length - 2] || {\n id: null,\n props: null,\n type: modal.type, // Keep type same as last modal type to make sure the animation isn't interrupted\n },\n modal.scope\n )\n }\n\n const closeAll = () => {\n _instances.forEach((modal) => modal.props?.onClose?.({ force: true }))\n _instances.clear()\n\n setActiveModal(initialModalState)\n }\n\n const context = {\n open,\n drawer,\n alert,\n confirm,\n close,\n closeAll,\n }\n\n const content = Object.entries(activeModals).map(([scope, config]) => {\n const Component = getModalComponent(config.type)\n\n const { title, children, ...props } = config.props || {}\n\n return (\n <Component\n key={scope}\n title={title}\n children={children}\n {...props}\n isOpen={!!(config.id && _instances.size)}\n onClose={() => close(config.id)}\n />\n )\n })\n\n return (\n <ModalsContext.Provider value={context}>\n {content}\n {children}\n </ModalsContext.Provider>\n )\n}\n\nexport const useModalsContext = (): ModalsContextValue =>\n React.useContext(ModalsContext)\n\nexport const useModals = () => {\n return useModalsContext()\n}\n"],"names":["ConfirmDialog","props","title","cancelProps","confirmProps","buttonGroupProps","isOpen","closeOnCancel","closeOnConfirm","leastDestructiveFocus","onClose","onCancel","onConfirm","children","rest","cancelRef","React","useRef","confirmRef","AlertDialog","leastDestructiveRef","h","AlertDialogOverlay","AlertDialogContent","AlertDialogHeader","AlertDialogBody","AlertDialogFooter","ButtonGroup","Button","ref","onClick","BaseDrawer","hideCloseButton","hideOverlay","ChakraDrawer","DrawerOverlay","DrawerContent","DrawerHeader","DrawerCloseButton","Drawer","footer","DrawerBody","DrawerFooter","BaseModal","ChakraModal","ModalOverlay","ModalContent","ModalHeader","ModalCloseButton","Modal","ModalBody","ModalFooter","ModalsContext","createContext","initialModalState","id","type","defaultModals","alert","confirm","drawer","modal","ModalsProvider","modals","_instances","useMemo","Set","useState","activeModals","setActiveModals","getModalComponent","_modals","setActiveModal","scope","prevState","open","options","size","body","add","close","force","filter","_modal$props","shouldClose","scoped","length","context","display","label","closeAll","forEach","_modal$props2","clear","content","Object","entries","map","config","Component","key","Provider","value","useModalsContext","useContext","useModals"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var e=require("react"),n=require("@chakra-ui/react"),r=require("@saas-ui/button");function o(e){if(e&&e.__esModule)return e;var n=Object.create(null);return e&&Object.keys(e).forEach(function(r){if("default"!==r){var o=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(n,r,o.get?o:{enumerable:!0,get:function(){return e[r]}})}}),n.default=e,n}var t=/*#__PURE__*/o(e);function l(){return l=Object.assign||function(e){for(var n=1;n<arguments.length;n++){var r=arguments[n];for(var o in r)Object.prototype.hasOwnProperty.call(r,o)&&(e[o]=r[o])}return e},l.apply(this,arguments)}function i(e,n){if(null==e)return{};var r,o,t={},l=Object.keys(e);for(o=0;o<l.length;o++)n.indexOf(r=l[o])>=0||(t[r]=e[r]);return t}var u=["title","cancelLabel","confirmLabel","cancelProps","confirmProps","buttonGroupProps","isOpen","closeOnCancel","closeOnConfirm","leastDestructiveFocus","onClose","onCancel","onConfirm","children"],a=function(e){var o=e.title,a=e.cancelProps,c=e.confirmProps,s=e.buttonGroupProps,d=e.isOpen,f=e.closeOnCancel,p=void 0===f||f,v=e.closeOnConfirm,C=void 0===v||v,O=e.leastDestructiveFocus,y=void 0===O?"cancel":O,m=e.onClose,D=e.onCancel,b=e.onConfirm,M=e.children,P=i(e,u),w=t.useRef(null),g=t.useRef(null);return h(n.AlertDialog,l({isOpen:d,onClose:m},P,{leastDestructiveRef:"cancel"===y?w:g}),h(n.AlertDialogOverlay,null,h(n.AlertDialogContent,null,h(n.AlertDialogHeader,null,o),h(n.AlertDialogBody,null,M),h(n.AlertDialogFooter,null,h(r.ButtonGroup,s,h(r.Button,l({ref:w},a,{onClick:function(){null==D||D(),p&&m()}}),"Cancel"),h(r.Button,l({ref:g},c,{onClick:function(){null==b||b(),C&&m()}}),"Confirm"))))))},c=["title","children","isOpen","onClose","hideCloseButton","hideOverlay"],s=["footer","children"],d=function(e){var r=e.title,o=e.children,t=e.isOpen,u=e.onClose,a=e.hideCloseButton,s=e.hideOverlay,d=i(e,c);return h(n.Drawer,l({isOpen:t,onClose:u},d),!s&&h(n.DrawerOverlay,null),h(n.DrawerContent,null,h(n.DrawerHeader,null,r),!a&&h(n.DrawerCloseButton,null),o))},f=function(e){var r=e.footer,o=e.children,t=i(e,s);return h(d,t,h(n.DrawerBody,null,o),r&&h(n.DrawerFooter,null,r))},p=["title","children","isOpen","onClose","hideCloseButton","hideOverlay"],v=["children","footer"],C=function(e){var r=e.title,o=e.children,t=e.isOpen,u=e.onClose,a=e.hideCloseButton,c=e.hideOverlay,s=i(e,p);return h(n.Modal,l({isOpen:t,onClose:u},s),!c&&h(n.ModalOverlay,null),h(n.ModalContent,null,h(n.ModalHeader,null,r),!a&&h(n.ModalCloseButton,null),o))},O=function(e){var r=e.children,o=e.footer,t=i(e,v);return h(C,t,h(n.ModalBody,null,r),o&&h(n.ModalFooter,null,o))},y=["id","type","scope"],m=["title","children"],D=t.createContext({}),b={id:null,props:null,type:"modal"},M={alert:a,confirm:a,drawer:f,modal:O},P=function(){return t.useContext(D)};exports.BaseDrawer=d,exports.BaseModal=C,exports.ConfirmDialog=a,exports.Drawer=f,exports.Modal=O,exports.ModalsContext=D,exports.ModalsProvider=function(e){var n=e.children,r=e.modals,o=t.useMemo(function(){return new Set},[]),u=t.useState({modal:b}),a=u[0],c=u[1],s=t.useMemo(function(){var e=l({},M,r);return function(n){return void 0===n&&(n="modal"),e[n]||e.modal}},[r]),d=function(e,n){if(!n)return c({modal:e});c(function(r){var o;return l({},r,((o={})[n]=e,o))})},f=function(e){var n=e.id,r=void 0===n?o.size+1:n,t=e.type,u=void 0===t?"modal":t,a=e.scope,c=void 0===a?"modal":a,s=i(e,y),f={id:r,props:l({},s,{children:s.body||s.children}),type:u,scope:c};return o.add(f),d(f,c),r},p=function(e,n){try{var r,t=[].concat(o),l=t.filter(function(n){return n.id===e})[0];return l?Promise.resolve(null==(r=l.props)||null==r.onClose?void 0:r.onClose({force:n})).then(function(e){if(!1!==e){o.delete(l);var n=t.filter(function(e){return e.scope===l.scope});d(n[n.length-2]||{id:null,props:null,type:l.type},l.scope)}}):Promise.resolve()}catch(e){return Promise.reject(e)}},v={open:f,drawer:function(e){return f(l({},e,{type:"drawer"}))},alert:function(e){return f(l({},e,{scope:"alert",type:"alert",cancelProps:{display:"none"},confirmProps:{label:"OK"},leastDestructiveFocus:"confirm"}))},confirm:function(e){return f(l({},e,{scope:"alert",type:"confirm"}))},close:p,closeAll:function(){o.forEach(function(e){var n;return null==(n=e.props)||null==n.onClose?void 0:n.onClose({force:!0})}),o.clear(),d(b)}},C=Object.entries(a).map(function(e){var n=e[0],r=e[1],t=s(r.type),u=r.props||{},a=u.title,c=u.children,d=i(u,m);return h(t,l({key:n,title:a,children:c},d,{isOpen:!(!r.id||!o.size),onClose:function(){return p(r.id)}}))});return h(D.Provider,{value:v},C,n)},exports.useModals=function(){return P()},exports.useModalsContext=P;
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/dialog.tsx","../src/drawer.tsx","../src/modal.tsx","../src/provider.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {\n AlertDialog,\n AlertDialogBody,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogContent,\n AlertDialogOverlay,\n AlertDialogProps,\n} from '@chakra-ui/react'\n\nimport {\n ButtonGroup,\n ButtonGroupProps,\n Button,\n ButtonProps,\n} from '@saas-ui/button'\n\nexport interface ConfirmDialogProps\n extends Omit<AlertDialogProps, 'leastDestructiveRef'> {\n /**\n * The dialog title\n */\n title?: React.ReactNode\n /**\n * The cancel button label\n */\n cancelLabel?: React.ReactNode\n /**\n * The confirm button label\n */\n confirmLabel?: React.ReactNode\n /**\n * The cancel button props\n */\n cancelProps?: ButtonProps\n /**\n * The confirm button props\n */\n confirmProps?: ButtonProps\n /**\n * The button group props\n */\n buttonGroupProps?: ButtonGroupProps\n /**\n * Close the dialog on cancel\n * @default true\n */\n closeOnCancel?: boolean\n /**\n * Close the dialog on confirm\n * @default true\n */\n closeOnConfirm?: boolean\n /**\n * Defines which button gets initial focus\n * https://www.w3.org/TR/wai-aria-practices/#alertdialog\n */\n leastDestructiveFocus?: 'cancel' | 'confirm'\n /**\n * Function that's called when cancel is clicked\n */\n onCancel?: () => void\n /**\n * Function that's called when confirm is clicked\n */\n onConfirm?: () => void\n}\n\nexport const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {\n const {\n title,\n cancelLabel = 'Cancel',\n confirmLabel = 'Confirm',\n cancelProps,\n confirmProps,\n buttonGroupProps,\n isOpen,\n closeOnCancel = true,\n closeOnConfirm = true,\n leastDestructiveFocus = 'cancel',\n onClose,\n onCancel,\n onConfirm,\n children,\n ...rest\n } = props\n\n const cancelRef = React.useRef(null)\n const confirmRef = React.useRef(null)\n\n return (\n <AlertDialog\n isOpen={isOpen}\n onClose={onClose}\n {...rest}\n leastDestructiveRef={\n leastDestructiveFocus === 'cancel' ? cancelRef : confirmRef\n }\n >\n <AlertDialogOverlay>\n <AlertDialogContent>\n <AlertDialogHeader>{title}</AlertDialogHeader>\n\n <AlertDialogBody>{children}</AlertDialogBody>\n\n <AlertDialogFooter>\n <ButtonGroup {...buttonGroupProps}>\n <Button\n ref={cancelRef}\n {...cancelProps}\n onClick={() => {\n onCancel?.()\n\n closeOnCancel && onClose()\n }}\n >\n Cancel\n </Button>\n <Button\n ref={confirmRef}\n {...confirmProps}\n onClick={() => {\n onConfirm?.()\n\n closeOnConfirm && onClose()\n }}\n >\n Confirm\n </Button>\n </ButtonGroup>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialogOverlay>\n </AlertDialog>\n )\n}\n","import * as React from 'react'\n\nimport {\n Drawer as ChakraDrawer,\n DrawerOverlay,\n DrawerContent,\n DrawerHeader,\n DrawerFooter,\n DrawerBody,\n DrawerCloseButton,\n DrawerProps as ChakraDrawerProps,\n} from '@chakra-ui/react'\n\nexport interface BaseDrawerProps extends Omit<ChakraDrawerProps, 'children'> {\n /**\n * The drawer title\n */\n title: React.ReactNode\n /**\n * Hide the close button\n */\n hideCloseButton?: boolean\n /**\n * Hide the overflow\n */\n hideOverlay?: boolean\n children?: React.ReactNode\n}\n\nexport const BaseDrawer: React.FC<BaseDrawerProps> = (props) => {\n const {\n title,\n children,\n isOpen,\n onClose,\n hideCloseButton,\n hideOverlay,\n ...rest\n } = props\n return (\n <ChakraDrawer isOpen={isOpen} onClose={onClose} {...rest}>\n {!hideOverlay && <DrawerOverlay />}\n <DrawerContent>\n <DrawerHeader>{title}</DrawerHeader>\n {!hideCloseButton && <DrawerCloseButton />}\n {children}\n </DrawerContent>\n </ChakraDrawer>\n )\n}\n\nexport interface DrawerProps extends BaseDrawerProps {\n /**\n * Drawer footer content, wrapped with `DrawerFooter`\n */\n footer?: React.ReactNode\n}\n\nexport const Drawer: React.FC<DrawerProps> = (props) => {\n const { footer, children, ...rest } = props\n return (\n <BaseDrawer {...rest}>\n <DrawerBody>{children}</DrawerBody>\n\n {footer && <DrawerFooter>{footer}</DrawerFooter>}\n </BaseDrawer>\n )\n}\n","import * as React from 'react'\n\nimport {\n Modal as ChakraModal,\n ModalOverlay,\n ModalContent,\n ModalHeader,\n ModalFooter,\n ModalBody,\n ModalCloseButton,\n ModalProps as ChakraModalProps,\n} from '@chakra-ui/react'\n\nexport interface BaseModalProps extends ChakraModalProps {\n /**\n * The modal title\n */\n title: React.ReactNode\n /**\n * Hide the close button\n */\n hideCloseButton?: boolean\n /**\n * Hide the overlay\n */\n hideOverlay?: boolean\n}\n\nexport const BaseModal: React.FC<BaseModalProps> = (props) => {\n const {\n title,\n children,\n isOpen,\n onClose,\n hideCloseButton,\n hideOverlay,\n ...rest\n } = props\n return (\n <ChakraModal isOpen={isOpen} onClose={onClose} {...rest}>\n {!hideOverlay && <ModalOverlay />}\n <ModalContent>\n <ModalHeader>{title}</ModalHeader>\n {!hideCloseButton && <ModalCloseButton />}\n {children}\n </ModalContent>\n </ChakraModal>\n )\n}\n\nexport interface ModalProps extends BaseModalProps {\n /**\n * The modal footer, wrapped with `ModalFooter`\n */\n footer?: React.ReactNode\n}\n\nexport const Modal: React.FC<ModalProps> = (props) => {\n const { children, footer, ...rest } = props\n return (\n <BaseModal {...rest}>\n <ModalBody>{children}</ModalBody>\n\n {footer && <ModalFooter>{footer}</ModalFooter>}\n </BaseModal>\n )\n}\n","import * as React from 'react'\n\nimport { Modal, ModalProps } from './modal'\nimport { Drawer, DrawerProps } from './drawer'\nimport { ConfirmDialog, ConfirmDialogProps } from './dialog'\n\nexport interface ModalsContextValue {\n open?: (options: OpenOptions) => ModalId\n drawer?: (options: ModalOptions) => ModalId\n alert?: (options: ModalOptions) => ModalId\n confirm?: (options: ModalOptions) => ModalId\n close?: (id: ModalId) => void\n closeAll?: () => void\n}\n\nexport const ModalsContext = React.createContext<ModalsContextValue>({})\n\ninterface ModalsProviderProps {\n children: React.ReactNode\n modals?: Record<string, React.FC<any>>\n}\n\nexport type ModalId = string | number\n\ninterface ModalOptions\n extends Omit<\n (ModalProps & DrawerProps & ConfirmDialogProps) & {\n body?: React.ReactNode\n },\n 'onClose' | 'isOpen' | 'children'\n > {\n onClose?: (args: { force?: boolean }) => Promise<boolean | undefined> | void\n children?: React.ReactNode\n}\n\nexport interface OpenOptions extends ModalOptions {\n type?: ModalTypes | string\n scope?: ModalScopes\n}\n\nexport type ModalScopes = 'modal' | 'alert'\n\nexport type ModalTypes = 'modal' | 'drawer' | 'alert' | 'confirm'\n\nexport interface ModalConfig {\n /**\n * The modal id, autogenerated when not set.\n * Can be used to close modals.\n */\n id?: ModalId | null\n /**\n * The modal props\n */\n props?: ModalOptions | null\n /**\n * The modal scope\n * Modals can only have one level per scope.\n * The default scopes are 'modal' and 'alert', alerts can be openend above modals.\n */\n scope?: ModalScopes | string\n /**\n * The modal type to open.\n * Build in types are 'modal', 'drawer', 'alert', 'confirm'\n *\n * Custom types can be configured using the `modals` prop of `ModalProvider`\n */\n type?: ModalTypes | string\n}\n\nconst initialModalState: ModalConfig = {\n id: null,\n props: null,\n type: 'modal',\n}\n\nconst defaultModals = {\n alert: ConfirmDialog,\n confirm: ConfirmDialog,\n drawer: Drawer,\n modal: Modal,\n}\n\nexport function ModalsProvider({ children, modals }: ModalsProviderProps) {\n // Note that updating the Set doesn't trigger a re-render,\n // use in conjuction with setActiveModals\n const _instances = React.useMemo(() => new Set<ModalConfig>(), [])\n\n const [activeModals, setActiveModals] = React.useState<\n Record<string, ModalConfig>\n >({\n modal: initialModalState,\n })\n\n const getModalComponent = React.useMemo(() => {\n const _modals = {\n ...defaultModals,\n ...modals,\n }\n\n return (type = 'modal') => {\n const component = _modals[type] || _modals.modal\n\n return component\n }\n }, [modals])\n\n const setActiveModal = (modal: ModalConfig, scope?: string) => {\n if (!scope) {\n return setActiveModals({\n modal,\n })\n }\n setActiveModals((prevState) => ({\n ...prevState,\n [scope]: modal,\n }))\n }\n\n const open = (options: OpenOptions): ModalId => {\n const {\n id = _instances.size + 1,\n type = 'modal',\n scope = 'modal',\n ...props\n } = options\n\n const modal: ModalConfig = {\n id,\n props: {\n ...props,\n children: props.body || props.children,\n },\n type,\n scope,\n }\n\n _instances.add(modal)\n setActiveModal(modal, scope)\n\n return id\n }\n\n const drawer = (options: ModalOptions): ModalId => {\n return open({\n ...options,\n type: 'drawer',\n })\n }\n\n const alert = (options: ModalOptions): ModalId => {\n return open({\n ...options,\n scope: 'alert',\n type: 'alert',\n cancelProps: {\n display: 'none',\n },\n confirmProps: {\n label: 'OK',\n },\n leastDestructiveFocus: 'confirm',\n })\n }\n\n const confirm = (options: ModalOptions): ModalId => {\n return open({\n ...options,\n scope: 'alert',\n type: 'confirm',\n })\n }\n\n const close = async (id?: ModalId | null, force?: boolean) => {\n const modals = [..._instances]\n const modal = modals.filter((modal) => modal.id === id)[0]\n\n if (!modal) {\n return\n }\n\n const shouldClose = await modal.props?.onClose?.({ force })\n if (shouldClose === false) {\n return\n }\n\n _instances.delete(modal)\n\n const scoped = modals.filter(({ scope }) => scope === modal.scope)\n\n setActiveModal(\n scoped[scoped.length - 2] || {\n id: null,\n props: null,\n type: modal.type, // Keep type same as last modal type to make sure the animation isn't interrupted\n },\n modal.scope\n )\n }\n\n const closeAll = () => {\n _instances.forEach((modal) => modal.props?.onClose?.({ force: true }))\n _instances.clear()\n\n setActiveModal(initialModalState)\n }\n\n const context = {\n open,\n drawer,\n alert,\n confirm,\n close,\n closeAll,\n }\n\n const content = Object.entries(activeModals).map(([scope, config]) => {\n const Component = getModalComponent(config.type)\n\n const { title, children, ...props } = config.props || {}\n\n return (\n <Component\n key={scope}\n title={title}\n children={children}\n {...props}\n isOpen={!!(config.id && _instances.size)}\n onClose={() => close(config.id)}\n />\n )\n })\n\n return (\n <ModalsContext.Provider value={context}>\n {content}\n {children}\n </ModalsContext.Provider>\n )\n}\n\nexport const useModalsContext = (): ModalsContextValue =>\n React.useContext(ModalsContext)\n\nexport const useModals = () => {\n return useModalsContext()\n}\n"],"names":["ConfirmDialog","props","title","cancelProps","confirmProps","buttonGroupProps","isOpen","closeOnCancel","closeOnConfirm","leastDestructiveFocus","onClose","onCancel","onConfirm","children","rest","cancelRef","React","useRef","confirmRef","AlertDialog","leastDestructiveRef","h","AlertDialogOverlay","AlertDialogContent","AlertDialogHeader","AlertDialogBody","AlertDialogFooter","ButtonGroup","Button","ref","onClick","BaseDrawer","hideCloseButton","hideOverlay","ChakraDrawer","DrawerOverlay","DrawerContent","DrawerHeader","DrawerCloseButton","Drawer","footer","DrawerBody","DrawerFooter","BaseModal","ChakraModal","ModalOverlay","ModalContent","ModalHeader","ModalCloseButton","Modal","ModalBody","ModalFooter","ModalsContext","createContext","initialModalState","id","type","defaultModals","alert","confirm","drawer","modal","useModalsContext","useContext","modals","_instances","useMemo","Set","useState","activeModals","setActiveModals","getModalComponent","_modals","setActiveModal","scope","prevState","open","options","size","body","add","close","force","filter","_modal$props","shouldClose","scoped","length","context","display","label","closeAll","forEach","_modal$props2","clear","content","Object","entries","map","config","Component","key","Provider","value"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+ import { ModalProps as ChakraModalProps } from '@chakra-ui/react';
3
+ export interface BaseModalProps extends ChakraModalProps {
4
+ /**
5
+ * The modal title
6
+ */
7
+ title: React.ReactNode;
8
+ /**
9
+ * Hide the close button
10
+ */
11
+ hideCloseButton?: boolean;
12
+ /**
13
+ * Hide the overlay
14
+ */
15
+ hideOverlay?: boolean;
16
+ }
17
+ export declare const BaseModal: React.FC<BaseModalProps>;
18
+ export interface ModalProps extends BaseModalProps {
19
+ /**
20
+ * The modal footer, wrapped with `ModalFooter`
21
+ */
22
+ footer?: React.ReactNode;
23
+ }
24
+ export declare const Modal: React.FC<ModalProps>;
25
+ //# sourceMappingURL=modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAQL,UAAU,IAAI,gBAAgB,EAC/B,MAAM,kBAAkB,CAAA;AAEzB,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAoB9C,CAAA;AAED,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD;;OAEG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAED,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,UAAU,CAStC,CAAA"}
@@ -0,0 +1,61 @@
1
+ import * as React from 'react';
2
+ import { ModalProps } from './modal';
3
+ import { DrawerProps } from './drawer';
4
+ import { ConfirmDialogProps } from './dialog';
5
+ export interface ModalsContextValue {
6
+ open?: (options: OpenOptions) => ModalId;
7
+ drawer?: (options: ModalOptions) => ModalId;
8
+ alert?: (options: ModalOptions) => ModalId;
9
+ confirm?: (options: ModalOptions) => ModalId;
10
+ close?: (id: ModalId) => void;
11
+ closeAll?: () => void;
12
+ }
13
+ export declare const ModalsContext: React.Context<ModalsContextValue>;
14
+ interface ModalsProviderProps {
15
+ children: React.ReactNode;
16
+ modals?: Record<string, React.FC<any>>;
17
+ }
18
+ export declare type ModalId = string | number;
19
+ interface ModalOptions extends Omit<(ModalProps & DrawerProps & ConfirmDialogProps) & {
20
+ body?: React.ReactNode;
21
+ }, 'onClose' | 'isOpen' | 'children'> {
22
+ onClose?: (args: {
23
+ force?: boolean;
24
+ }) => Promise<boolean | undefined> | void;
25
+ children?: React.ReactNode;
26
+ }
27
+ export interface OpenOptions extends ModalOptions {
28
+ type?: ModalTypes | string;
29
+ scope?: ModalScopes;
30
+ }
31
+ export declare type ModalScopes = 'modal' | 'alert';
32
+ export declare type ModalTypes = 'modal' | 'drawer' | 'alert' | 'confirm';
33
+ export interface ModalConfig {
34
+ /**
35
+ * The modal id, autogenerated when not set.
36
+ * Can be used to close modals.
37
+ */
38
+ id?: ModalId | null;
39
+ /**
40
+ * The modal props
41
+ */
42
+ props?: ModalOptions | null;
43
+ /**
44
+ * The modal scope
45
+ * Modals can only have one level per scope.
46
+ * The default scopes are 'modal' and 'alert', alerts can be openend above modals.
47
+ */
48
+ scope?: ModalScopes | string;
49
+ /**
50
+ * The modal type to open.
51
+ * Build in types are 'modal', 'drawer', 'alert', 'confirm'
52
+ *
53
+ * Custom types can be configured using the `modals` prop of `ModalProvider`
54
+ */
55
+ type?: ModalTypes | string;
56
+ }
57
+ export declare function ModalsProvider({ children, modals }: ModalsProviderProps): JSX.Element;
58
+ export declare const useModalsContext: () => ModalsContextValue;
59
+ export declare const useModals: () => ModalsContextValue;
60
+ export {};
61
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAS,UAAU,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAU,WAAW,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAiB,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE5D,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAA;IACxC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAA;IAC3C,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAA;IAC1C,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAA;IAC5C,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,eAAO,MAAM,aAAa,mCAA8C,CAAA;AAExE,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;CACvC;AAED,oBAAY,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;AAErC,UAAU,YACR,SAAQ,IAAI,CACV,CAAC,UAAU,GAAG,WAAW,GAAG,kBAAkB,CAAC,GAAG;IAChD,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACvB,EACD,SAAS,GAAG,QAAQ,GAAG,UAAU,CAClC;IACD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,IAAI,CAAA;IAC5E,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAED,MAAM,WAAW,WAAY,SAAQ,YAAY;IAC/C,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;IAC1B,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAED,oBAAY,WAAW,GAAG,OAAO,GAAG,OAAO,CAAA;AAE3C,oBAAY,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAA;AAEjE,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,EAAE,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACnB;;OAEG;IACH,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAA;IAC3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CAC3B;AAeD,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,mBAAmB,eA4JvE;AAED,eAAO,MAAM,gBAAgB,QAAO,kBACH,CAAA;AAEjC,eAAO,MAAM,SAAS,0BAErB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@saas-ui/modals",
3
+ "version": "0.1.0",
4
+ "description": "A modal manager for Chakra UI",
5
+ "source": "src/index.ts",
6
+ "exports": {
7
+ ".": {
8
+ "require": "./dist/index.js",
9
+ "default": "./dist/index.modern.js"
10
+ },
11
+ "./src": {
12
+ "default": "./src/index.ts"
13
+ }
14
+ },
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.esm.js",
17
+ "types": "./dist/index.d.ts",
18
+ "scripts": {
19
+ "prebuild": "rimraf dist",
20
+ "build": "microbundle --tsconfig ./tsconfig.json -f cjs,es --compress",
21
+ "lint": "eslint src --ext .ts,.tsx,.js,.jsx --config ../../.eslintrc.js",
22
+ "lint:staged": "lint-staged --allow-empty --config ../../lint-staged.config.js",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "sideEffects": false,
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "author": "Eelco Wiersma <eelco@appulse.nl>",
30
+ "license": "MIT",
31
+ "homepage": "https://saas-ui.dev/",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/saas-js/saas-ui",
35
+ "directory": "packages/saas-ui-modals"
36
+ },
37
+ "keywords": [
38
+ "react",
39
+ "ui",
40
+ "chakra-ui",
41
+ "design-system",
42
+ "react-components",
43
+ "uikit",
44
+ "accessible",
45
+ "components",
46
+ "emotion",
47
+ "library",
48
+ "modal"
49
+ ],
50
+ "storybook": {
51
+ "title": "Saas UI",
52
+ "url": "https://storybook.saas-ui.dev"
53
+ },
54
+ "dependencies": {
55
+ "@saas-ui/button": "0.2.0"
56
+ },
57
+ "peerDependencies": {
58
+ "@chakra-ui/react": ">=1.8.0",
59
+ "react": ">=16.8.6"
60
+ }
61
+ }
package/src/dialog.tsx ADDED
@@ -0,0 +1,138 @@
1
+ import * as React from 'react'
2
+
3
+ import {
4
+ AlertDialog,
5
+ AlertDialogBody,
6
+ AlertDialogFooter,
7
+ AlertDialogHeader,
8
+ AlertDialogContent,
9
+ AlertDialogOverlay,
10
+ AlertDialogProps,
11
+ } from '@chakra-ui/react'
12
+
13
+ import {
14
+ ButtonGroup,
15
+ ButtonGroupProps,
16
+ Button,
17
+ ButtonProps,
18
+ } from '@saas-ui/button'
19
+
20
+ export interface ConfirmDialogProps
21
+ extends Omit<AlertDialogProps, 'leastDestructiveRef'> {
22
+ /**
23
+ * The dialog title
24
+ */
25
+ title?: React.ReactNode
26
+ /**
27
+ * The cancel button label
28
+ */
29
+ cancelLabel?: React.ReactNode
30
+ /**
31
+ * The confirm button label
32
+ */
33
+ confirmLabel?: React.ReactNode
34
+ /**
35
+ * The cancel button props
36
+ */
37
+ cancelProps?: ButtonProps
38
+ /**
39
+ * The confirm button props
40
+ */
41
+ confirmProps?: ButtonProps
42
+ /**
43
+ * The button group props
44
+ */
45
+ buttonGroupProps?: ButtonGroupProps
46
+ /**
47
+ * Close the dialog on cancel
48
+ * @default true
49
+ */
50
+ closeOnCancel?: boolean
51
+ /**
52
+ * Close the dialog on confirm
53
+ * @default true
54
+ */
55
+ closeOnConfirm?: boolean
56
+ /**
57
+ * Defines which button gets initial focus
58
+ * https://www.w3.org/TR/wai-aria-practices/#alertdialog
59
+ */
60
+ leastDestructiveFocus?: 'cancel' | 'confirm'
61
+ /**
62
+ * Function that's called when cancel is clicked
63
+ */
64
+ onCancel?: () => void
65
+ /**
66
+ * Function that's called when confirm is clicked
67
+ */
68
+ onConfirm?: () => void
69
+ }
70
+
71
+ export const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {
72
+ const {
73
+ title,
74
+ cancelLabel = 'Cancel',
75
+ confirmLabel = 'Confirm',
76
+ cancelProps,
77
+ confirmProps,
78
+ buttonGroupProps,
79
+ isOpen,
80
+ closeOnCancel = true,
81
+ closeOnConfirm = true,
82
+ leastDestructiveFocus = 'cancel',
83
+ onClose,
84
+ onCancel,
85
+ onConfirm,
86
+ children,
87
+ ...rest
88
+ } = props
89
+
90
+ const cancelRef = React.useRef(null)
91
+ const confirmRef = React.useRef(null)
92
+
93
+ return (
94
+ <AlertDialog
95
+ isOpen={isOpen}
96
+ onClose={onClose}
97
+ {...rest}
98
+ leastDestructiveRef={
99
+ leastDestructiveFocus === 'cancel' ? cancelRef : confirmRef
100
+ }
101
+ >
102
+ <AlertDialogOverlay>
103
+ <AlertDialogContent>
104
+ <AlertDialogHeader>{title}</AlertDialogHeader>
105
+
106
+ <AlertDialogBody>{children}</AlertDialogBody>
107
+
108
+ <AlertDialogFooter>
109
+ <ButtonGroup {...buttonGroupProps}>
110
+ <Button
111
+ ref={cancelRef}
112
+ {...cancelProps}
113
+ onClick={() => {
114
+ onCancel?.()
115
+
116
+ closeOnCancel && onClose()
117
+ }}
118
+ >
119
+ Cancel
120
+ </Button>
121
+ <Button
122
+ ref={confirmRef}
123
+ {...confirmProps}
124
+ onClick={() => {
125
+ onConfirm?.()
126
+
127
+ closeOnConfirm && onClose()
128
+ }}
129
+ >
130
+ Confirm
131
+ </Button>
132
+ </ButtonGroup>
133
+ </AlertDialogFooter>
134
+ </AlertDialogContent>
135
+ </AlertDialogOverlay>
136
+ </AlertDialog>
137
+ )
138
+ }
package/src/drawer.tsx ADDED
@@ -0,0 +1,68 @@
1
+ import * as React from 'react'
2
+
3
+ import {
4
+ Drawer as ChakraDrawer,
5
+ DrawerOverlay,
6
+ DrawerContent,
7
+ DrawerHeader,
8
+ DrawerFooter,
9
+ DrawerBody,
10
+ DrawerCloseButton,
11
+ DrawerProps as ChakraDrawerProps,
12
+ } from '@chakra-ui/react'
13
+
14
+ export interface BaseDrawerProps extends Omit<ChakraDrawerProps, 'children'> {
15
+ /**
16
+ * The drawer title
17
+ */
18
+ title: React.ReactNode
19
+ /**
20
+ * Hide the close button
21
+ */
22
+ hideCloseButton?: boolean
23
+ /**
24
+ * Hide the overflow
25
+ */
26
+ hideOverlay?: boolean
27
+ children?: React.ReactNode
28
+ }
29
+
30
+ export const BaseDrawer: React.FC<BaseDrawerProps> = (props) => {
31
+ const {
32
+ title,
33
+ children,
34
+ isOpen,
35
+ onClose,
36
+ hideCloseButton,
37
+ hideOverlay,
38
+ ...rest
39
+ } = props
40
+ return (
41
+ <ChakraDrawer isOpen={isOpen} onClose={onClose} {...rest}>
42
+ {!hideOverlay && <DrawerOverlay />}
43
+ <DrawerContent>
44
+ <DrawerHeader>{title}</DrawerHeader>
45
+ {!hideCloseButton && <DrawerCloseButton />}
46
+ {children}
47
+ </DrawerContent>
48
+ </ChakraDrawer>
49
+ )
50
+ }
51
+
52
+ export interface DrawerProps extends BaseDrawerProps {
53
+ /**
54
+ * Drawer footer content, wrapped with `DrawerFooter`
55
+ */
56
+ footer?: React.ReactNode
57
+ }
58
+
59
+ export const Drawer: React.FC<DrawerProps> = (props) => {
60
+ const { footer, children, ...rest } = props
61
+ return (
62
+ <BaseDrawer {...rest}>
63
+ <DrawerBody>{children}</DrawerBody>
64
+
65
+ {footer && <DrawerFooter>{footer}</DrawerFooter>}
66
+ </BaseDrawer>
67
+ )
68
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './dialog'
2
+ export * from './drawer'
3
+ export * from './modal'
4
+ export * from './provider'
package/src/modal.tsx ADDED
@@ -0,0 +1,67 @@
1
+ import * as React from 'react'
2
+
3
+ import {
4
+ Modal as ChakraModal,
5
+ ModalOverlay,
6
+ ModalContent,
7
+ ModalHeader,
8
+ ModalFooter,
9
+ ModalBody,
10
+ ModalCloseButton,
11
+ ModalProps as ChakraModalProps,
12
+ } from '@chakra-ui/react'
13
+
14
+ export interface BaseModalProps extends ChakraModalProps {
15
+ /**
16
+ * The modal title
17
+ */
18
+ title: React.ReactNode
19
+ /**
20
+ * Hide the close button
21
+ */
22
+ hideCloseButton?: boolean
23
+ /**
24
+ * Hide the overlay
25
+ */
26
+ hideOverlay?: boolean
27
+ }
28
+
29
+ export const BaseModal: React.FC<BaseModalProps> = (props) => {
30
+ const {
31
+ title,
32
+ children,
33
+ isOpen,
34
+ onClose,
35
+ hideCloseButton,
36
+ hideOverlay,
37
+ ...rest
38
+ } = props
39
+ return (
40
+ <ChakraModal isOpen={isOpen} onClose={onClose} {...rest}>
41
+ {!hideOverlay && <ModalOverlay />}
42
+ <ModalContent>
43
+ <ModalHeader>{title}</ModalHeader>
44
+ {!hideCloseButton && <ModalCloseButton />}
45
+ {children}
46
+ </ModalContent>
47
+ </ChakraModal>
48
+ )
49
+ }
50
+
51
+ export interface ModalProps extends BaseModalProps {
52
+ /**
53
+ * The modal footer, wrapped with `ModalFooter`
54
+ */
55
+ footer?: React.ReactNode
56
+ }
57
+
58
+ export const Modal: React.FC<ModalProps> = (props) => {
59
+ const { children, footer, ...rest } = props
60
+ return (
61
+ <BaseModal {...rest}>
62
+ <ModalBody>{children}</ModalBody>
63
+
64
+ {footer && <ModalFooter>{footer}</ModalFooter>}
65
+ </BaseModal>
66
+ )
67
+ }
@@ -0,0 +1,246 @@
1
+ import * as React from 'react'
2
+
3
+ import { Modal, ModalProps } from './modal'
4
+ import { Drawer, DrawerProps } from './drawer'
5
+ import { ConfirmDialog, ConfirmDialogProps } from './dialog'
6
+
7
+ export interface ModalsContextValue {
8
+ open?: (options: OpenOptions) => ModalId
9
+ drawer?: (options: ModalOptions) => ModalId
10
+ alert?: (options: ModalOptions) => ModalId
11
+ confirm?: (options: ModalOptions) => ModalId
12
+ close?: (id: ModalId) => void
13
+ closeAll?: () => void
14
+ }
15
+
16
+ export const ModalsContext = React.createContext<ModalsContextValue>({})
17
+
18
+ interface ModalsProviderProps {
19
+ children: React.ReactNode
20
+ modals?: Record<string, React.FC<any>>
21
+ }
22
+
23
+ export type ModalId = string | number
24
+
25
+ interface ModalOptions
26
+ extends Omit<
27
+ (ModalProps & DrawerProps & ConfirmDialogProps) & {
28
+ body?: React.ReactNode
29
+ },
30
+ 'onClose' | 'isOpen' | 'children'
31
+ > {
32
+ onClose?: (args: { force?: boolean }) => Promise<boolean | undefined> | void
33
+ children?: React.ReactNode
34
+ }
35
+
36
+ export interface OpenOptions extends ModalOptions {
37
+ type?: ModalTypes | string
38
+ scope?: ModalScopes
39
+ }
40
+
41
+ export type ModalScopes = 'modal' | 'alert'
42
+
43
+ export type ModalTypes = 'modal' | 'drawer' | 'alert' | 'confirm'
44
+
45
+ export interface ModalConfig {
46
+ /**
47
+ * The modal id, autogenerated when not set.
48
+ * Can be used to close modals.
49
+ */
50
+ id?: ModalId | null
51
+ /**
52
+ * The modal props
53
+ */
54
+ props?: ModalOptions | null
55
+ /**
56
+ * The modal scope
57
+ * Modals can only have one level per scope.
58
+ * The default scopes are 'modal' and 'alert', alerts can be openend above modals.
59
+ */
60
+ scope?: ModalScopes | string
61
+ /**
62
+ * The modal type to open.
63
+ * Build in types are 'modal', 'drawer', 'alert', 'confirm'
64
+ *
65
+ * Custom types can be configured using the `modals` prop of `ModalProvider`
66
+ */
67
+ type?: ModalTypes | string
68
+ }
69
+
70
+ const initialModalState: ModalConfig = {
71
+ id: null,
72
+ props: null,
73
+ type: 'modal',
74
+ }
75
+
76
+ const defaultModals = {
77
+ alert: ConfirmDialog,
78
+ confirm: ConfirmDialog,
79
+ drawer: Drawer,
80
+ modal: Modal,
81
+ }
82
+
83
+ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
84
+ // Note that updating the Set doesn't trigger a re-render,
85
+ // use in conjuction with setActiveModals
86
+ const _instances = React.useMemo(() => new Set<ModalConfig>(), [])
87
+
88
+ const [activeModals, setActiveModals] = React.useState<
89
+ Record<string, ModalConfig>
90
+ >({
91
+ modal: initialModalState,
92
+ })
93
+
94
+ const getModalComponent = React.useMemo(() => {
95
+ const _modals = {
96
+ ...defaultModals,
97
+ ...modals,
98
+ }
99
+
100
+ return (type = 'modal') => {
101
+ const component = _modals[type] || _modals.modal
102
+
103
+ return component
104
+ }
105
+ }, [modals])
106
+
107
+ const setActiveModal = (modal: ModalConfig, scope?: string) => {
108
+ if (!scope) {
109
+ return setActiveModals({
110
+ modal,
111
+ })
112
+ }
113
+ setActiveModals((prevState) => ({
114
+ ...prevState,
115
+ [scope]: modal,
116
+ }))
117
+ }
118
+
119
+ const open = (options: OpenOptions): ModalId => {
120
+ const {
121
+ id = _instances.size + 1,
122
+ type = 'modal',
123
+ scope = 'modal',
124
+ ...props
125
+ } = options
126
+
127
+ const modal: ModalConfig = {
128
+ id,
129
+ props: {
130
+ ...props,
131
+ children: props.body || props.children,
132
+ },
133
+ type,
134
+ scope,
135
+ }
136
+
137
+ _instances.add(modal)
138
+ setActiveModal(modal, scope)
139
+
140
+ return id
141
+ }
142
+
143
+ const drawer = (options: ModalOptions): ModalId => {
144
+ return open({
145
+ ...options,
146
+ type: 'drawer',
147
+ })
148
+ }
149
+
150
+ const alert = (options: ModalOptions): ModalId => {
151
+ return open({
152
+ ...options,
153
+ scope: 'alert',
154
+ type: 'alert',
155
+ cancelProps: {
156
+ display: 'none',
157
+ },
158
+ confirmProps: {
159
+ label: 'OK',
160
+ },
161
+ leastDestructiveFocus: 'confirm',
162
+ })
163
+ }
164
+
165
+ const confirm = (options: ModalOptions): ModalId => {
166
+ return open({
167
+ ...options,
168
+ scope: 'alert',
169
+ type: 'confirm',
170
+ })
171
+ }
172
+
173
+ const close = async (id?: ModalId | null, force?: boolean) => {
174
+ const modals = [..._instances]
175
+ const modal = modals.filter((modal) => modal.id === id)[0]
176
+
177
+ if (!modal) {
178
+ return
179
+ }
180
+
181
+ const shouldClose = await modal.props?.onClose?.({ force })
182
+ if (shouldClose === false) {
183
+ return
184
+ }
185
+
186
+ _instances.delete(modal)
187
+
188
+ const scoped = modals.filter(({ scope }) => scope === modal.scope)
189
+
190
+ setActiveModal(
191
+ scoped[scoped.length - 2] || {
192
+ id: null,
193
+ props: null,
194
+ type: modal.type, // Keep type same as last modal type to make sure the animation isn't interrupted
195
+ },
196
+ modal.scope
197
+ )
198
+ }
199
+
200
+ const closeAll = () => {
201
+ _instances.forEach((modal) => modal.props?.onClose?.({ force: true }))
202
+ _instances.clear()
203
+
204
+ setActiveModal(initialModalState)
205
+ }
206
+
207
+ const context = {
208
+ open,
209
+ drawer,
210
+ alert,
211
+ confirm,
212
+ close,
213
+ closeAll,
214
+ }
215
+
216
+ const content = Object.entries(activeModals).map(([scope, config]) => {
217
+ const Component = getModalComponent(config.type)
218
+
219
+ const { title, children, ...props } = config.props || {}
220
+
221
+ return (
222
+ <Component
223
+ key={scope}
224
+ title={title}
225
+ children={children}
226
+ {...props}
227
+ isOpen={!!(config.id && _instances.size)}
228
+ onClose={() => close(config.id)}
229
+ />
230
+ )
231
+ })
232
+
233
+ return (
234
+ <ModalsContext.Provider value={context}>
235
+ {content}
236
+ {children}
237
+ </ModalsContext.Provider>
238
+ )
239
+ }
240
+
241
+ export const useModalsContext = (): ModalsContextValue =>
242
+ React.useContext(ModalsContext)
243
+
244
+ export const useModals = () => {
245
+ return useModalsContext()
246
+ }
@@ -0,0 +1,158 @@
1
+ import * as React from 'react'
2
+ import { Stack, Container } from '@chakra-ui/react'
3
+ import { ModalsProvider, useModals } from '../src/provider'
4
+
5
+ import { Button } from '@saas-ui/button'
6
+
7
+ const CustomModal: React.FC<{ title: string }> = ({ title, children }) => (
8
+ <div>
9
+ {title} - {children}
10
+ </div>
11
+ )
12
+
13
+ const modals = {
14
+ custom: CustomModal,
15
+ }
16
+
17
+ export default {
18
+ title: 'Components/Overlay/Modals',
19
+ decorators: [
20
+ (Story: any) => (
21
+ <Container mt="40px">
22
+ <ModalsProvider modals={modals}>
23
+ <Story />
24
+ </ModalsProvider>
25
+ </Container>
26
+ ),
27
+ ],
28
+ }
29
+
30
+ export const basic = () => {
31
+ const modals = useModals()
32
+
33
+ return (
34
+ <Stack>
35
+ <Button
36
+ onClick={() => {
37
+ const id = modals.open({
38
+ title: 'My Modal',
39
+ body: <>My modal</>,
40
+ footer: <Button onClick={() => modals.close(id)} label="Close" />,
41
+ })
42
+ }}
43
+ >
44
+ Open modal
45
+ </Button>
46
+ <Button
47
+ onClick={() =>
48
+ modals.alert({
49
+ title: 'Import finished',
50
+ body: 'Your import has finish and can now be used.',
51
+ })
52
+ }
53
+ >
54
+ Open alert dialog
55
+ </Button>
56
+ <Button
57
+ onClick={() =>
58
+ modals.confirm({
59
+ title: 'Delete user?',
60
+ body: 'Are you sure you want to delete this user?',
61
+ confirmProps: {
62
+ colorScheme: 'red',
63
+ label: 'Delete',
64
+ },
65
+ })
66
+ }
67
+ >
68
+ Open confirm dialog
69
+ </Button>
70
+ <Button
71
+ onClick={() =>
72
+ modals.drawer({
73
+ title: 'My drawer',
74
+ body: (
75
+ <Stack>
76
+ <Button
77
+ onClick={() =>
78
+ modals.confirm({
79
+ title: 'Delete user?',
80
+ body: 'Are you sure you want to delete this user?',
81
+ confirmProps: {
82
+ colorScheme: 'red',
83
+ label: 'Delete',
84
+ },
85
+ })
86
+ }
87
+ >
88
+ Open confirm dialog
89
+ </Button>
90
+ <Button
91
+ onClick={() =>
92
+ modals.drawer({
93
+ title: 'Subdrawer',
94
+ body: (
95
+ <>
96
+ <Button
97
+ onClick={() => modals.closeAll()}
98
+ label="Close all"
99
+ >
100
+ Close all
101
+ </Button>
102
+ </>
103
+ ),
104
+ })
105
+ }
106
+ >
107
+ Open drawer
108
+ </Button>
109
+ </Stack>
110
+ ),
111
+ })
112
+ }
113
+ >
114
+ Open drawer
115
+ </Button>
116
+ </Stack>
117
+ )
118
+ }
119
+
120
+ export const custom = () => {
121
+ const modals = useModals()
122
+
123
+ return (
124
+ <Button
125
+ onClick={() =>
126
+ modals.open({
127
+ title: 'My Modal',
128
+ body: <>My modal</>,
129
+ type: 'custom',
130
+ })
131
+ }
132
+ >
133
+ Open modal
134
+ </Button>
135
+ )
136
+ }
137
+
138
+ export const onClose = () => {
139
+ const modals = useModals()
140
+
141
+ return (
142
+ <Button
143
+ onClick={() =>
144
+ modals.open({
145
+ title: 'My Modal',
146
+ body: <>My modal</>,
147
+ onClose: () => {
148
+ modals.confirm({
149
+ title: 'You closed the modal',
150
+ })
151
+ },
152
+ })
153
+ }
154
+ >
155
+ Open modal
156
+ </Button>
157
+ )
158
+ }
@@ -0,0 +1 @@
1
+ import { Modal } from '../src'
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["src"],
4
+ "exclude": [
5
+ "src/tests",
6
+ "**/*.spec.ts",
7
+ "**/*.spec.tsx",
8
+ "**/*.test.ts",
9
+ "**/*.test.tsx",
10
+ "**/*/*.stories.tsx"
11
+ ]
12
+ }