Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-horizontal-scrolling-menu

asmyshlyaev177163.6kMIT8.2.0TypeScript support: included

Scrolling horizontal menu component for React, support mouse and touch devices.

front-end, react, react-component, menu, navigation, gallery, horizontal, scroll, scrolling, scrolling-menu, popular

readme

For hire

React horizontal scrolling menu

example

npm NPM Downloads Codacy Badge Codacy Badge Commitizen friendly npm bundle size (minified + gzip) Donate Bitcoin

Poll what you like/dislike/need from this library

Check out my new project 👉 https://github.com/asmyshlyaev177/state-in-url

Add a ⭐️ and follow me to support the project!

Proud corner

performance-dashboard-on-aws | React status code

Storybook (Faster and more convinient, new examples will be here)

Codesandbox Examples (Deprecated)

Center items

Dynamically add items when last is visible

apiRef - controling component outside

Add item and scroll to it

Loop scroll

Custom transition/animation

Swipe on mobile devices(need to run locally, codesandbox has issues)

Previous version V1

This is a highly customizable horizontal scrolling menu component for React. Can also use it for Amazon like items block or a Gallery. Menu component is responsive, 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.

Check out examples on Storybook or codesandbox.

:star: if you like the project :)

NextJS issues

Cannot use import statement outside a module

Quick start

npm install --save react-horizontal-scrolling-menu@7.1.1 // last version has a bug

test In project:

import React from 'react';
import { ScrollMenu, VisibilityContext } from 'react-horizontal-scrolling-menu';
import '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 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 }) => (
        <Card
          itemId={id} // NOTE: itemId is required for track items
          title={id}
          key={id}
          onClick={handleClick(id)}
          selected={isItemSelected(id)}
        />
      ))}
    </ScrollMenu>
  );
}

const LeftArrow = () => {
  const visibility = React.useContext < publicApiType > VisibilityContext;
  const isFirstItemVisible = visibility.useIsVisible('first', true);
  return (
    <Arrow
      disabled={isFirstItemVisible}
      onClick={() => visibility.scrollPrev()}
      className="left"
    >
      Left
    </Arrow>
  );
};

const RightArrow = () => {
  const visibility = React.useContext < publicApiType > VisibilityContext;
  const isLastItemVisible = visibility.useIsVisible('last', false);
  return (
    <Arrow
      disabled={isLastItemVisible}
      onClick={() => visibility.scrollNext()}
      className="right"
    >
      Right
    </Arrow>
  );
};

