{"id":17621124,"url":"https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu","last_synced_at":"2025-05-14T04:07:37.514Z","repository":{"id":32747341,"uuid":"140855557","full_name":"asmyshlyaev177/react-horizontal-scrolling-menu","owner":"asmyshlyaev177","description":"Horizontal scrolling menu component for React. ","archived":false,"fork":false,"pushed_at":"2025-02-02T13:04:45.000Z","size":27714,"stargazers_count":776,"open_issues_count":2,"forks_count":100,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-10T22:35:04.809Z","etag":null,"topics":["gallery","javascript","navigation","popular","react","react-component","reactjs","scroll","scrolling","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-horizontal-scrolling-menu","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/asmyshlyaev177.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["asmyshlyaev177"],"patreon":"asmyshlyaev177"}},"created_at":"2018-07-13T14:17:33.000Z","updated_at":"2025-04-02T07:01:39.000Z","dependencies_parsed_at":"2024-01-14T16:25:16.609Z","dependency_job_id":"5893b95c-71e3-40ac-94ae-203539374f82","html_url":"https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu","commit_stats":{"total_commits":474,"total_committers":2,"mean_commits":237.0,"dds":0.008438818565400852,"last_synced_commit":"2dccc7a5daf639c6d38b5c5bf0ba048150ab5743"},"previous_names":[],"tags_count":123,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmyshlyaev177%2Freact-horizontal-scrolling-menu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmyshlyaev177%2Freact-horizontal-scrolling-menu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmyshlyaev177%2Freact-horizontal-scrolling-menu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmyshlyaev177%2Freact-horizontal-scrolling-menu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/asmyshlyaev177","download_url":"https://codeload.github.com/asmyshlyaev177/react-horizontal-scrolling-menu/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254069217,"owners_count":22009511,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gallery","javascript","navigation","popular","react","react-component","reactjs","scroll","scrolling","typescript"],"created_at":"2024-10-22T20:10:55.163Z","updated_at":"2025-05-14T04:07:37.473Z","avatar_url":"https://github.com/asmyshlyaev177.png","language":"TypeScript","funding_links":["https://github.com/sponsors/asmyshlyaev177","https://patreon.com/asmyshlyaev177"],"categories":[],"sub_categories":[],"readme":"[![For hire](/hireBadge.svg)](https://www.linkedin.com/in/asmyshlyaev177/)\n\n# React horizontal scrolling menu\n\n![example](/sample.gif)\n\n[![npm](https://img.shields.io/npm/v/react-horizontal-scrolling-menu.svg)](https://www.npmjs.com/package/react-horizontal-scrolling-menu)\n![NPM Downloads](https://img.shields.io/npm/dm/react-horizontal-scrolling-menu)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/433d9b4a8a374109a9f96b8faf3c175d)](https://www.codacy.com/gh/asmyshlyaev177/react-horizontal-scrolling-menu/dashboard?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=asmyshlyaev177/react-horizontal-scrolling-menu\u0026utm_campaign=Badge_Grade)\n[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/433d9b4a8a374109a9f96b8faf3c175d)](https://www.codacy.com/gh/asmyshlyaev177/react-horizontal-scrolling-menu/dashboard?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=asmyshlyaev177/react-horizontal-scrolling-menu\u0026utm_campaign=Badge_Coverage)\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/)\n![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/react-horizontal-scrolling-menu.svg)\n[![Donate Bitcoin](https://img.shields.io/badge/donate-$5-orange.svg)](https://asmyshlyaev177.github.io/donate-bitcoin?amount=5\u0026currency=USD)\n\n\n#### [Poll what you like/dislike/need from this library](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/discussions/221)\n\n### Check out my new project 👉 https://github.com/asmyshlyaev177/state-in-url\nAdd a ⭐️ and \u003ca href=\"https://github.com/asmyshlyaev177\" target=\"_blank\"\u003efollow me\u003c/a\u003e to support the project!\n\n### Proud corner\n\n[performance-dashboard-on-aws\n](https://github.com/awslabs/performance-dashboard-on-aws/blob/49ce2517a29569a9761dec8f212f25cf85a394af/frontend/src/components/Tabs.tsx#L3) |\n[React status code](https://react.statuscode.com/issues/257)\n\n# Check out examples for common cases\n### [Storybook](https://asmyshlyaev177.github.io/react-horizontal-scrolling-menu) (Faster and more convinient, new examples will be here)\n\n### Codesandbox Examples (Deprecated)\n\u003cdetails\u003e\n  \u003csummary\u003eCodesandbox examples\u003c/summary\u003e\n  \n[Center items](https://codesandbox.io/s/center-items-on-click-drag-e8cyph?file=/src/index.tsx)\n\n[Dynamically add items when last is visible](https://codesandbox.io/s/react-horizontal-scrolling-menu-v2-dynamically-add-items-38ted?file=/src/index.tsx)\n\n[apiRef - controling component outside](https://codesandbox.io/s/react-horizontal-scrolling-menu-v2-apiref-vdr0d?file=/src/index.tsx)\n\n[Add item and scroll to it](https://codesandbox.io/s/basic-example-forked-3j0xm?file=/src/index.tsx)\n\n[Loop scroll](https://codesandbox.io/s/loop-scroll-4w8ek6?file=/src/index.tsx)\n\n[Custom transition/animation](https://codesandbox.io/p/sandbox/custom-transition-animation-3h4d2y?file=%2Fsrc%2Findex.tsx)\n\n[Swipe on mobile devices(need to run locally, codesandbox has issues)](https://codesandbox.io/s/swipe-on-mobile-qmgqtj)\n  \n\u003c/details\u003e\n\n\n### Previous version [V1](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/tree/v1)\n\nThis is a highly customizable horizontal scrolling menu component for React. Can also use it for Amazon like items block or a Gallery.\nMenu component is responsive, just set width for parent container.\nItems width will be determined from CSS styles.\n\nFor navigation, you can use scrollbar, native touch scroll, mouse wheel or drag by mouse.\n\nComponent provide context with visible items and helpers.\n\nPossible set default position on initialization.\n\nCheck out examples on [Storybook](https://asmyshlyaev177.github.io/react-horizontal-scrolling-menu) or codesandbox.\n\n:star: if you like the project :)\n\n### NextJS issues\n\n[Cannot use import statement outside a module](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/issues/240)\n\n## Quick start\n\n```bash\nnpm install --save react-horizontal-scrolling-menu\n```\ntest\nIn project:\n\n```javascript\nimport React from 'react';\nimport { ScrollMenu, VisibilityContext } from 'react-horizontal-scrolling-menu';\nimport 'react-horizontal-scrolling-menu/dist/styles.css';\n\nconst getItems = () =\u003e\n  Array(20)\n    .fill(0)\n    .map((_, ind) =\u003e ({ id: `element-${ind}` }));\n\nfunction App() {\n  const [items, setItems] = React.useState(getItems);\n  const [selected, setSelected] = React.useState([]);\n\n  const isItemSelected = (id) =\u003e !!selected.find((el) =\u003e el === id);\n\n  const handleClick =\n    (id) =\u003e\n    ({ getItemById, scrollToItem }) =\u003e {\n      const itemSelected = isItemSelected(id);\n\n      setSelected((currentSelected) =\u003e\n        itemSelected\n          ? currentSelected.filter((el) =\u003e el !== id)\n          : currentSelected.concat(id),\n      );\n    };\n\n  return (\n    \u003cScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}\u003e\n      {items.map(({ id }) =\u003e (\n        \u003cCard\n          itemId={id} // NOTE: itemId is required for track items\n          title={id}\n          key={id}\n          onClick={handleClick(id)}\n          selected={isItemSelected(id)}\n        /\u003e\n      ))}\n    \u003c/ScrollMenu\u003e\n  );\n}\n\nconst LeftArrow = () =\u003e {\n  const visibility = React.useContext \u003c publicApiType \u003e VisibilityContext;\n  const isFirstItemVisible = visibility.useIsVisible('first', true);\n  return (\n    \u003cArrow\n      disabled={isFirstItemVisible}\n      onClick={() =\u003e visibility.scrollPrev()}\n      className=\"left\"\n    \u003e\n      Left\n    \u003c/Arrow\u003e\n  );\n};\n\nconst RightArrow = () =\u003e {\n  const visibility = React.useContext \u003c publicApiType \u003e VisibilityContext;\n  const isLastItemVisible = visibility.useIsVisible('last', false);\n  return (\n    \u003cArrow\n      disabled={isLastItemVisible}\n      onClick={() =\u003e visibility.scrollNext()}\n      className=\"right\"\n    \u003e\n      Right\n    \u003c/Arrow\u003e\n  );\n};\n\nfunction Card({ onClick, selected, title, itemId }) {\n  const visibility = React.useContext \u003c publicApiType \u003e VisibilityContext;\n  const visible = visibility.useIsVisible(itemId, true);\n\n  return (\n    \u003cdiv\n      onClick={() =\u003e onClick(visibility)}\n      style={{\n        width: '160px',\n      }}\n      tabIndex={0}\n    \u003e\n      \u003cdiv className=\"card\"\u003e\n        \u003cdiv\u003e{title}\u003c/div\u003e\n        \u003cdiv\u003evisible: {JSON.stringify(visible)}\u003c/div\u003e\n        \u003cdiv\u003eselected: {JSON.stringify(!!selected)}\u003c/div\u003e\n      \u003c/div\u003e\n      \u003cdiv\n        style={{\n          height: '200px',\n        }}\n      /\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default App;\n```\n\nCheck out Example in `example-nextjs` folder for info how to implement more features like mouse drag or disable body scroll.\n\n## Example\n\nYou can clone repository and run demo project.\n\n```bash\ngit clone https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu\nnpm run setup\nnpm run demo\n```\n\n## Storybook\n\nCan clone repo and run storybook\n\n```bash\ngit clone https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu\nnpm run setup\nnpm run storybook\n```\n\n\u003c!-- DOCS_START --\u003e\n### Helpers and api\n\nChildren of main ScrollMenu component(arrows, fotter, items) can use **VisibilityContext** to access state and callbacks.\nFunction callbacks also pass context, eg `onWheel`, `onScroll` etc.\n\n## Properties and callbacks\n\n| Prop                     | Signature                                                                                              |\n| ------------------------ | ------------------------------------------------------------------------------------------------------ |\n| LeftArrow                | React component for left arrow                                                                         |\n| RightArrow               | React component for right arrow                                                                        |\n| Header                   | React component Header                                                                                 |\n| Footer                   | React component Footer                                                                                 |\n| onWheel                  | (VisibilityContext, event) =\u003e void                                                                     |\n| onScroll                 | (VisibilityContext, event) =\u003e void, will fire _before_ scroll                                          |\n| onInit                   | (VisibilityContext) =\u003e void                                                                            |\n| apiRef                   | React.RefObject \\| React.RefCallback                                                                   |\n| options                  | options for IntersectionObserver - `rootMargin`, `threshold`, and `ratio` to consider element visible |\n| containerRef             | React.RefObject \\| React.RefCallback                                                                   |\n| onUpdate                 | (VisibilityContext) =\u003e void                                                                            |\n| onMouseDown              | (VisibilityContext) =\u003e (React.MouseEventHandler) =\u003e void                                               |\n| onMouseLeave             | (VisibilityContext) =\u003e (React.MouseEventHandler) =\u003e void                                               |\n| onMouseUp                | (VisibilityContext) =\u003e (React.MouseEventHandler) =\u003e void                                               |\n| onMouseMove              | (VisibilityContext) =\u003e (React.MouseEventHandler) =\u003e void                                               |\n| onTouchMove              | (VisibilityContext) =\u003e (React.TouchEventHandler) =\u003e void                                               |\n| onTouchStart             | (VisibilityContext) =\u003e (React.TouchEventHandler) =\u003e void                                               |\n| onTouchEnd               | (VisibilityContext) =\u003e (React.TouchEventHandler) =\u003e void                                               |\n| itemClassName            | ClassName of Item                                                                                      |\n| scrollContainerClassName | ClassName of scrollContainer                                                                           |\n| transitionDuration       | Duration of transitions in ms, default `500`                                                           |\n| transitionBehavior       | 'smooth' \\|'auto' \\| customFunction                                                                    |\n| wrapperClassName         | ClassName of the outer-most div                                                                        |\n| RTL                      | Enable Right to left direction                                                                         |\n| noPolyfill               | Don't use polyfill for scroll, no transitions, `true` by default                                       |\n\n### VisibilityContext\n\n| Prop                  | Signature                                              |\n| --------------------- | ------------------------------------------------------ |\n| useIsVisible          | (itemId: string, defaultValue?: false) =\u003e boolean      |\n| getItemById           | itemId =\u003e IOItem \\| undefined                          |\n| getItemElementById    | itemId =\u003e DOM Element \\| null                          |\n| getItemByIndex        | index =\u003e IOItem \\| undefined                           |\n| getItemElementByIndex | index =\u003e DOM Element \\| null                           |\n| getNextElement        | () =\u003e IOItem \\| undefined                              |\n| getPrevElement        | () =\u003e IOItem \\| undefined                              |\n| isFirstItemVisible    | boolean                                                |\n| isItemVisible         | itemId =\u003e boolean                                      |\n| isLastItem            | boolean                                                |\n| isLastItemVisible     | boolean                                                |\n| menuVisible           | { current: boolean }\n| scrollNext            | (behavior, inline, block, ScrollOptions) =\u003e void       |\n| scrollPrev            | (behavior, inline, block, ScrollOptions) =\u003e void       |\n| scrollToItem          | (item, behavior, inline, block, ScrollOptions) =\u003e void |\n| items                 | ItemsMap class instance                                |\n| scrollContainer       | Ref\u003cOuterContainer\u003e                                    |\n\n### items class instance\n\nItemsMap class store info about all items and has methods to get currently visible items, prev/next item. Also, can subscribe to updates.\n\n| Prop/method | Description                                                                                                                                    |\n| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n| subscribe   | subscribe for events for `itemId` or `first`, `last`, `onInit`, `onUpdate`, eg. `items.subscribe('item5', (item) =\u003e setVisible(item.visible))` |\n| unsubscribe | use in useEffect to cleanup, pass same cb instance                                                                                             |\n| getVisible  | return only visible items                                                                                                                      |\n| toItems     | return ids for all items                                                                                                                       |\n| toArr       | return all items                                                                                                                               |\n| first       | return first item                                                                                                                              |\n| last        | return last item                                                                                                                               |\n| prev        | (itemId \\| Item) =\u003e previous item \\| undefined                                                                                                 |\n| next        | (itemId \\| Item) =\u003e next item \\| undefined                                                                                                     |\n\n### Transition/Animation\n\nNOTE: won't work with RTL prop\n\nCan use `transitionDuration`, and `transitionBehavior`\nSee [example](https://codesandbox.io/p/sandbox/custom-transition-animation-3h4d2y?file=%2Fsrc%2Findex.tsx)\n\n#### ScrollOptions for scrollToItem, scrollPrev, scrollNext\n\nWill override transition\\* options passed to ScrollMenu\n\n```javascript\n{\n  // target,\n  behavior, // 'smooth', 'auto' or custom function\n    // inline,\n    // block,\n    {\n      duration: number, // number in milliseconds\n    };\n}\n```\n\n### Other helpers\n\n#### slidingWindow\n\nCan get previous or next visible group of items with `slidingWindow(allItems: string[], visibleItems: string[])` helper, e.g\n\n```Javascript\nslidingWindow(allItems, visibleItems)\n.prev()\n//.next()\n```\n\n#### getItemsPos\n\nCan get first, center and last items, e.g.\n\n```Javascript\nconst prevGroup = slidingWindow(allItems, visibleItems).prev()\nconst { first, center: centerItem, last } = getItemsPos(prevGroup)\n\n// and scroll to center item of previous group of items\nscrollToItem(getItemById(centerItem, 'smooth', 'center'))\n\n```\n\nCheck out [examples](#examples)\n\n### apiRef\n\nCan 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`.\n\nFor scrolling use `apiRef.scrollToItem(apiRef.getItemElementById)` instead of `apiRef.scrollToItem(apiRef.getItemById)`.\n\nCan get item outside of context via `apiRef.getItemElementById(id)` or directly via ``document.querySelector(`[data-key='${itemId}']`)``.\nSee [`apiRef` example and `Add item and scroll to it`](#examples)\n\n\u003c!-- DOCS_END --\u003e\n\n## Browser support\n\n- Browser must support **IntersectionObserver API** and **requestAnimationFrame** or use polyfills.\n- Only modern browsers, no IE or smart toasters\n\n## About\n\nMy first npm project. Sorry for my english.\n\nAny contribution and correction appreciated. Just fork repo, commit and make PR, don't forget about tests.\n\n## [Contributing](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/blob/main/CONTRIBUTING.md)\n\n## [Changelog](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/blob/main/CHANGELOG.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasmyshlyaev177%2Freact-horizontal-scrolling-menu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasmyshlyaev177%2Freact-horizontal-scrolling-menu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasmyshlyaev177%2Freact-horizontal-scrolling-menu/lists"}