Fork of React horizontal scrolling menu
performance-dashboard-on-aws | React status code
Hidden scrollbar and arrows on bottom
Click and select multiple items
Dynamically add items when last is visible
apiRef - controling component outside
Swipe on mobile devices(need to run locally, codesandbox has issues)
Previous version V1
This is a horizontal scrolling menu component for React. Menu component has adaptive width, just set width for parent container. Items width will be determined from CSS styles.
For navigation, you can use scrollbar, native touch scroll, mouse wheel or drag by mouse.
Component provide context with visible items and helpers.
Possible set default position on initialization.
⭐ if you like the project :)
Cannot use import statement outside a module_
npm install @ramirezcgn/react-horizontal-scrolling-menu
In project:
import React from 'react';
import { ScrollMenu, VisibilityContext } from '@ramirezcgn/react-horizontal-scrolling-menu';
import '@ramirezcgn/react-horizontal-scrolling-menu/dist/styles.css';
const getItems = () =>
Array(20)
.fill(0)
.map((_, ind) => ({ id: `element-${ind}` }));
function App() {
const [items, setItems] = React.useState(getItems);
const [selected, setSelected] = React.useState([]);
const [position, setPosition] = React.useState(0);
const isItemSelected = (id) => !!selected.find((el) => el === id);
const handleClick =
(id) =>
({ getItemById, scrollToItem }) => {
const itemSelected = isItemSelected(id);
setSelected((currentSelected) =>
itemSelected
? currentSelected.filter((el) => el !== id)
: currentSelected.concat(id)
);
};
return (
<ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
{items.map(({ id, i }) => (
<Card
itemId={id} // NOTE: itemId is required for track items
itemClassName={ i % 2 ? 'odd' : 'even'} // Optional custom class for item container element
title={id}
key={id}
onClick={handleClick(id)}
selected={isItemSelected(id)}
/>
))}
</ScrollMenu>
);
}
function LeftArrow() {
const { isFirstItemVisible, scrollPrev } =
React.useContext(VisibilityContext);
return (
<Arrow disabled={isFirstItemVisible} onClick={() => scrollPrev()}>
Left
</Arrow>
);
}
function RightArrow() {
const { isLastItemVisible, scrollNext } = React.useContext(VisibilityContext);
return (
<Arrow disabled={isLastItemVisible} onClick={() => scrollNext()}>
Right
</Arrow>
);
}
function Card({ onClick, selected, title, itemId }) {
const visibility = React.useContext(VisibilityContext);
return (
<div
onClick={() => onClick(visibility)}
style={{
width: '160px',
}}
tabIndex={0}
>
<div className="card">
<div>{title}</div>
<div>visible: {JSON.stringify(!!visibility.isItemVisible(itemId))}</div>
<div>selected: {JSON.stringify(!!selected)}</div>
</div>
<div
style={{
height: '200px',
}}
/>
</div>
);
}
export default App;
Check out Example in example-nextjs
folder for info how to implement more features like mouse drag or disable body scroll.
You can clone repository and run demo project.
git clone https://github.com/ramirezcgn/react-horizontal-scrolling-menu
yarn install
yarn run demo
Children of main ScrollMenu component(arrows, fotter, items) can use VisibilityContext to access state and callbacks.
Function callbacks also pass context, eg onWheel
, onScroll
etc.
Prop | Signature |
---|---|
LeftArrow | React component for left arrow |
RightArrow | React component for right arrow |
Header | React component Header |
Footer | React component Footer |
onWheel | (VisibilityContext, event) => void |
onScroll | (VisibilityContext, event) => void, will fire before scroll |
onInit | (VisibilityContext) => void |
apiRef | React.RefObject |
onUpdate | (VisibilityContext) => void |
onMouseDown | (VisibilityContext) => (React.MouseEventHandler) => void |
onMouseUp | (VisibilityContext) => (React.MouseEventHandler) => void |
onMouseMove | (VisibilityContext) => (React.MouseEventHandler) => void |
onTouchMove | (VisibilityContext) => (React.TouchEventHandler) => void |
onTouchStart | (VisibilityContext) => (React.TouchEventHandler) => void |
onTouchEnd | (VisibilityContext) => (React.TouchEventHandler) => void |
itemClassName | ClassName of Item |
separatorClassName | ClassName of Item's separator |
scrollContainerClassName | ClassName of scrollContainer |
transitionDuration | Duration of transitions in ms, default 500 |
transitionBehavior | 'smooth' |'auto' | customFunction |
transitionEase | Ease function, eg t => t*(2-t) |
wrapperClassName | ClassName of the outer-most div |
RTL | Enable Right to left direction |
noPolyfill | Don't use polyfill for scroll, no transitions |
Prop | Signature |
---|---|
getItemById | itemId => IOItem | undefined |
getItemElementById | itemId => DOM Element | null |
getItemByIndex | index => IOItem | undefined |
getItemElementByIndex | index => DOM Element | null |
getNextElement (use this first, result without separators) | () => IOItem | undefined |
getNextItem | () => IOItem | undefined) |
getPrevElement (use this first, result without separators) | () => IOItem | undefined |
getPrevItem | () => IOItem | undefined |
initComplete | boolean |
isFirstItemVisible | boolean |
isItemVisible | itemId => boolean |
isLastItem | boolean |
isLastItemVisible | boolean |
scrollNext | (behavior, inline, block, ScrollOptions) => void |
scrollPrev | (behavior, inline, block, ScrollOptions) => void |
scrollToItem | (item, behavior, inline, block, ScrollOptions) => void |
initComplete | boolean |
items | ItemsMap class instance |
scrollContainer | Ref |
visibleElements | ['item1', 'item2'] |
visibleElementsWithSeparators | ['item1', 'item1-separator', 'item2'] |
visibleItemsWithoutSeparators (deprecated, use visibleElements) | ['item1', 'item2'] |
visibleItems (deprecated, use visibleElementsWithSeparators) | ['item1', 'item1-separator', 'item2'] |
NOTE: won't work with RTL prop
Can use transitionDuration
, transitionEase
and transitionBehavior
See example
Will override transition* options passed to ScrollMenu
{
// target,
behavior, // 'smooth', 'auto' or custom function
// inline,
// block,
{
duration: number, // number in milliseconds
ease: (t) => t, // ease function, more https://gist.github.com/gre/1650294#file-easing-js
};
}
Can get previous or next visible group of items with slidingWindow(allItems: string[], visibleItems: string[])
helper, e.g
slidingWindow(allItems, visibleItems)
.prev()
//.next()
Can get first, center and last items, e.g.
const prevGroup = slidingWindow(allItems, visibleItems).prev()
const { first, center: centerItem, last } = getItemsPos(prevGroup)
// and scroll to center item of previous group of items
scrollToItem(getItemById(centerItem, 'smooth', 'center'))
Check out examples
Can pass Ref object to Menu, current value will assigned as VisibilityContext. But visibleItems
and some other values can be staled, so better use it only for firing functions like scrollToItem
.
For scrolling use apiRef.scrollToItem(apiRef.getItemElementById)
instead of apiRef.scrollToItem(apiRef.getItemById)
.
Can get item outside of context via apiRef.getItemElementById(id)
or directly via document.querySelector(`[data-key='${itemId}']`)
.
See apiRef
example and Add item and scroll to it
- Browser must support IntersectionObserver API and requestAnimationFrame or use polyfills.
- Only modern browsers, no IE or smart toasters
My first npm project. Sorry for my english.
Any contribution and correction appreciated. Just fork repo, commit and make PR, don't forget about tests.