Пример того о чем пойдет речь:

Card 1

Card 2

Card 3

CSS

Спецификация CSS Scroll Snap позволяет привязывать положение прокрутки к определённым элементам или местоположению после того, как пользователь прокрутил страницу или элемент.

.wrapper {
  display: flex;
  gap: 8px;
  flex-direction: row;
  flex-wrap: nowrap;

  overflow-x: scroll;
  scroll-snap-type: x mandatory;

  /* NOTE: [1/2] Если нужны первая и последняя по центру */
  padding-left: 10%;
  padding-right: 10%;
}

.wrapper::-webkit-scrollbar {
  display: none;
}

.cardWrapper {
  scroll-snap-align: center;

  /* NOTE: [2/2] Если нужен хвост и перед, но [1/2] выключено */
  min-width: 100%;
  width: 100%;
}

.card {
  height: 100%;
  padding: 8px 8px 8px 8px;
  border-radius: 8px;
  font-size: small;
  border: 1px solid #959eaa;
}

React-компонент с перелистыванием карочек

SubjobsExperimentalCards.tsx

import { CSSProperties, memo } from 'react'
import classes from './SubjobsExperimentalCards.module.scss'

const genericMemo: <T>(component: T) => T = memo

type TProps<T> = {
  wrapperStyles?: CSSProperties;
  items: T[];
  cardRenderer: (ps: { itemData: T }) => React.ReactNode;
  getItemKey: (itemData: T) => string;
}

function _SubjobsExperimentalCards<TItem>({
  wrapperStyles,
  items,
  cardRenderer,
  getItemKey,
}: TProps<TItem>) {
  return (
    <>
      {
        items.length > 0 && (
          <div
            style={wrapperStyles}
            className={classes.wrapper}
          >
            {
              items.map((item) => {
                return (
                  <div
                    className={classes.cardWrapper}
                    key={getItemKey(item)}

                  >
                    {cardRenderer({ itemData: item })}
                  </div>
                )
              })
            }
          </div>
        )
      }
    </>
  )
}

export const SubjobsExperimentalCards = genericMemo(_SubjobsExperimentalCards)

Usage

<SubjobsExperimentalCards
  items={[
    { id: 1 }
  ]}
  cardRenderer={({ itemData }) => (
    return (
      <div>{itemData.id}</div>
    )
  )}
  getItemKey={(item) => String(item.id)}
/>

See also