Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/eighty-singers-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"overlay-kit": patch
---

refactor: Extract determine current overlay logic to a utility function
54 changes: 54 additions & 0 deletions packages/src/context/reducer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest';
import { determineCurrentOverlayId } from './reducer';

describe('determineCurrentOverlayId', () => {
it('should return the previous overlay when closing the last overlay', () => {
const overlayOrderList = ['id1', 'id2', 'id3'];
const overlayData = {
id1: { id: 'id1', componentKey: 'key1', isOpen: true, controller: () => null },
id2: { id: 'id2', componentKey: 'key2', isOpen: true, controller: () => null },
id3: { id: 'id3', componentKey: 'key3', isOpen: true, controller: () => null },
};

const result = determineCurrentOverlayId(overlayOrderList, overlayData, 'id3');

expect(result).toBe('id2');
});

it('should return the last overlay when closing an intermediate overlay', () => {
const overlayOrderList = ['id1', 'id2', 'id3'];
const overlayData = {
id1: { id: 'id1', componentKey: 'key1', isOpen: true, controller: () => null },
id2: { id: 'id2', componentKey: 'key2', isOpen: true, controller: () => null },
id3: { id: 'id3', componentKey: 'key3', isOpen: true, controller: () => null },
};

const result = determineCurrentOverlayId(overlayOrderList, overlayData, 'id2');

expect(result).toBe('id3');
});

it('should skip closed overlays when determining the current overlay', () => {
const overlayOrderList = ['id1', 'id2', 'id3'];
const overlayData = {
id1: { id: 'id1', componentKey: 'key1', isOpen: true, controller: () => null },
id2: { id: 'id2', componentKey: 'key2', isOpen: false, controller: () => null },
id3: { id: 'id3', componentKey: 'key3', isOpen: true, controller: () => null },
};

const result = determineCurrentOverlayId(overlayOrderList, overlayData, 'id3');

expect(result).toBe('id1');
});

it('should return null when closing the only open overlay', () => {
const overlayOrderList = ['id1'];
const overlayData = {
id1: { id: 'id1', componentKey: 'key1', isOpen: true, controller: () => null },
};

const result = determineCurrentOverlayId(overlayOrderList, overlayData, 'id1');

expect(result).toBe(null);
});
});
72 changes: 34 additions & 38 deletions packages/src/context/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,38 @@ type OverlayReducerAction =
| { type: 'CLOSE_ALL' }
| { type: 'REMOVE_ALL' };

/**
* Determines which overlay should become the current one when closing or removing an overlay.
*
* @description If closing the last overlay, specify the overlay before it.
* @description If closing intermediate overlays, specifies the last overlay.
*
* @example open - [1, 2, 3, 4]
* close 2 => current: 4
* close 4 => current: 3
* close 3 => current: 1
* close 1 => current: null
*
* @param overlayOrderList The ordered list of overlay IDs
* @param overlayData The map of overlay data
* @param targetOverlayId The ID of the overlay being closed or removed
* @returns The ID of the overlay that should become current, or null if none
*/
export const determineCurrentOverlayId = (
overlayOrderList: OverlayId[],
overlayData: Record<OverlayId, OverlayItem>,
targetOverlayId: OverlayId
): OverlayId | null => {
const openedOverlayOrderList = overlayOrderList.filter(
(orderedOverlayId) => overlayData[orderedOverlayId].isOpen === true
);
const targetIndexInOpenedList = openedOverlayOrderList.findIndex((item) => item === targetOverlayId);

return targetIndexInOpenedList === openedOverlayOrderList.length - 1
? openedOverlayOrderList[targetIndexInOpenedList - 1] ?? null
: openedOverlayOrderList.at(-1) ?? null;
};

export function overlayReducer(state: OverlayData, action: OverlayReducerAction): OverlayData {
switch (action.type) {
case 'ADD': {
Expand Down Expand Up @@ -78,25 +110,7 @@ export function overlayReducer(state: OverlayData, action: OverlayReducerAction)
return state;
}

const openedOverlayOrderList = state.overlayOrderList.filter(
(orderedOverlayId) => state.overlayData[orderedOverlayId].isOpen === true
);
const targetIndexInOpenedList = openedOverlayOrderList.findIndex((item) => item === action.overlayId);

/**
* @description If closing the last overlay, specify the overlay before it.
* @description If closing intermediate overlays, specifies the last overlay.
*
* @example open - [1, 2, 3, 4]
* close 2 => current: 4
* close 4 => current: 3
* close 3 => current: 1
* close 1 => current: null
*/
const currentOverlayId =
targetIndexInOpenedList === openedOverlayOrderList.length - 1
? openedOverlayOrderList[targetIndexInOpenedList - 1] ?? null
: openedOverlayOrderList.at(-1) ?? null;
const currentOverlayId = determineCurrentOverlayId(state.overlayOrderList, state.overlayData, action.overlayId);

return {
...state,
Expand Down Expand Up @@ -126,25 +140,7 @@ export function overlayReducer(state: OverlayData, action: OverlayReducerAction)
const copiedOverlayData = { ...state.overlayData };
delete copiedOverlayData[action.overlayId];

const openedOverlayOrderList = state.overlayOrderList.filter(
(orderedOverlayId) => state.overlayData[orderedOverlayId].isOpen === true
);
const targetIndexInOpenedList = openedOverlayOrderList.findIndex((item) => item === action.overlayId);

/**
* @description If unmounting the last overlay, specify the overlay before it.
* @description If unmounting intermediate overlays, specifies the last overlay.
*
* @example open - [1, 2, 3, 4]
* unmount 2 => current: 4
* unmount 4 => current: 3
* unmount 3 => current: 1
* unmount 1 => current: null
*/
const currentOverlayId =
targetIndexInOpenedList === openedOverlayOrderList.length - 1
? openedOverlayOrderList[targetIndexInOpenedList - 1] ?? null
: openedOverlayOrderList.at(-1) ?? null;
const currentOverlayId = determineCurrentOverlayId(state.overlayOrderList, state.overlayData, action.overlayId);

return {
current: currentOverlayId,
Expand Down