npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-snaplist-carousel

v4.6.0

Published

A light, pure React, no dependencies and flexible carousel. A modern way to do a classic thing.

Downloads

5,249

Readme

react-snaplist-carousel

A modern way to do a classic thing.

  • Less than 3K gzipped size.
  • Made 100% in React, no porting.
  • No dependencies.
  • Typescript ready.
  • Using it in production.
  • Using native browser snap option.
  • No magic, you get the control thanks to the hooks.

Demo

Install

npm install --save react-snaplist-carousel
import {
  SnapList,
  SnapItem,
  useVisibleElements,
  useScroll,
} from 'react-snaplist-carousel';

Basic Example

import * as React from 'react';

import { SnapList, SnapItem } from 'react-snaplist-carousel';

const MyItem = ({ children }) => (
  <div style={{ width: '70vw', height: 200, background: '#cccccc' }}>
    {children}
  </div>
);

export const App = () => (
  <SnapList>
    <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
      <MyItem>Item 0</MyItem>
    </SnapItem>
    <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
      <MyItem>Item 1</MyItem>
    </SnapItem>
    <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
      <MyItem>Item 2</MyItem>
    </SnapItem>
    <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
      <MyItem>Item 3</MyItem>
    </SnapItem>
    <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
      <MyItem>Item 4</MyItem>
    </SnapItem>
  </SnapList>
);

A11y Example

import * as React from 'react';

import { SnapList, SnapItem } from 'react-snaplist-carousel';

const MyItem = React.forwardRef(({ children, ...props }, ref) => (
  <div
    style={{ width: '70vw', height: 200, background: '#cccccc' }}
    ref={ref}
    {...props}
  >
    {children}
  </div>
));

export const App = () => {
  const snapList = useRef(null);
  const lastSnapItem = useRef(null);
  const goToSnapItem = useScroll({ ref: snapList });
  return (
    <SnapList
      ref={snapList}
      tabIndex={0} // so it can receive focus and can be scrolled with keyboard
      role="region" // context for screen readers
      aria-label="my awesome snaplist" // for screen readers to read out loud on focus
    >
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem>Item 0</MyItem>
        <button
          onClick={() => {
            goToSnapItem(4);
            lastSnapItem.current?.focus();
          }}
        >
          go to last item
        </button>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem>Item 1</MyItem>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem>Item 2</MyItem>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem>Item 3</MyItem>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem ref={lastSnapItem} tabIndex={-1}>
          Item 4
        </MyItem>
      </SnapItem>
    </SnapList>
  );
};

Advanced Example

import React, { useRef } from 'react';

import {
  SnapList,
  SnapItem,
  useVisibleElements,
  useScroll,
  useDragToScroll,
  isTouchDevice,
} from 'react-snaplist-carousel';

const MyItem = ({ onClick, children, visible }) => (
  <div
    style={{
      width: '60vw',
      height: 200,
      background: visible ? '#bce6fe' : '#cccccc',
      cursor: visible ? 'default' : 'pointer',
    }}
    onClick={onClick}
  >
    {children}
  </div>
);

export const App = () => {
  const snapList = useRef(null);

  const visible = useVisibleElements(
    { debounce: 10, ref: snapList },
    ([element]) => element,
  );
  const goToSnapItem = useScroll({ ref: snapList });
  const { isDragging } = useDragToScroll({ ref: snapList });

  return (
    <SnapList ref={snapList}>
      <SnapItem margin={{ left: '20vw', right: '15px' }} snapAlign="center">
        <MyItem onClick={() => goToSnapItem(0)} visible={visible === 0}>
          Item 0
        </MyItem>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem onClick={() => goToSnapItem(1)} visible={visible === 1}>
          Item 1
        </MyItem>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem onClick={() => goToSnapItem(2)} visible={visible === 2}>
          Item 2
        </MyItem>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '15px' }} snapAlign="center">
        <MyItem onClick={() => goToSnapItem(3)} visible={visible === 3}>
          Item 3
        </MyItem>
      </SnapItem>
      <SnapItem margin={{ left: '15px', right: '20vw' }} snapAlign="center">
        <MyItem onClick={() => goToSnapItem(4)} visible={visible === 4}>
          Item 4
        </MyItem>
      </SnapItem>
    </SnapList>
  );
};

Options

SnapList

  • direction { horizontal | vertical }: Scroll direction. *
  • disableScroll { boolean | undefined }: Disable the native scroll on swipe or mouse wheel.
  • snapType { mandatory | proximity }. Optional (default mandatory): Change how items are snapped inside the container.
  • width { string | undefined }: Width CSS property
  • height { string | undefined }: Height CSS property
  • scrollPadding { { top?: string; right?: string; bottom?: string; left?: string; } | undefined }: Use this to configure the space to see from the previous/next hidden element. See scroll-padding for more information
  • hideScrollbar { boolean }. Optional (default true): Hide/show scrollbars.
  • ref { React.RefObject<HTMLDivElement> | undefined }: The React.ref to the element required by the hooks.
  • className { string | undefined }: 🚑Please, use this only in case of emergency. It allows you to add/overwrite/extend all the CSS properties. If you need this, please consider opening an issue or contribute with a PR to cover your use case.

* Required fields

SnapItem

  • snapAlign { start | center | end | none }: The box’s snap position when the scroll stops. See scroll-snap-align for more information *
  • disableScroll { boolean | undefined }: Avoid the scroll to "pass over" possible snap positions. See scroll-snap-stop for more information
  • width { string | undefined }: Width CSS property
  • height { string | undefined }: Height CSS property
  • margin { { top?: string; right?: string; bottom?: string; left?: string; } | undefined }: The margin is used to set the separation between the items. You can use different margin for the first and last item to get better results.
  • className { string | undefined }: 🚑Please, use this only in case of emergency. It allows you to add/overwrite/extend all the CSS properties. If you need this, please consider opening an issue or contribute with a PR to cover your use case.

