Skip to content

Commit

Permalink
feat: locally overlay-kit (#102)
Browse files Browse the repository at this point in the history
* refactor: Add createRegisterOverlaysStore

* refactor: Add createOverlay function

* refactor: Add createOverlayProvider function

* refactor: Refactor overlay provider structure and relocate content overlay controller

* feat: Add experimental_createOverlayContext function

* Create fair-shrimps-begin.md

* feat: improve overlay context management with createOverlaySafeContext function

* refactor: streamline overlay context creation and enhance dispatch logic

* test: Update overlay-kit test
  • Loading branch information
jungpaeng authored Feb 3, 2025
1 parent a54b71a commit becbd90
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 191 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-shrimps-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"overlay-kit": minor
---

feat: locally overlay-kit
16 changes: 10 additions & 6 deletions packages/src/context/context.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { type OverlayData } from './store';
import { createSafeContext } from '../utils/create-safe-context';

export const [OverlayContextProvider, useOverlayContext] = createSafeContext<OverlayData>('overlay-kit/OverlayContext');
export function createOverlaySafeContext() {
const [OverlayContextProvider, useOverlayContext] = createSafeContext<OverlayData>('overlay-kit/OverlayContext');

export function useCurrentOverlay() {
return useOverlayContext().current;
}
function useCurrentOverlay() {
return useOverlayContext().current;
}

function useOverlayData() {
return useOverlayContext().overlayData;
}

export function useOverlayData() {
return useOverlayContext().overlayData;
return { OverlayContextProvider, useCurrentOverlay, useOverlayData };
}
3 changes: 1 addition & 2 deletions packages/src/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { OverlayProvider } from './provider';
export { useCurrentOverlay, useOverlayData } from './context';
export { OverlayProvider, useCurrentOverlay, useOverlayData } from './provider';
95 changes: 0 additions & 95 deletions packages/src/context/provider.tsx

This file was deleted.

55 changes: 55 additions & 0 deletions packages/src/context/provider/content-overlay-controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { type FC, useEffect, useRef } from 'react';

type OverlayControllerProps = {
overlayId: string;
isOpen: boolean;
close: () => void;
unmount: () => void;
};

type OverlayAsyncControllerProps<T> = Omit<OverlayControllerProps, 'close'> & {
close: (param: T) => void;
};

export type OverlayControllerComponent = FC<OverlayControllerProps>;
export type OverlayAsyncControllerComponent<T> = FC<OverlayAsyncControllerProps<T>>;

type ContentOverlayControllerProps = {
isOpen: boolean;
current: string | null;
overlayId: string;
onMounted: () => void;
onCloseModal: () => void;
onExitModal: () => void;
controller: OverlayControllerComponent;
};

export function ContentOverlayController({
isOpen,
current,
overlayId,
onMounted,
onCloseModal,
onExitModal,
controller: Controller,
}: ContentOverlayControllerProps) {
const prevCurrent = useRef(current);
const onMountedRef = useRef(onMounted);

/**
* @description Executes when closing and reopening an overlay without unmounting.
*/
if (prevCurrent.current !== current) {
prevCurrent.current = current;

if (current === overlayId) {
onMountedRef.current();
}
}

useEffect(() => {
onMountedRef.current();
}, []);

return <Controller overlayId={overlayId} isOpen={isOpen} close={onCloseModal} unmount={onExitModal} />;
}
49 changes: 49 additions & 0 deletions packages/src/context/provider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect, type PropsWithChildren } from 'react';
import { ContentOverlayController } from './content-overlay-controller';
import { useSyncOverlayStore } from './use-sync-overlay-store';
import { createOverlay } from '../../event';
import { createOverlaySafeContext } from '../context';
import { type OverlayStore } from '../store';

export function createOverlayProvider(overlayStore: OverlayStore) {
const overlay = createOverlay(overlayStore);
const { OverlayContextProvider, useCurrentOverlay, useOverlayData } = createOverlaySafeContext();

function OverlayProvider({ children }: PropsWithChildren) {
const overlayState = useSyncOverlayStore(overlayStore);

useEffect(() => {
return () => {
overlayStore.dispatchOverlay({ type: 'REMOVE_ALL' });
};
}, []);

return (
<OverlayContextProvider value={overlayState}>
{children}
{overlayState.overlayOrderList.map((item) => {
const { id: currentOverlayId, isOpen, controller: currentController } = overlayState.overlayData[item];

return (
<ContentOverlayController
key={currentOverlayId}
isOpen={isOpen}
current={overlayState.current}
overlayId={currentOverlayId}
onMounted={() => {
requestAnimationFrame(() => {
overlayStore.dispatchOverlay({ type: 'OPEN', overlayId: currentOverlayId });
});
}}
onCloseModal={() => overlay.close(currentOverlayId)}
onExitModal={() => overlay.unmount(currentOverlayId)}
controller={currentController}
/>
);
})}
</OverlayContextProvider>
);
}

return { overlay, OverlayProvider, useCurrentOverlay, useOverlayData };
}
10 changes: 10 additions & 0 deletions packages/src/context/provider/use-sync-overlay-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js';
import { type OverlayStore } from '../store';

export function useSyncOverlayStore(overlayStore: OverlayStore) {
const {
registerOverlaysStore: { subscribe, getSnapshot },
} = overlayStore;

return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}
68 changes: 38 additions & 30 deletions packages/src/context/store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type OverlayControllerComponent } from './provider';
import { type OverlayControllerComponent } from './provider/content-overlay-controller';
import { type OverlayReducerAction, overlayReducer } from './reducer';

