Skip to content

Commit

Permalink
fix: fix arrow status (#291)
Browse files Browse the repository at this point in the history
  • Loading branch information
asmyshlyaev177 authored Oct 7, 2024
1 parent 78ce1a2 commit 6f9ce44
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 41 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ Function callbacks also pass context, eg `onWheel`, `onScroll` etc.
| isItemVisible | itemId => boolean |
| isLastItem | boolean |
| isLastItemVisible | boolean |
| menuVisible | { current: boolean }
| scrollNext | (behavior, inline, block, ScrollOptions) => void |
| scrollPrev | (behavior, inline, block, ScrollOptions) => void |
| scrollToItem | (item, behavior, inline, block, ScrollOptions) => void |
Expand Down
20 changes: 18 additions & 2 deletions example-nextjs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,17 @@ const easingFunctions = {
const LeftArrow = React.memo(() => {
const visibility = React.useContext<publicApiType>(VisibilityContext);
const isFirstItemVisible = visibility.useIsVisible('first', true);

const [disabled, setDisabled] = React.useState(isFirstItemVisible);
React.useEffect(() => {
if (visibility.menuVisible.current) {
setDisabled(isFirstItemVisible);
}
}, [isFirstItemVisible, visibility.menuVisible]);

return (
<Arrow
disabled={isFirstItemVisible}
disabled={disabled}
onClick={() => visibility.scrollPrev()}
className="left"
>
Expand All @@ -266,9 +274,17 @@ const LeftArrow = React.memo(() => {
const RightArrow = React.memo(() => {
const visibility = React.useContext<publicApiType>(VisibilityContext);
const isLastItemVisible = visibility.useIsVisible('last', false);

const [disabled, setDisabled] = React.useState(isLastItemVisible);
React.useEffect(() => {
if (visibility.menuVisible) {
setDisabled(isLastItemVisible);
}
}, [isLastItemVisible, visibility.menuVisible]);

return (
<Arrow
disabled={isLastItemVisible}
disabled={disabled}
onClick={() => visibility.scrollNext()}
className="right"
>
Expand Down
1 change: 1 addition & 0 deletions src/createApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,5 @@ export default function createApi(
export interface publicApiType extends ReturnType<typeof createApi> {
items: ItemsMap;
scrollContainer: React.RefObject<HTMLElement | null>;
menuVisible: { current: boolean };
}
15 changes: 13 additions & 2 deletions src/hooks/useIntersectionObserver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ describe('useIntersectionObserver', () => {
const itemsChanged = '';
const options = observerOptions;
const refs: Refs = {};
const props = { items, itemsChanged, options, refs };
const props = {
items,
itemsChanged,
options,
refs,
};

observerMock.mockReturnValueOnce([]);
renderHook(() => useIntersectionObserver(props));
Expand Down Expand Up @@ -137,7 +142,13 @@ describe('useIntersectionObserver', () => {
el1: { current: document.createElement('div') },
el2: { current: document.createElement('div') },
};
const props = { items, itemsChanged, options, refs };
const props = {
items,
itemsChanged,
options,
refs,
menuVisible: { current: true },
};

const { unmount } = renderHook(() => useIntersectionObserver(props));

Expand Down
24 changes: 9 additions & 15 deletions src/hooks/useMenuVisible.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,30 @@ export const useMenuVisible = (
menuRef: { current: HTMLDivElement | null },
ratio: number,
) => {
const wrapperVisible = React.useRef(true);
const menuVisible = React.useRef(true);

const _ratio = React.useMemo(() => ratio + 0.01, [ratio]);
const threshold = React.useMemo(
() => [_ratio - 0.01, _ratio, _ratio + 0.01],
[_ratio],
() => [ratio - 0.05, ratio - 0.01, ratio, ratio + 0.01, ratio + 0.05],
[ratio],
);
const ioCb = React.useCallback(
(entries: IntersectionObserverEntry[]) => {
const isIntersecting = entries?.[0]?.intersectionRatio > _ratio;

if (wrapperVisible.current !== isIntersecting) {
wrapperVisible.current = isIntersecting;
}
menuVisible.current = entries?.[0]?.intersectionRatio >= ratio;
},
[_ratio],
[ratio],
);

useIsomorphicLayoutEffect(() => {
const observerInstance = new IntersectionObserver(ioCb, {
threshold,
rootMargin: '0px',
});
if (menuRef.current) {
observerInstance.observe(menuRef.current);
}

return () => {
observerInstance.disconnect();
};
}, [wrapperVisible, menuRef, ioCb, threshold]);
return () => observerInstance.disconnect();
}, [menuVisible, menuRef, ioCb, threshold]);

return wrapperVisible;
return menuVisible;
};
5 changes: 3 additions & 2 deletions src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ describe('ScrollMenu', () => {
items: expect.any(Object),
itemsChanged: '',
options,
wrapperVisible: { current: true },
refs: mapRefs(defaultItems),
});
});
Expand All @@ -189,7 +188,6 @@ describe('ScrollMenu', () => {
items: expect.any(Object),
itemsChanged: '',
options,
wrapperVisible: { current: false },
refs: mapRefs(defaultItems),
});
});
Expand Down Expand Up @@ -277,6 +275,9 @@ describe('ScrollMenu', () => {
const context = {
isFirstItemVisible: false,
isLastItemVisible: false,
menuVisible: {
current: true,
},
};

expect(ScrollContainer).toHaveClass(scrollContainerClassName);
Expand Down
16 changes: 8 additions & 8 deletions src/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ exports[`ScrollMenu Children, arrows, header and footer Header and footer 1`] =
<div
data-testid="test1"
>
{"itemId":"test1","isFirstItemVisible":false,"isLastItemVisible":false}
{"itemId":"test1","isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</div>
</div>
<div
Expand All @@ -42,7 +42,7 @@ exports[`ScrollMenu Children, arrows, header and footer Header and footer 1`] =
<div
data-testid="test2"
>
{"itemId":"test2","isFirstItemVisible":false,"isLastItemVisible":false}
{"itemId":"test2","isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</div>
</div>
</div>
Expand Down Expand Up @@ -81,7 +81,7 @@ exports[`ScrollMenu Children, arrows, header and footer LeftArrow, ScrollContain
class="left-arrow"
data-testid="left-arrow"
>
{"isFirstItemVisible":false,"isLastItemVisible":false}
{"isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</button>
</div>
<div
Expand All @@ -95,7 +95,7 @@ exports[`ScrollMenu Children, arrows, header and footer LeftArrow, ScrollContain
<div
data-testid="test1"
>
{"itemId":"test1","isFirstItemVisible":false,"isLastItemVisible":false}
{"itemId":"test1","isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</div>
</div>
<div
Expand All @@ -106,7 +106,7 @@ exports[`ScrollMenu Children, arrows, header and footer LeftArrow, ScrollContain
<div
data-testid="test2"
>
{"itemId":"test2","isFirstItemVisible":false,"isLastItemVisible":false}
{"itemId":"test2","isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</div>
</div>
</div>
Expand All @@ -117,7 +117,7 @@ exports[`ScrollMenu Children, arrows, header and footer LeftArrow, ScrollContain
class="right-arrow"
data-testid="right-arrow"
>
{"isFirstItemVisible":false,"isLastItemVisible":false}
{"isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</button>
</div>
</div>
Expand Down Expand Up @@ -153,7 +153,7 @@ exports[`ScrollMenu should render without props 1`] = `
<div
data-testid="test1"
>
{"itemId":"test1","isFirstItemVisible":false,"isLastItemVisible":false}
{"itemId":"test1","isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</div>
</div>
<div
Expand All @@ -164,7 +164,7 @@ exports[`ScrollMenu should render without props 1`] = `
<div
data-testid="test2"
>
{"itemId":"test2","isFirstItemVisible":false,"isLastItemVisible":false}
{"itemId":"test2","isFirstItemVisible":false,"isLastItemVisible":false,"menuVisible":{"current":true}}
</div>
</div>
</div>
Expand Down
16 changes: 8 additions & 8 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function ScrollMenu({
const Header = getElementOrConstructor(_Header);
const Footer = getElementOrConstructor(_Footer);

const scrollContainerRef = React.useRef<HTMLElement | null>(null);
const scrollContainerRef = React.useRef<HTMLDivElement | null>(null);
const [menuItemsRefs] = React.useState<Refs>({});

const observerOptions = React.useMemo(
Expand All @@ -199,18 +199,18 @@ function ScrollMenu({
// NOTE: hack for detect when items added/removed dynamicaly
const itemsChanged = useItemsChanged(children, items);

const wrapperRef = React.useRef<HTMLDivElement>(null);
const wrapperVisible = useMenuVisible(wrapperRef, observerOptions.ratio);

const menuVisible = useMenuVisible(
scrollContainerRef,
observerOptions.ratio + 0.05 < 1 ? observerOptions.ratio + 0.05 : 0.95,
);
const ioOptions = React.useMemo(
() => ({
items,
itemsChanged,
options: observerOptions,
refs: menuItemsRefs,
wrapperVisible,
}),
[items, itemsChanged, wrapperVisible, menuItemsRefs, observerOptions],
[items, itemsChanged, menuItemsRefs, observerOptions],
);
useIntersectionObserver(ioOptions);

Expand All @@ -233,8 +233,9 @@ function ScrollMenu({
...api,
items,
scrollContainer: scrollContainerRef,
menuVisible,
}),
[api, items, scrollContainerRef],
[api, items, scrollContainerRef, menuVisible],
);

const [context, setContext] = React.useState<publicApiType>(() =>
Expand Down Expand Up @@ -285,7 +286,6 @@ function ScrollMenu({
onTouchStart={onTouchStart?.(context)}
onTouchMove={onTouchMove?.(context)}
onTouchEnd={onTouchEnd?.(context)}
ref={wrapperRef}
>
<VisibilityContext.Provider value={context}>
<div className={constants.headerClassName}>{Header}</div>
Expand Down
12 changes: 8 additions & 4 deletions stories/0_Simple/Simple.source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ function LeftArrow() {

const [disabled, setDisabled] = React.useState(isFirstItemVisible);
React.useEffect(() => {
setDisabled(isFirstItemVisible);
}, [isFirstItemVisible]);
if (visibility.menuVisible.current) {
setDisabled(isFirstItemVisible);
}
}, [isFirstItemVisible, visibility.menuVisible]);

return (
<Arrow
Expand All @@ -93,8 +95,10 @@ function RightArrow() {

const [disabled, setDisabled] = React.useState(isLastItemVisible);
React.useEffect(() => {
setDisabled(isLastItemVisible);
}, [isLastItemVisible]);
if (visibility.menuVisible) {
setDisabled(isLastItemVisible);
}
}, [isLastItemVisible, visibility.menuVisible]);

return (
<Arrow
Expand Down

0 comments on commit 6f9ce44

Please sign in to comment.