diff --git a/.changeset/polite-pans-stare.md b/.changeset/polite-pans-stare.md new file mode 100644 index 0000000..85e7da8 --- /dev/null +++ b/.changeset/polite-pans-stare.md @@ -0,0 +1,9 @@ +--- +"overlay-kit": minor +--- + +feat: Add overlayAsync implementation + +This change implements the openAsync method for overly-kit's promise support discussed in #47. + +**Related Issue:** Fixes #47 diff --git a/packages/src/context/provider.tsx b/packages/src/context/provider.tsx index c6200c4..6d2046e 100644 --- a/packages/src/context/provider.tsx +++ b/packages/src/context/provider.tsx @@ -41,7 +41,12 @@ type OverlayControllerProps = { unmount: () => void; }; +type OverlayAsyncControllerProps = Omit & { + close: (param: T) => void; +}; + export type OverlayControllerComponent = FC; +export type OverlayAsyncControllerComponent = FC>; type ContentOverlayControllerProps = { isOpen: boolean; diff --git a/packages/src/event.test.tsx b/packages/src/event.test.tsx index c5e6188..9a4a24d 100644 --- a/packages/src/event.test.tsx +++ b/packages/src/event.test.tsx @@ -1,6 +1,6 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { act, useEffect, type PropsWithChildren } from 'react'; -import { afterEach, describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { OverlayProvider } from './context/provider'; import { overlay } from './event'; @@ -74,4 +74,72 @@ describe('overlay object', () => { expect(screen.queryByText(testContent3)).toBeInTheDocument(); expect(screen.queryByText(testContent4)).toBeInTheDocument(); }); + + it('The value passed as an argument to close is passed to resolve. overlay.openAsync', async () => { + const wrapper = ({ children }: PropsWithChildren) => {children}; + const testContent = 'context-modal-test-content'; + const dialogContent = 'context-modal-dialog-content'; + const mockFn = vi.fn(); + const Component = () => { + const handleClick = async () => { + const result = await overlay.openAsync(({ close }) => ( + + )); + + if (result) { + mockFn(result); + } + }; + + return ; + }; + + const renderComponent = render(, { wrapper }); + const testContentElement = await renderComponent.findByText(testContent); + + act(() => { + testContentElement.click(); + }); + + const dialogContentElement = await renderComponent.findByText(dialogContent); + + act(() => { + dialogContentElement.click(); + }); + await waitFor(() => { + expect(mockFn).toHaveBeenCalledWith(true); + }); + }); + + it('should be able to turn off overlay through close overlay.openAsync', async () => { + const wrapper = ({ children }: PropsWithChildren) => {children}; + const testContent = 'context-modal-test-content'; + const dialogContent = 'context-modal-dialog-content'; + + const Component = () => { + const handleClick = async () => { + overlay.openAsync(({ isOpen, close }) => + isOpen ? : null + ); + }; + return ; + }; + + const renderComponent = render(, { wrapper }); + const testContentElement = await renderComponent.findByText(testContent); + + act(() => { + testContentElement.click(); + }); + + const dialogContentElement = await renderComponent.findByText(dialogContent); + + act(() => { + dialogContentElement.click(); + }); + + await waitFor(() => { + expect(dialogContentElement).not.toBeInTheDocument(); + }); + }); }); diff --git a/packages/src/event.ts b/packages/src/event.ts index 080089b..2e1dd40 100644 --- a/packages/src/event.ts +++ b/packages/src/event.ts @@ -1,4 +1,4 @@ -import { type OverlayControllerComponent } from './context/provider'; +import { type OverlayAsyncControllerComponent, type OverlayControllerComponent } from './context/provider'; import { dispatchOverlay } from './context/store'; import { randomId } from './utils'; @@ -20,6 +20,26 @@ function open(controller: OverlayControllerComponent, options?: OpenOverlayOptio return overlayId; } + +async function openAsync(controller: OverlayAsyncControllerComponent, options?: OpenOverlayOptions) { + return new Promise((resolve) => { + open((overlayProps, ...deprecatedLegacyContext) => { + /** + * @description close the overlay with resolve + */ + const close = (param: T) => { + resolve(param as T); + overlayProps.close(); + }; + /** + * @description Passing overridden methods + */ + const props = { ...overlayProps, close }; + return controller(props, ...deprecatedLegacyContext); + }, options); + }); +} + function close(overlayId: string) { dispatchOverlay({ type: 'CLOSE', overlayId }); } @@ -33,4 +53,4 @@ function unmountAll() { dispatchOverlay({ type: 'REMOVE_ALL' }); } -export const overlay = { open, close, unmount, closeAll, unmountAll }; +export const overlay = { open, close, unmount, closeAll, unmountAll, openAsync };