type OverlayId = string;
Expand All @@ -13,36 +13,44 @@ export type OverlayData = {
overlayData: Record<OverlayId, OverlayItem>;
};

let overlays: OverlayData = {
current: null,
overlayOrderList: [],
overlayData: {},
};
let listeners: Array<() => void> = [];

function emitChangeListener() {
for (const listener of listeners) {
listener();
}
}

export function dispatchOverlay(action: OverlayReducerAction) {
overlays = overlayReducer(overlays, action);
emitChangeListener();
}
export type OverlayStore = ReturnType<typeof createRegisterOverlaysStore>;

/**
* @description for useSyncExternalStorage
*/
export const registerOverlaysStore = {
subscribe(listener: () => void) {
listeners = [...listeners, listener];

return () => {
listeners = listeners.filter((l) => l !== listener);
};
},
getSnapshot() {
return overlays;
},
};
export function createRegisterOverlaysStore() {
let overlays: OverlayData = {
current: null,
overlayOrderList: [],
overlayData: {},
};
let listeners: Array<() => void> = [];

function emitChangeListener() {
for (const listener of listeners) {
listener();
}
}

const registerOverlaysStore = {
subscribe(listener: () => void) {
listeners = [...listeners, listener];

return () => {
listeners = listeners.filter((l) => l !== listener);
};
},
getSnapshot() {
return overlays;
},
};

function dispatchOverlay(action: OverlayReducerAction) {
console.log('test:: dispatchOverlay Before', action, overlays);
overlays = overlayReducer(overlays, action);
console.log('test:: dispatchOverlay After', action, overlays);
emitChangeListener();
}

return { registerOverlaysStore, dispatchOverlay };
}
7 changes: 0 additions & 7 deletions packages/src/context/use-sync-overlay-store.ts

This file was deleted.

3 changes: 1 addition & 2 deletions packages/src/event.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React, { useEffect, type PropsWithChildren } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { OverlayProvider } from './context/provider';
import { overlay } from './event';
import { OverlayProvider, overlay } from './utils/create-overlay-context';

function wrapper({ children }: PropsWithChildren) {
return <OverlayProvider>{children}</OverlayProvider>;
Expand Down
Loading

0 comments on commit becbd90

Please sign in to comment.