diff --git a/.changeset/fast-meals-call.md b/.changeset/fast-meals-call.md new file mode 100644 index 0000000000..3b006f1b2d --- /dev/null +++ b/.changeset/fast-meals-call.md @@ -0,0 +1,9 @@ +--- +'@alfalab/core-components-bottom-sheet': patch +'@alfalab/core-components': patch +--- + +##### BottomSheet + +- Исправлено нестабильное поведение `BottomSheet` при динамической смене `magneticAreas`. +- Если после обновления `magneticAreas` активный индекс выходит за границы массива (например, когда массив стал короче), компонент теперь переводит шторку в ближайшую доступную магнитную область вместо сброса в offset `0`. Это устраняет рывки и визуальные скачки, сохраняя ближайшую предыдущую высоту. diff --git a/packages/bottom-sheet/src/component.test.tsx b/packages/bottom-sheet/src/component.test.tsx index 9d835b1d91..fb07f97b43 100644 --- a/packages/bottom-sheet/src/component.test.tsx +++ b/packages/bottom-sheet/src/component.test.tsx @@ -494,6 +494,38 @@ describe('Bottom sheet', () => { await waitFor(() => expect(onMagnetizeEnd).toHaveBeenCalledWith(1)); }); + it('should remap active magnetic area after magneticAreas change', async () => { + const onMagnetize = jest.fn(); + + const { rerender } = render( + , + ); + + await waitFor(() => expect(onMagnetize).toHaveBeenCalledWith(2)); + + onMagnetize.mockClear(); + + rerender( + , + ); + + await waitFor(() => expect(onMagnetize).toHaveBeenCalledWith(1)); + }); + it('should call onMagnetize and onMagnetizeEnd prop after closing', async () => { const onMagnetize = jest.fn(); const onMagnetizeEnd = jest.fn(); diff --git a/packages/bottom-sheet/src/component.tsx b/packages/bottom-sheet/src/component.tsx index 1fbbf2ec5c..6030fe77cd 100644 --- a/packages/bottom-sheet/src/component.tsx +++ b/packages/bottom-sheet/src/component.tsx @@ -141,6 +141,7 @@ export const BottomSheet = forwardRef( fullHeight = adjustContainerHeight(fullHeight); const initialIndexRef = useRef(initialActiveAreaIndex); + const prevMagneticAreasRef = useRef(); const prevMagneticAreasPropRef = useRef | undefined>( magneticAreasProp, ); @@ -259,6 +260,25 @@ export const BottomSheet = forwardRef( }; const getActiveAreaIndex = (area?: number) => magneticAreas.findIndex((a) => a === area); + const getClosestAreaIndex = (area: number, areas: number[]) => + areas.reduce( + (result, currentArea, currentIndex) => { + const distance = Math.abs(currentArea - area); + + if (distance < result.distance) { + return { + distance, + index: currentIndex, + }; + } + + return result; + }, + { + distance: Number.POSITIVE_INFINITY, + index: 0, + }, + ).index; const setSheetHeight = () => { if (sheetRef.current) { @@ -552,8 +572,10 @@ export const BottomSheet = forwardRef( if (!isFirstRender) { const magneticAreasPropChanged = prevMagneticAreasPropRef.current !== magneticAreasProp; + const prevMagneticAreas = prevMagneticAreasRef.current; prevMagneticAreasPropRef.current = magneticAreasProp; + prevMagneticAreasRef.current = magneticAreas; if (magneticAreasPropChanged) { setSkipTransition(true); @@ -565,6 +587,16 @@ export const BottomSheet = forwardRef( setSheetOffset(isNil(idx) ? 0 : lastMagneticArea - magneticAreas[idx]); setActiveAreaIdx(isNil(idx) ? magneticAreas.length - 1 : idx); + } else if (magneticAreasPropChanged && prevMagneticAreas?.length) { + const prevActiveArea = + prevMagneticAreas[activeAreaIdx] ?? + prevMagneticAreas[prevMagneticAreas.length - 1]; + const nextAreaIdx = getClosestAreaIndex(prevActiveArea, magneticAreas); + const nextArea = magneticAreas[nextAreaIdx]; + + setSheetOffset(lastMagneticArea - nextArea); + setActiveAreaIdx(nextAreaIdx); + onMagnetize?.(nextAreaIdx); } else { setSheetOffset(activeArea ? lastMagneticArea - activeArea : 0); }