Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions demo/ModalControls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ const ModalControls: React.FC<ModalPropsWithContext> = (props) => {
lockBodyScroll,
} = props;

const {
closeOnBlur,
closeAllModals,
toggleModal,
} = useModal();
const { closeOnBlur, closeAllModals, toggleModal, updateModalLock, isModalLocked } = useModal();

return (
<Fragment>
Expand Down Expand Up @@ -47,6 +43,22 @@ const ModalControls: React.FC<ModalPropsWithContext> = (props) => {
</button>
<br />
<br />
<label htmlFor="lockOpen">
<code>Lock Open:&nbsp;</code>
<input
type="checkbox"
id="lockOpen"
onChange={(e) => {
updateModalLock({
slug,
lock: Boolean(e.target.value === "true"),
})
}}
value={Boolean(!isModalLocked(slug)).toString() || ""}
checked={isModalLocked(slug)}
/>
</label>
<br />
<label htmlFor="closeOnBlur">
<code>closeOnBlur:&nbsp;</code>
<input
Expand Down
10 changes: 10 additions & 0 deletions src/ModalProvider/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type ModalStatus = {
slug: string
isOpen: boolean
openedOn?: number
locked?: boolean
}

export type ModalState = {
Expand All @@ -19,6 +20,10 @@ export interface IModalContext extends ModalProviderProps {
modalState: ModalState
oneModalIsOpen: boolean
isModalOpen: (slug: string) => boolean // eslint-disable-line no-unused-vars
/**
* Check if a modal is locked (cannot be closed programmatically)
*/
isModalLocked: (slug: string) => boolean // eslint-disable-line no-unused-vars
closeOnBlur: boolean
bodyScrollIsLocked: boolean
classPrefix?: string
Expand All @@ -32,6 +37,11 @@ export interface IModalContext extends ModalProviderProps {
shouldLock: boolean, // eslint-disable-line no-unused-vars
excludingRef: HTMLElement // eslint-disable-line no-unused-vars
) => void
/**
* Set a modal to "locked" to prevent closing it when
* calling functions that would close it
*/
updateModalLock: ({slug, lock}: {slug: string, lock: boolean}) => void // eslint-disable-line no-unused-vars
}

export const ModalContext = createContext<IModalContext>({} as IModalContext);
18 changes: 17 additions & 1 deletion src/ModalProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const ModalProvider: React.FC<ModalProviderProps> = (props) => {
const escIsBound = useRef(false);

const bindEsc = useCallback((e: KeyboardEvent) => {
if (e.keyCode === 27) {
if (e.key === 'Escape' || e.keyCode === 27) {
dispatchModalState({
type: 'CLOSE_LATEST_MODAL',
})
Expand Down Expand Up @@ -194,10 +194,24 @@ export const ModalProvider: React.FC<ModalProviderProps> = (props) => {
})
}, [])

const updateModalLock = useCallback(({slug, lock}: {slug: string, lock: boolean}) => {
dispatchModalState({
type: 'UPDATE_LOCK_MODAL',
payload: {
slug,
lock,
}
})
}, [])

const isModalOpen = useCallback((slug: string) => {
return modalState[slug] && modalState[slug].isOpen;
}, [modalState])

const isModalLocked = useCallback((slug: string) => {
return Boolean(modalState[slug] && modalState[slug].locked);
}, [modalState])

const inheritedProps = { ...props };
delete inheritedProps.children;

Expand Down Expand Up @@ -225,6 +239,8 @@ export const ModalProvider: React.FC<ModalProviderProps> = (props) => {
toggleModal,
setBodyScrollLock,
setContainerRef,
updateModalLock,
isModalLocked,
}}
>
{children}
Expand Down
40 changes: 35 additions & 5 deletions src/ModalProvider/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ export type OPEN_MODAL = {

export type CLOSE_MODAL = {
type: 'CLOSE_MODAL'
payload: {
slug: string;
};
};

export type UPDATE_LOCK_MODAL = {
type: "UPDATE_LOCK_MODAL";
payload: {
slug: string
lock: boolean
}
}

Expand Down Expand Up @@ -47,6 +55,7 @@ export type Action = UPDATE_MODAL
| OPEN_MODAL
| REMOVE_MODAL
| CLOSE_MODAL
| UPDATE_LOCK_MODAL
| TOGGLE_MODAL
| CLOSE_LATEST_MODAL
| CLOSE_ALL_MODALS;
Expand Down Expand Up @@ -107,11 +116,15 @@ export const reducer = (
if (slug) {
const isCurrentlyOpen = slug in newState && newState[slug].isOpen;

if (isCurrentlyOpen && newState[slug].locked) {
break;
}

newState[slug] = {
...newState[slug],
slug,
openedOn: !isCurrentlyOpen ? Date.now() : undefined,
isOpen: !isCurrentlyOpen
isOpen: !isCurrentlyOpen,
};
}

Expand All @@ -123,7 +136,7 @@ export const reducer = (
slug,
} = payload;

if (slug) {
if (slug && !newState[slug].locked) {
newState[slug] = {
...newState[slug],
slug,
Expand All @@ -135,6 +148,23 @@ export const reducer = (
break;
}

case 'UPDATE_LOCK_MODAL': {
const {
slug,
lock,
} = payload;

if (slug && newState[slug].isOpen) {
newState[slug] = {
...newState[slug],
slug,
locked: lock,
};
}

break;
}

case 'REMOVE_MODAL': {
const {
slug,
Expand All @@ -159,7 +189,7 @@ export const reducer = (
return acc;
}, undefined);

if (latestModal) {
if (latestModal && !latestModal.locked) {
newState[latestModal.slug] = {
...newState[latestModal.slug],
isOpen: false,
Expand All @@ -174,8 +204,8 @@ export const reducer = (
newState = Object.entries((newState)).reduce((acc, [key, value]) => {
acc[key] = {
...value,
isOpen: false,
openedOn: undefined,
isOpen: newState[key].locked ? newState[key].isOpen : false,
openedOn: newState[key].locked ? newState[key].openedOn : undefined,
};

return acc;
Expand Down