* Required fields

useScroll

const snapList = useRef(null);
const goToElement = useScroll({ ref: snapList });

return (
  <SnapList ref={snapList}>
    <SnapItem snapAlign="left">
      <div onClick={() => goTo(0)}>Item 0</div>
    </SnapItem>
    <SnapItem snapAlign="left">
      <div onClick={() => goTo(1)}>Item 1</div>
    </SnapItem>
  </SnapList>
);

Response

  • A function (element:number, options?: { animationEnabled: boolean}) => void to scroll to the element. The animationEnabled is true by default. It can be use to scroll to a component when mounting it.

Arguments

  • ref: { React.RefObject<HTMLDivElement> } *

* Required fields

useVisibleElements

const snapList = useRef(null);
const selected = useVisibleElements(
  { ref: snapList, debounce: 10 },
  elements => elements[0],
);
const goToElement = useScroll({ ref: snapList });

React.useEffect(() => {
  // scroll instantly on component did mount
  goToElement(1, { animationEnabled: false });
}, []);

return (
  <SnapList ref={snapList}>
    <SnapItem snapAlign="left">
      <div
        onClick={() => goToElement(0)}
        style={{
          backgroundColor: selected === 0 ? 'papayawhip' : null,
        }}
      >
        Item 0
      </div>
    </SnapItem>
    <SnapItem snapAlign="left">
      <div
        onClick={() => goToElement(1)}
        style={{
          backgroundColor: selected === 1 ? 'papayawhip' : null,
        }}
      >
        Item 1
      </div>
    </SnapItem>
  </SnapList>
);

Arguments

  • ref: { React.RefObject<HTMLDivElement> } *
  • debounce: { number }. Optional (default 10). The time that the scroll is stopped before firing the visible elements check.
  • selectorFunction: { (element:number[], elementInCenter: number | null) => any }. This selector gets an array of the visible elements as an argument and the return value will be returned by the useVisibleElements. Use this function to add some logic like select only the first one, calculate if there hidden elements before or later, etc... *

* Required fields

Tip

Use many times useVisibleElements hook with different debounce values for different purposes. For instance with a SnapList to select one option, one with debounce 10 for the slider dots animation or the selected option background and another one with debounce 100 to fire a select sideEffect.

useDragToScroll

Thanks @danieljb for the contribution

const snapList = useRef(null);
const selected = useVisibleElements(
  { ref: snapList, debounce: 10 },
  elements => elements[0],
);

const { isDragging } = useDragToScroll({ ref: snapList, disable: false });

return (
  <>
    <p>{isDragging ? 'Dragging' : 'No dragging'}</p>
    <SnapList ref={snapList}>
      <SnapItem snapAlign="left">
        <div
          onClick={() => goToElement(0)}
          style={{
            backgroundColor: selected === 0 ? 'papayawhip' : null,
          }}
        >
          Item 0
        </div>
      </SnapItem>
      <SnapItem snapAlign="left">
        <div
          onClick={() => goToElement(1)}
          style={{
            backgroundColor: selected === 1 ? 'papayawhip' : null,
          }}
        >
          Item 1
        </div>
      </SnapItem>
    </SnapList>
  </p>
);

Response

  • isDragging: {boolean}.
  • disable: A function () => void to disable the dragToScroll feature.
  • enable: A function () => void to enable the dragToScroll feature.

Arguments

  • ref: { React.RefObject<HTMLDivElement> } *
  • disable: { booleal }. Optional (default false). The hook will be auto-disabled on touch devices but you can force it using this option.

* Required fields

isTouchDevice

This an internal util function used by useDragToScroll that can be useful for you. You can use it to modify your UI depending on the device. For example, you can show next/previous arrows only on no touch devices.

Do you want to contribute?

  • You can give a star to the project to help with the reputation
  • You can share it with your colleagues.
  • You can fork the repository and make your PR contribution.
  • You can explore using IntersectionObserver for the useVisible hook.
  • You can explore with better scrollTo polyfills.
  • You can create usefull extra elements like Dots, Thumbnails, Progress or Arrows.
  • You can create a new demo example, sky is the limit!
  • Yes, you can.

Changelog

Version 4

  • Fix decimal pixel problems
  • Breakchange useDragToScroll is returning { isDragging } instead of the boolean. https://github.com/luispuig/react-snaplist-carousel/commit/a2d23d6b804dcab1da9520db8edc746c6837f23e#diff-e3457effa5fa347d185fdd0d08ba3209R173
  • Version 4.1.0. Fix support for macOS Big Sur
  • Version 4.2.0. Add useScroll / goTo / animationEnabled option. Usefull to scroll on component mount.
  • Version 4.3.0.
    • Removed event.preventDefault() form mouseDown so focus event can be propagated up in the DOM tree.
    • <SnapList> now accept all the HTMLDivElement properties.
    • New className attached so it is easier to target it through CSS:
      • <SnapList> has a .snaplist.
      • <SnapItem> has a .snapitem.
  • Version 4.4.0. Added rtl support to the useScroll hook. Thanks to @dmvakh
  • Version 4.5.0. Added rtl support to the useScroll hook. Thanks to @equinusocio

Version 3

  • Added useDragToScroll
  • Added util isTouchDevice
  • Improved useScroll. Now uses the scrollPading to calculate the position of the elements.
  • Breakchange the SnapItem elements now uses margin in favor of padding. The browser native behavior works better works better.

License

MIT © luispuig