function Card({ onClick, selected, title, itemId }) {
  const visibility = React.useContext < publicApiType > VisibilityContext;
  const visible = visibility.useIsVisible(itemId, true);

  return (
    <div
      onClick={() => onClick(visibility)}
      style={{
        width: '160px',
      }}
      tabIndex={0}
    >
      <div className="card">
        <div>{title}</div>
        <div>visible: {JSON.stringify(visible)}</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.

Example

You can clone repository and run demo project.

git clone https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu
npm run setup
npm run demo

Storybook

Can clone repo and run storybook

git clone https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu
npm run setup
npm run storybook

Helpers and api

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.

Properties and callbacks

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 | React.RefCallback
options options for IntersectionObserver - rootMargin, threshold, and ratio to consider element visible
containerRef React.RefObject | React.RefCallback
onUpdate (VisibilityContext) => void
onMouseDown (VisibilityContext) => (React.MouseEventHandler) => void
onMouseLeave (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
scrollContainerClassName ClassName of scrollContainer
transitionDuration Duration of transitions in ms, default 500
transitionBehavior 'smooth' |'auto' | customFunction
wrapperClassName ClassName of the outer-most div
RTL Enable Right to left direction
noPolyfill Don't use polyfill for scroll, no transitions, true by default

VisibilityContext

Prop Signature
useIsVisible (itemId: string, defaultValue?: false) => boolean
getItemById itemId => IOItem | undefined
getItemElementById itemId => DOM Element | null
getItemByIndex index => IOItem | undefined
getItemElementByIndex index => DOM Element | null
getNextElement () => IOItem | undefined
getPrevElement () => IOItem | undefined
isFirstItemVisible boolean
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
items ItemsMap class instance
scrollContainer Ref<OuterContainer>

items class instance

ItemsMap class store info about all items and has methods to get currently visible items, prev/next item. Also, can subscribe to updates.

Prop/method Description
subscribe subscribe for events for itemId or first, last, onInit, onUpdate, eg. items.subscribe('item5', (item) => setVisible(item.visible))
unsubscribe use in useEffect to cleanup, pass same cb instance
getVisible return only visible items
toItems return ids for all items
toArr return all items
first return first item
last return last item
prev (itemId | Item) => previous item | undefined
next (itemId | Item) => next item | undefined

Transition/Animation

NOTE: won't work with RTL prop

Can use transitionDuration, and transitionBehavior See example

ScrollOptions for scrollToItem, scrollPrev, scrollNext

Will override transition* options passed to ScrollMenu

{
  // target,
  behavior, // 'smooth', 'auto' or custom function
    // inline,
    // block,
    {
      duration: number, // number in milliseconds
    };
}

Other helpers

slidingWindow

Can get previous or next visible group of items with slidingWindow(allItems: string[], visibleItems: string[]) helper, e.g

slidingWindow(allItems, visibleItems)
.prev()
//.next()

getItemsPos

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

apiRef

Can pass Ref object to Menu, current value will assigned as VisibilityContext. But 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 support

  • Browser must support IntersectionObserver API and requestAnimationFrame or use polyfills.
  • Only modern browsers, no IE or smart toasters

About

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.

Contributing

Changelog

changelog

Changelog

All notable changes to this project will be documented in this file. See standard-version for commit guidelines.

8.2.0 (2024-10-27)

Bug Fixes

8.1.0 (2024-10-11)

⚠ BREAKING CHANGES

  • noPolyfill options is true by default now
  • Removed getPrevItem and getNextItem and code related to separators

  • chore: update eslint plugin

  • refactor(imports): fix imports with eslint

    • Removed visibleElements, isFirstItemVisible and isLastItemVisible. Can use api.useIsVisible hook, api.isItemVisible, and api.items.getVisibleElements to get visible items.
  • Removed initComplete

  • ci(lint-staged): ignore stories folder in lint-staged

  • docs(readme): update docs

  • docs(storybook): performance example

  • docs(storybook): save/restore position example

  • refactor: remove stack and flush updates logic

  • style(eslint): eslint/ts config for stories

  • ci: add webkit for storybook tests

  • Changed type of package to module, upgrade your NPM version

  • fix(ts): fix publicApiType type export

  • chore: cRA example

  • chore(update): update smooth-scroll-into-view-if-needed to v2

  • Possible new behavior, test after update

  • ci(github actions): action to publish lib to npm

  • ci(github action): refactore test/release action

  • chore(release): 5.0.0

  • styles: Need to import styles in your code - example "import 'react-horizontal-scrolling-menu/dist/styles.css'"
  • Removed Arrows prop

  • fix: removed globalThis

  • test: test for Header and Footer

  • chore: updated example and README

  • chore(release): 3.0.0

Features

  • useLeftArrowVisible and useRightArrowVisible hooks (#292) (5ae82a8)
  • apiref: apiRef object for access VisibilityContext from outside of Menu component (32012f6)
  • arrows prop for use arrows with additional content (e222381), closes #197
  • classname: className for Item, Separator and ScrollContainer (4cb0a3f)
  • containerRef prop to make it work with formkit/auto-animate (#273) (44b13b4)
  • getItemElementById and getItemElementByIndex helpers (f0a0475), closes #167
  • header and footer initial implementation (#200) (7aaac71), closes #197
  • onmouseleave: onMouseLeave prop (57ad4cb)
  • onupdate: onUpdate cb that called every time visibleItems changed (010f5ff)
  • rtl: rtl support (7d7740b)
  • slidingWindow and getItemsPos helpers (0bc0839)
  • touch events (b0baa6c)
  • transition and animations (40e9201)
  • use key prop if itemId not provided, getItemId helper (5d700f2)
  • use smooth-scroll-into-view-if-needed library as polyfill (28b2a9c), closes #174

Bug Fixes

  • change mjs to be browser and main entries (1dc5ebd)
  • changed target to es2015(es6) since some uglifiers don't support newer (a557987)
  • convert itemId to string (ede8987), closes #207
  • don't fire updates if Menu is hidden (95aa22e)
  • don't use labeled tuple for support TS < 4 (61fbe8a)
  • fix arrow status (#291) (6ad8b64)
  • fix dependencies array for transitions (5b61bb6)
  • fix if prev/next group of items in slidingWindow smaller than current one (292d581)
  • fix visibility issue (7f71b1c)
  • fixed Arrows props can't pass as component (219b58b)
  • fixed onInit cb and example (d24278b)
  • fixed situation when items added at start, need to handle when items removed (210f8d4), closes #164
  • fixed styles for wrapper (66006e3)
  • fixed useOnUpdate staled value (8a7386f)
  • getelementorconstructor: fix for React.memo elements as arrows/footer (b398ad5)
  • nextjs complaining about useLayoutEffect (#191) (bef5dca)
  • noPolyfill=true by default (dad3dab)
  • package.json: add exports.types field (#247) (2c6ef40)
  • pass arrows as FC or Element (b53a8dd), closes #152
  • remove separators too when remove items (fccd6a6), closes #171
  • removed Separator elements (#274) (c033bf9)
  • scripts: fix release script (e763c9e)
  • scroll by 1 item: fns for scroll by 1 item (bdd4e68)
  • scrolltoitem: scrollToItem accepts IOItem as an argument (f1732e2), closes #157
  • styles.css import: fixed exports field for /dist/styles.css (c7dc811), closes #231
  • styles: bundle styles to styles.css file (6b298f1), closes #227
  • ts types on same level as index, codesandbox doesn't see it otherwise (d2a7249)
  • yarn/vite: polyfill to dependencies, issues with yarn/vite (e60faf4), closes #269

8.0.2 (2024-10-07)

Bug Fixes

8.0.1 (2024-10-07)

8.0.0 (2024-10-04)

⚠ BREAKING CHANGES

  • noPolyfill options is true by default now

Bug Fixes

  • noPolyfill=true by default (ddbe715)

7.1.8 (2024-09-19)

7.1.7 (2024-08-28)

7.1.5 (2024-08-28)

7.1.4 (2024-08-27)

Bug Fixes

7.1.3 (2024-08-21)

7.1.2 (2024-08-19)

Bug Fixes

  • don't fire updates if Menu is hidden (8aae6ef)

7.1.1 (2024-06-13)

7.1.0 (2024-05-28)

Features

  • onmouseleave: onMouseLeave prop (661ba27)

7.0.0 (2024-03-26)

⚠ BREAKING CHANGES

  • Removed getPrevItem and getNextItem and code related to separators

  • chore: update eslint plugin

  • refactor(imports): fix imports with eslint

Bug Fixes

6.1.0 (2024-03-23)

Features

  • containerRef prop to make it work with formkit/auto-animate (#273) (d7ac7c3)

6.0.2 (2024-02-21)

6.0.1 (2024-02-20)

Bug Fixes

  • fix dependencies array for transitions (d066024)

6.0.0 (2024-02-20)

⚠ BREAKING CHANGES

    • Removed visibleElements, isFirstItemVisible and isLastItemVisible. Can use api.useIsVisible hook, api.isItemVisible, and api.items.getVisibleElements to get visible items.
  • Removed initComplete

  • ci(lint-staged): ignore stories folder in lint-staged

  • docs(readme): update docs

  • docs(storybook): performance example

  • docs(storybook): save/restore position example

  • refactor: remove stack and flush updates logic

  • style(eslint): eslint/ts config for stories

  • ci: add webkit for storybook tests

  • Observer pattern (#270) (d95befc), closes #270

5.0.2 (2024-02-13)

  • getelementorconstructor: fix for React.memo elements as arrows/footer (e5b3dc4)
  • scripts: fix release script (0e3a4bb)

5.0.1 (2024-02-09)

Bug Fixes

  • yarn/vite: polyfill to dependencies, issues with yarn/vite (8a746f0), closes #269

5.0.0 (2024-02-05)

⚠ BREAKING CHANGES

  • Changed type of package to module, upgrade your NPM version

  • fix(ts): fix publicApiType type export

  • chore: cRA example

  • chore(update): update smooth-scroll-into-view-if-needed to v2

  • Possible new behavior, test after update

  • ci(github actions): action to publish lib to npm

  • ci(github action): refactore test/release action

  • chore(release): 5.0.0

Bug Fixes

  • yarn/vite: polyfill to dependencies, issues with yarn/vite (8a746f0), closes #269

  • Module (#266) (2498835), closes #266

5.0.0-beta.3 (2024-02-04)

5.0.0-beta.2 (2024-02-04)

⚠ BREAKING CHANGES

  • update: Possible new behavior, test after update

  • update: update smooth-scroll-into-view-if-needed to v2 (b30cf83)

5.0.0-beta.1 (2024-02-04)

⚠ BREAKING CHANGES

  • package.json: Changed type of package to module, upgrade your NPM version

Bug Fixes

  • package.json: type module (242eb69)
  • ts: fix publicApiType type export (90f99e7)

4.1.4 (2024-01-29)

4.1.3 (2024-01-29)

4.1.2 (2024-01-11)

4.1.1 (2023-10-04)

Bug Fixes

  • package.json: add exports.types field (#247) (980775a)

4.1.0 (2023-06-11)

Features

4.0.4 (2023-04-14)

4.0.4-beta.1 (2023-04-14)

4.0.3 (2023-04-11)

Bug Fixes

  • removed copy package.json (ce9699b)

4.0.2 (2023-04-11)

4.0.1 (2023-03-21)

⚠ BREAKING CHANGES

  • styles: Need to import styles in your code - example "import 'react-horizontal-scrolling-menu/dist/styles.css'"

Bug Fixes

  • styles.css import: fixed exports field for /dist/styles.css (2c47f89), closes #231
  • styles: bundle styles to styles.css file (142dde6), closes #227

4.0.0 (2023-03-12)

⚠ BREAKING CHANGES

  • styles: Need to import styles in your code - example "import 'react-horizontal-scrolling-menu/dist/styles.css'"

227

Bug Fixes

  • styles: bundle styles to styles.css file (314b894)

3.2.5 (2023-02-19)

3.2.4 (2023-02-19)

3.2.4-0 (2023-02-19)

3.2.3 (2022-10-30)

Bug Fixes

  • scroll by 1 item: fns for scroll by 1 item (679914c)

3.2.1 (2022-10-23)

3.2.0 (2022-08-07)

Features

3.1.1 (2022-07-23)

Bug Fixes

3.1.0 (2022-06-26)

Features

  • use key prop if itemId not provided, getItemId helper (29967d2)

3.0.1 (2022-06-19)

Bug Fixes

  • fixed styles for wrapper (5755c1d)

3.0.0 (2022-06-19)

⚠ BREAKING CHANGES

  • Removed Arrows prop

Features

  • header and footer initial implementation (7d18fd4), closes #197

Bug Fixes

  • removed globalThis (5d3d82e)

  • test for header and footer, removed arrows (4ae3014)

2.8.2 (2022-05-17)

2.8.1 (2022-05-17)

Bug Fixes

  • fixed Arrows props can't pass as component (4a24803)

2.8.0 (2022-05-17)

Features

  • arrows prop for use arrows with additional content (7d53430), closes #197

Bug Fixes

  • changed target to es2015(es6) since some uglifiers don't support newer (a6b42e5)

2.7.2 (2022-05-04)

Bug Fixes

  • changed target to es2015(es6) since some uglifiers don't support newer (b92f230), closes #195

2.7.1 (2022-02-23)

Bug Fixes

  • nextjs complaining about useLayoutEffect (#191) (b07a2b8)

2.7.0 (2021-11-28)

Features

  • transition and animations (0e9b5b9)

2.6.1 (2021-11-21)

Bug Fixes

  • fixed useOnUpdate staled value (0c9a77b)

2.6.0 (2021-11-21)

Features

  • use smooth-scroll-into-view-if-needed library as polyfill (c1911ab), closes #174

2.5.2 (2021-11-10)

Bug Fixes

  • remove separators too when remove items (079f2c5), closes #171

2.5.1 (2021-11-03)

Bug Fixes

  • don't use labeled tuple for support TS < 4 (05e67ea)

2.5.0 (2021-10-16)

Features

  • getItemElementById and getItemElementByIndex helpers (5bdc115), closes #167

2.4.4 (2021-10-14)

2.4.3 (2021-10-13)

2.4.2 (2021-10-12)

Bug Fixes

  • fixed situation when items added at start, need to handle when items removed (83a484e), closes #164

2.4.1 (2021-10-08)

2.4.0 (2021-10-06)

Features

  • apiref: apiRef object for access VisibilityContext from outside of Menu component (ef9a281)

2.3.3 (2021-09-26)

2.3.2 (2021-09-24)

2.3.1 (2021-09-24)

Bug Fixes

  • ts types on same level as index, codesandbox doesn't see it otherwise (27cc26f)

2.3.0 (2021-09-24)

Features

  • onupdate: onUpdate cb that called every time visibleItems changed (f4f5dd5)

2.2.1 (2021-09-23)

2.2.0 (2021-09-20)

Features

  • classname: className for Item, Separator and ScrollContainer (0e925a2)

2.1.1 (2021-09-14)

Bug Fixes

  • fix if prev/next group of items in slidingWindow smaller than current one (93c43c5)

2.1.0 (2021-09-13)

Features

  • slidingWindow and getItemsPos helpers (eeae101)

2.0.10 (2021-09-11)

Bug Fixes

  • scrolltoitem: scrollToItem accepts IOItem as an argument (a5f5011), closes #157

2.0.9 (2021-09-02)

2.0.8 (2021-09-02)

Bug Fixes

  • fixed onInit cb and example (2955f42)

2.0.7 (2021-08-26)

Bug Fixes

2.0.6 (2021-08-13)

2.0.5 (2021-08-13)