Skip to content

Commit

Permalink
feat: eslint, tests, docs
Browse files Browse the repository at this point in the history
  • Loading branch information
monteri committed Jun 2, 2023
1 parent 4ef4317 commit 6f587a0
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 42 deletions.
6 changes: 5 additions & 1 deletion src/OverflowScroll/OverflowScroll.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function OverflowScroll({
onScrollPrevious,
onScrollNext,
offset,
offsetType,
}) {
const [overflowRef, setOverflowRef] = useState();

Expand All @@ -31,6 +32,7 @@ function OverflowScroll({
onScrollNext,
overflowRef,
offset,
offsetType,
});

const contextValue = useMemo(() => ({
Expand Down Expand Up @@ -82,7 +84,8 @@ OverflowScroll.propTypes = {
onScrollPrevious: PropTypes.func,
/** Callback function for when the user scrolls to the next element. */
onScrollNext: PropTypes.func,
offset: PropTypes.string,
offset: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
offsetType: PropTypes.oneOf(['percentage', 'fixed']),
};

OverflowScroll.defaultProps = {
Expand All @@ -93,6 +96,7 @@ OverflowScroll.defaultProps = {
onScrollPrevious: undefined,
onScrollNext: undefined,
offset: undefined,
offsetType: 'percentage',
};

export default OverflowScroll;
80 changes: 80 additions & 0 deletions src/OverflowScroll/data/tests/useOverflowScrollActions.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,84 @@ describe('useOverflowScrollActions', () => {
);
expect(mockScrollTo).toBeCalledTimes(1);
});

it('scrollToPrevious moves scroll to specified percentage', () => {
const scrollWidth = 1000;
const clientWidth = 200;

const overflowRef = {
scrollWidth,
clientWidth,
scrollTo: jest.fn(),
};
const activeChildElementIndex = 2;
const childrenElements = [...Array(5)];
const offset = '20';
const offsetType = 'percentage';
const currentOffset = 500;
const onChangeOffset = jest.fn();
const scrollAnimationBehavior = 'smooth';

const { result } = renderHook(() => useOverflowScrollActions({
overflowRef,
activeChildElementIndex,
childrenElements,
offset,
offsetType,
currentOffset,
onChangeOffset,
scrollAnimationBehavior,
}));
const { scrollToPrevious } = result.current;

act(() => {
scrollToPrevious();
});

expect(onChangeOffset).toHaveBeenCalledWith(currentOffset - 160);
expect(overflowRef.scrollTo).toHaveBeenCalledWith({
left: currentOffset - 160,
behavior: scrollAnimationBehavior,
});
});

it('scrollToNext moves scroll to specified fixed value', async () => {
const scrollWidth = 1000;
const clientWidth = 200;

const overflowRef = {
scrollWidth,
clientWidth,
scrollTo: jest.fn(),
};
const activeChildElementIndex = 2;
const childrenElements = [...Array(5)];
const offset = '20';
const offsetType = 'percentage';
const currentOffset = 0;
const onChangeOffset = jest.fn();
const scrollAnimationBehavior = 'smooth';

const { result } = renderHook(() => useOverflowScrollActions({
overflowRef,
activeChildElementIndex,
childrenElements,
offset,
offsetType,
currentOffset,
onChangeOffset,
scrollAnimationBehavior,
}));
const { scrollToNext } = result.current;

act(() => {
scrollToNext();
});

expect(onChangeOffset).toHaveBeenCalledWith(currentOffset + 160);
expect(overflowRef.scrollTo).toHaveBeenCalledWith({
left: currentOffset + 160,
behavior: scrollAnimationBehavior,
});
});
});
2 changes: 2 additions & 0 deletions src/OverflowScroll/data/useOverflowScroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const useOverflowScroll = ({
disableOpacityMasks = false,
scrollAnimationBehavior = 'smooth',
offset,
offsetType = 'percentage',
}) => {
const [currentOffset, setCurrentOffset] = useState(0);
const [isScrolledToStart, setIsScrolledToStart] = useState(true);
Expand Down Expand Up @@ -159,6 +160,7 @@ const useOverflowScroll = ({
onChangeOffset: setCurrentOffset,
currentOffset,
offset,
offsetType,
});

return {
Expand Down
75 changes: 34 additions & 41 deletions src/OverflowScroll/data/useOverflowScrollActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,15 @@ const useOverflowScrollActions = ({
onChangeOffset,
currentOffset,
offset,
offsetType = 'percentage',
}) => {
const getOffset = useCallback((value, type, maxOffset) => {
const numOffset = parseInt(value, 10);

const getOffset = (offsetString, maxOffset) => {
let offset = 0;

if (offsetString.endsWith("px")) {
offset = parseInt(offsetString);
} else if (offsetString.endsWith("%")) {
const percent = parseInt(offsetString) / 100;
offset = Math.round(maxOffset * percent);
}

return offset;
}
return type === 'percentage'
? Math.round(maxOffset * (numOffset / 100))
: numOffset;
}, []);

/**
* A helper function to scroll to the previous element in the overflow container.
Expand All @@ -55,12 +50,14 @@ const useOverflowScrollActions = ({

if (offset) {
const maxOffset = overflowRef.scrollWidth - overflowRef.clientWidth;
const offsetValue = getOffset(offset, maxOffset);
const offsetValue = getOffset(offset, offsetType, maxOffset);
calculatedOffsetLeft = currentOffset - offsetValue;
if (calculatedOffsetLeft < 0) {
onChangeOffset(0);
} else {
onChangeOffset(calculatedOffsetLeft);
if (onChangeOffset) {
if (calculatedOffsetLeft < 0) {
onChangeOffset(0);
} else {
onChangeOffset(calculatedOffsetLeft);
}
}
}

Expand All @@ -69,16 +66,13 @@ const useOverflowScrollActions = ({
behavior: scrollAnimationBehavior,
});
const currentActiveChildElementIndex = previousChildElementIndex <= 0 ? 0 : previousChildElementIndex;
onScrollPrevious({
currentActiveChildElementIndex,
});
}, [
overflowRef,
childrenElements,
activeChildElementIndex,
scrollAnimationBehavior,
onScrollPrevious,
]);
if (onScrollPrevious) {
onScrollPrevious({
currentActiveChildElementIndex,
});
}
}, [overflowRef, activeChildElementIndex, offset, scrollAnimationBehavior, onScrollPrevious, childrenElements,
getOffset, offsetType, currentOffset, onChangeOffset]);

/**
* A helper function to scroll to the next element in the overflow container.
Expand All @@ -101,17 +95,19 @@ const useOverflowScrollActions = ({
return childrenElements[nextChildElementIndex];
};

let nextChildElement = getNextChildElement();
const nextChildElement = getNextChildElement();
let calculatedOffsetLeft = calculateOffsetLeft(nextChildElement);

if (offset) {
const maxOffset = overflowRef.scrollWidth - overflowRef.clientWidth;
const offsetValue = getOffset(offset, maxOffset);
const offsetValue = getOffset(offset, offsetType, maxOffset);
calculatedOffsetLeft = currentOffset + offsetValue;
if (calculatedOffsetLeft > maxOffset) {
onChangeOffset(maxOffset);
} else {
onChangeOffset(calculatedOffsetLeft);
if (onChangeOffset) {
if (calculatedOffsetLeft > maxOffset) {
onChangeOffset(maxOffset);
} else {
onChangeOffset(calculatedOffsetLeft);
}
}
}

Expand All @@ -120,14 +116,11 @@ const useOverflowScrollActions = ({
behavior: scrollAnimationBehavior,
});
const currentActiveChildElementIndex = isNextChildIndexAtEnd ? lastChildElementIndex : nextChildElementIndex;
onScrollNext({ currentActiveChildElementIndex });
}, [
overflowRef,
activeChildElementIndex,
scrollAnimationBehavior,
childrenElements,
onScrollNext,
]);
if (onScrollNext) {
onScrollNext({ currentActiveChildElementIndex });
}
}, [overflowRef, childrenElements, activeChildElementIndex, offset, scrollAnimationBehavior, onScrollNext,
getOffset, offsetType, currentOffset, onChangeOffset]);

return {
scrollToPrevious,
Expand Down
92 changes: 92 additions & 0 deletions src/OverflowScroll/useOverflowScroll.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,95 @@ The hook returns the following:
* `activeChildElementIndex`. The index of the child element that is currently deemed to be "active", i.e. the child element used as the reference position for any `scrollToPrevious` or `scrollToNext` calls.

See [`OverflowScroll`](/components/overflowscroll/overflowscroll) for React components that encapsulate the logic within `useOverflowScroll`.

### Use of `offset` and `offsetType`

```jsx live
() => {
const [offset, setOffset] = useState(50);
const [offsetType, setOffsetType] = useState('percentage');

const [overflowRef, setOverflowRef] = useState();

const {
isScrolledToStart,
isScrolledToEnd,
scrollToPrevious,
scrollToNext,
} = useOverflowScroll({
childQuerySelector: '.example-item',
overflowRef,
offset,
offsetType,
});

const ExampleItem = ({ className }) => (
<div
className={classNames('example-item border flex-shrink-0 text-center', className)}
style={{ width: 160 }}
>
Item
</div>
);
const itemCount = 20;
const items = useMemo(() => Array.from({ length: itemCount }).map((index) => {
if (index !== itemCount - 1) {
return <ExampleItem key={uuidv4()} className="mr-2" />;
}
// last element, no right margin
return <ExampleItem key={uuidv4()} />;
}), []);

return (
<>
{/* start example form block */}
<ExamplePropsForm
inputs={[
{
value: offset,
setValue: setOffset,
range: {
min: 0,
max: offsetType === 'percentage' ? 100 : 1000,
step: offsetType === 'percentage' ? 1 : 50,
},
name: 'offset'
},
{
value: offsetType,
setValue: setOffsetType,
options: ['percentage', 'fixed'],
name: 'offsetType'
},
]}
/>
{/* end example form block */}

<div className="mb-3">
<Button
onClick={scrollToPrevious}
disabled={isScrolledToStart}
size="sm"
className="mr-2"
>
Previous
</Button>
<Button
onClick={scrollToNext}
disabled={isScrolledToEnd}
size="sm"
>
Next
</Button>
</div>
<div
ref={setOverflowRef}
aria-label="example overflow scroll container"
className="d-flex"
>
{items}
</div>
</>
);
};
```

0 comments on commit 6f587a0

Please sign in to comment.