Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(packages): simplify and clarify overlay-related types and constants #92

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
11 changes: 6 additions & 5 deletions packages/src/context/context.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { type OverlayData } from './store';
import { type OverlayState } from './store';
import { createSafeContext } from '../utils/create-safe-context';

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

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

export function useOverlayData() {
return useOverlayContext().overlayData;
return useOverlayContext().data;
}
2 changes: 1 addition & 1 deletion packages/src/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { OverlayProvider } from './provider';
export { useCurrentOverlay, useOverlayData } from './context';
export { useOverlayCurrentId, useOverlayData } from './context';
32 changes: 16 additions & 16 deletions packages/src/context/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type FC, useEffect, useRef, type PropsWithChildren } from 'react';
import { OverlayContextProvider } from './context';
import { dispatchOverlay } from './store';
import { type OverlayItem, dispatchOverlay } from './store';
import { useSyncOverlayStore } from './use-sync-overlay-store';
import { overlay } from '../event';

Expand All @@ -16,22 +16,22 @@ export function OverlayProvider({ children }: PropsWithChildren) {
return (
<OverlayContextProvider value={overlayState}>
{children}
{overlayState.overlayOrderList.map((item) => {
const { id: currentOverlayId, isOpen, controller: currentController } = overlayState.overlayData[item];
{overlayState.orderIds.map((orderId) => {
const { id: currentId, isOpen, controller: currentController } = overlayState.data[orderId];

return (
<ContentOverlayController
key={currentOverlayId}
key={currentId}
isOpen={isOpen}
current={overlayState.current}
overlayId={currentOverlayId}
currentId={overlayState.currentId}
overlayId={currentId}
onMounted={() => {
requestAnimationFrame(() => {
dispatchOverlay({ type: 'OPEN', overlayId: currentOverlayId });
dispatchOverlay({ type: 'OPEN', overlayId: currentId });
});
}}
onCloseModal={() => overlay.close(currentOverlayId)}
onExitModal={() => overlay.unmount(currentOverlayId)}
onCloseModal={() => overlay.close(currentId)}
onExitModal={() => overlay.unmount(currentId)}
controller={currentController}
/>
);
Expand All @@ -56,8 +56,8 @@ export type OverlayAsyncControllerComponent<T> = FC<OverlayAsyncControllerProps<

type ContentOverlayControllerProps = {
isOpen: boolean;
current: string | null;
overlayId: string;
currentId: OverlayItem['id'] | null;
overlayId: OverlayItem['id'];
onMounted: () => void;
onCloseModal: () => void;
onExitModal: () => void;
Expand All @@ -66,23 +66,23 @@ type ContentOverlayControllerProps = {

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

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

if (current === overlayId) {
if (currentId === overlayId) {
onMountedRef.current();
}
}
Expand Down
82 changes: 40 additions & 42 deletions packages/src/context/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,51 @@
import { type OverlayData, type OverlayItem } from './store';
import { type OverlayState, type OverlayItem } from './store';

export type OverlayReducerAction =
| { type: 'ADD'; overlay: OverlayItem }
| { type: 'OPEN'; overlayId: string }
| { type: 'CLOSE'; overlayId: string }
| { type: 'REMOVE'; overlayId: string }
| { type: 'OPEN'; overlayId: OverlayItem['id'] }
| { type: 'CLOSE'; overlayId: OverlayItem['id'] }
| { type: 'REMOVE'; overlayId: OverlayItem['id'] }
| { type: 'CLOSE_ALL' }
| { type: 'REMOVE_ALL' };

export function overlayReducer(state: OverlayData, action: OverlayReducerAction): OverlayData {
export function overlayReducer(state: OverlayState, action: OverlayReducerAction): OverlayState {
switch (action.type) {
case 'ADD': {
const isExisted = state.overlayOrderList.includes(action.overlay.id);
const isExisted = state.orderIds.includes(action.overlay.id);

if (isExisted && state.overlayData[action.overlay.id].isOpen === true) {
if (isExisted && state.data[action.overlay.id].isOpen === true) {
throw new Error("You can't open the multiple overlays with the same overlayId. Please set a different id.");
}

return {
current: action.overlay.id,
currentId: action.overlay.id,
/**
* @description Brings the overlay to the front when reopened after closing without unmounting.
*/
overlayOrderList: [...state.overlayOrderList.filter((item) => item !== action.overlay.id), action.overlay.id],
overlayData: isExisted
? state.overlayData
orderIds: [...state.orderIds.filter((orderId) => orderId !== action.overlay.id), action.overlay.id],
data: isExisted
? state.data
: {
...state.overlayData,
...state.data,
[action.overlay.id]: action.overlay,
},
};
}
case 'OPEN': {
return {
...state,
overlayData: {
...state.overlayData,
data: {
...state.data,
[action.overlayId]: {
...state.overlayData[action.overlayId],
...state.data[action.overlayId],
isOpen: true,
},
},
};
}
case 'CLOSE': {
const openedOverlayOrderList = state.overlayOrderList.filter(
(orderedOverlayId) => state.overlayData[orderedOverlayId].isOpen === true
);
const targetIndexInOpenedList = openedOverlayOrderList.findIndex((item) => item === action.overlayId);
const openedOrderIds = state.orderIds.filter((orderId) => state.data[orderId].isOpen === true);
const targetOpenedOrderIdIndex = openedOrderIds.findIndex((openedOrderId) => openedOrderId === action.overlayId);

/**
* @description If closing the last overlay, specify the overlay before it.
Expand All @@ -60,60 +58,60 @@ export function overlayReducer(state: OverlayData, action: OverlayReducerAction)
* close 1 => current: null
*/
const currentOverlayId =
targetIndexInOpenedList === openedOverlayOrderList.length - 1
? openedOverlayOrderList[targetIndexInOpenedList - 1] ?? null
: openedOverlayOrderList.at(-1) ?? null;
targetOpenedOrderIdIndex === openedOrderIds.length - 1
? openedOrderIds[targetOpenedOrderIdIndex - 1] ?? null
: openedOrderIds.at(-1) ?? null;

return {
...state,
current: currentOverlayId,
overlayData: {
...state.overlayData,
currentId: currentOverlayId,
data: {
...state.data,
[action.overlayId]: {
...state.overlayData[action.overlayId],
...state.data[action.overlayId],
isOpen: false,
},
},
};
}
case 'REMOVE': {
const remainingOverlays = state.overlayOrderList.filter((item) => item !== action.overlayId);
if (state.overlayOrderList.length === remainingOverlays.length) {
const remainingOrderIds = state.orderIds.filter((orderId) => orderId !== action.overlayId);
if (state.orderIds.length === remainingOrderIds.length) {
return state;
}

const copiedOverlayData = { ...state.overlayData };
delete copiedOverlayData[action.overlayId];
const copiedData = { ...state.data };
delete copiedData[action.overlayId];

const current = state.current
? remainingOverlays.includes(state.current)
const currentId = state.currentId
? remainingOrderIds.includes(state.currentId)
? /**
* @description If `unmount` was executed after `close`
*/
state.current
state.currentId
: /**
* @description If you only run `unmount`, there is no `current` in `remainingOverlays`
* @description If you only run `unmount`, there is no `currentId` in `remainingOrderIds`
*/
remainingOverlays.at(-1) ?? null
remainingOrderIds.at(-1) ?? null
: /**
* @description The case where `current` is `null`
* @description The case where `currentId` is `null`
*/
null;

return {
current,
overlayOrderList: remainingOverlays,
overlayData: copiedOverlayData,
currentId,
orderIds: remainingOrderIds,
data: copiedData,
};
}
case 'CLOSE_ALL': {
return {
...state,
overlayData: Object.keys(state.overlayData).reduce(
data: Object.keys(state.data).reduce(
(prev, curr) => ({
...prev,
[curr]: {
...state.overlayData[curr],
...state.data[curr],
isOpen: false,
} satisfies OverlayItem,
}),
Expand All @@ -122,7 +120,7 @@ export function overlayReducer(state: OverlayData, action: OverlayReducerAction)
};
}
case 'REMOVE_ALL': {
return { current: null, overlayOrderList: [], overlayData: {} };
return { currentId: null, orderIds: [], data: {} };
}
}
}
22 changes: 11 additions & 11 deletions packages/src/context/store.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { type OverlayControllerComponent } from './provider';
import { type OverlayReducerAction, overlayReducer } from './reducer';

type OverlayId = string;
type OverlayItemId = string;
export type OverlayItem = {
id: OverlayId;
id: OverlayItemId;
isOpen: boolean;
controller: OverlayControllerComponent;
};
export type OverlayData = {
current: OverlayId | null;
overlayOrderList: OverlayId[];
overlayData: Record<OverlayId, OverlayItem>;
export type OverlayState = {
currentId: OverlayItemId | null;
orderIds: OverlayItemId[];
data: Record<OverlayItemId, OverlayItem>;
};

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

Expand All @@ -34,7 +34,7 @@ export function dispatchOverlay(action: OverlayReducerAction) {
/**
* @description for useSyncExternalStorage
*/
export const registerOverlaysStore = {
export const registerOverlayStore = {
subscribe(listener: () => void) {
listeners = [...listeners, listener];

Expand Down
4 changes: 2 additions & 2 deletions packages/src/context/use-sync-overlay-store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js';
import { registerOverlaysStore } from './store';
import { registerOverlayStore } from './store';

export function useSyncOverlayStore() {
const { subscribe, getSnapshot } = registerOverlaysStore;
const { subscribe, getSnapshot } = registerOverlayStore;
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}
8 changes: 4 additions & 4 deletions packages/src/event.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type OverlayAsyncControllerComponent, type OverlayControllerComponent } from './context/provider';
import { dispatchOverlay } from './context/store';
import { type OverlayItem, dispatchOverlay } from './context/store';
import { randomId } from './utils';

type OpenOverlayOptions = {
overlayId?: string;
overlayId?: OverlayItem['id'];
};

function open(controller: OverlayControllerComponent, options?: OpenOverlayOptions) {
Expand Down Expand Up @@ -40,10 +40,10 @@ async function openAsync<T>(controller: OverlayAsyncControllerComponent<T>, opti
});
}

function close(overlayId: string) {
function close(overlayId: OverlayItem['id']) {
dispatchOverlay({ type: 'CLOSE', overlayId });
}
function unmount(overlayId: string) {
function unmount(overlayId: OverlayItem['id']) {
dispatchOverlay({ type: 'REMOVE', overlayId });
}
function closeAll() {
Expand Down
Loading