[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/SliderLayout.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { PropsWithChildren } from 'react'
import { defineMessages } from 'react-intl'
import { CssHandlesTypes, useCssHandles } from 'vtex.css-handles'
import { useListContext } from 'vtex.list-context'
import { useResponsiveValue } from 'vtex.responsive-values'

import Slider, { CSS_HANDLES as SliderCssHandles } from './components/Slider'
import {
  SliderContextProvider,
  SliderLayoutProps,
  SliderLayoutSiteEditorProps,
} from './components/SliderContext'
import { CssHandlesProvider } from './modules/cssHandles'

export const CSS_HANDLES = SliderCssHandles

interface Props {
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function SliderLayout({
  totalItems,
  infinite = false,
  showNavigationArrows = 'always',
  showPaginationDots = 'always',
  usePagination = true,
  fullWidth = true,
  arrowSize = 25,
  children,
  centerMode = 'disabled',
  centerModeSlidesGap,
  itemsPerPage = {
    desktop: 5,
    tablet: 3,
    phone: 1,
  },
  classes,
  ...contextProps
}: PropsWithChildren<SliderLayoutProps & SliderLayoutSiteEditorProps & Props>) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const list = useListContext()?.list ?? []
  const totalSlides = totalItems ?? React.Children.count(children) + list.length
  const responsiveArrowIconSize = useResponsiveValue(arrowSize)
  const responsiveItemsPerPage = useResponsiveValue(itemsPerPage)
  const responsiveCenterMode = useResponsiveValue(centerMode)
  const slides = React.Children.toArray(children).concat(list)
  // Force fullWidth mode when centerMode is on
  const resolvedFullWidth = fullWidth || responsiveCenterMode !== 'disabled'

  return (
    <CssHandlesProvider handles={handles} withModifiers={withModifiers}>
      <SliderContextProvider
        infinite={infinite}
        totalItems={totalSlides}
        itemsPerPage={responsiveItemsPerPage}
        centerMode={responsiveCenterMode}
        centerModeSlidesGap={centerModeSlidesGap}
        {...contextProps}
      >
        <Slider
          centerMode={responsiveCenterMode}
          centerModeSlidesGap={centerModeSlidesGap}
          infinite={infinite}
          showNavigationArrows={showNavigationArrows}
          showPaginationDots={showPaginationDots}
          totalItems={totalSlides}
          usePagination={usePagination}
          fullWidth={resolvedFullWidth}
          arrowSize={responsiveArrowIconSize}
          itemsPerPage={responsiveItemsPerPage}
        >
          {slides}
        </Slider>
      </SliderContextProvider>
    </CssHandlesProvider>
  )
}

const messages = defineMessages({
  sliderTitle: {
    id: 'admin/editor.slider-layout.title',
    defaultMessage: '',
  },
  sliderInfinite: {
    id: 'admin/editor.slider-layout.infinite',
    defaultMessage: '',
  },
  sliderShowNavigation: {
    id: 'admin/editor.slider-layout.showNavigation',
    defaultMessage: '',
  },
  sliderShowPaginationDots: {
    id: 'admin/editor.slider-layout.showPaginationDots',
    defaultMessage: '',
  },
  sliderUsePagination: {
    id: 'admin/editor.slider-layout.usePagination',
    defaultMessage: '',
  },
  sliderFullWidth: {
    id: 'admin/editor.slider-layout.sliderFullWidth',
    defaultMessage: '',
  },
  sliderFullWidthDescription: {
    id: 'admin/editor.slider-layout.sliderFullWidthDescription',
    defaultMessage: '',
  },
  sliderNavigationAndPaginationPropertyMobileOnly: {
    id:
      'admin/editor.slider-layout.sliderNavigationAndPaginationPropertyMobileOnly',
    defaultMessage: '',
  },
  sliderNavigationAndPaginationPropertyDesktopOnly: {
    id:
      'admin/editor.slider-layout.sliderNavigationAndPaginationPropertyDesktopOnly',
    defaultMessage: '',
  },
  sliderNavigationAndPaginationPropertyAlways: {
    id:
      'admin/editor.slider-layout.sliderNavigationAndPaginationPropertyAlways',
    defaultMessage: '',
  },
  sliderNavigationAndPaginationPropertyNever: {
    id: 'admin/editor.slider-layout.sliderNavigationAndPaginationPropertyNever',
    defaultMessage: '',
  },
})

SliderLayout.schema = {
  title: messages.sliderTitle.id,
  type: 'object',
  properties: {
    autoplay: {
      type: 'object',
      isLayout: true,
      properties: {
        timeout: {
          type: 'number',
        },
        stopOnHover: {
          type: 'boolean',
        },
      },
    },
    itemsPerPage: {
      type: 'object',
      isLayout: true,
      properties: {
        desktop: {
          default: 5,
          type: 'number',
        },
        tablet: {
          default: 3,
          type: 'number',
        },
        phone: {
          default: 1,
          type: 'number',
        },
      },
    },
  },
}

export default SliderLayout

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/SliderLayoutGroup.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { createContext, useReducer, useContext } from 'react'

interface State {
  currentSlide: number
  transform?: null | number
  isHovering: boolean
}

interface SlideAction {
  type: 'SLIDE'
  payload: {
    currentSlide: number
    transform?: number
  }
}

interface HoverAction {
  type: 'HOVER'
  payload: {
    isHovering: boolean
  }
}

type Action = SlideAction | HoverAction
type Dispatch = (action: Action) => void

const SliderGroupStateContext = createContext<State | undefined>(undefined)

const SliderGroupDispatchContext = createContext<Dispatch | undefined>(
  undefined
)

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'SLIDE': {
      return {
        ...state,
        currentSlide: action.payload.currentSlide,
        transform: action.payload.transform,
      }
    }

    default: {
      return state
    }
  }
}

const SliderLayoutGroup: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    currentSlide: 0,
    transform: null,
    isHovering: false,
  })

  return (
    <SliderGroupStateContext.Provider value={state}>
      <SliderGroupDispatchContext.Provider value={dispatch}>
        {children}
      </SliderGroupDispatchContext.Provider>
    </SliderGroupStateContext.Provider>
  )
}

export function useSliderGroupState() {
  const context = useContext(SliderGroupStateContext)

  return context
}

export function useSliderGroupDispatch() {
  const context = useContext(SliderGroupDispatchContext)

  return context
}

export default SliderLayoutGroup

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/package.json from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "vtex.slider-layout",
  "version": "0.24.8",
  "description": "A React Slider component that works well in SPA and SSR",
  "files": [
    "react"
  ],
  "scripts": {
    "pretest": "yarn",
    "lint": "tsc --noEmit && eslint --ext ts,tsx .",
    "test": "vtex-test-tools test",
    "test:watch": "vtex-test-tools test --watch"
  },
  "dependencies": {
    "react": "^16.6.3",
    "react-intl": "^4.5.8"
  },
  "devDependencies": {
    "@apollo/react-testing": "^3.1.4",
    "@types/react": "^16.8.15",
    "@vtex/test-tools": "^3.1.0",
    "@vtex/tsconfig": "^0.5.0",
    "prettier": "^1.18.2",
    "typescript": "3.9.7",
    "vtex.css-handles": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.css-handles@1.0.0/public/@types/vtex.css-handles",
    "vtex.device-detector": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.device-detector@0.2.6/public/@types/vtex.device-detector",
    "vtex.list-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.list-context@0.1.1/public/@types/vtex.list-context",
    "vtex.render-runtime": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.render-runtime@8.126.4/public/@types/vtex.render-runtime",
    "vtex.responsive-values": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.responsive-values@0.4.0/public/@types/vtex.responsive-values",
    "vtex.store-icons": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-icons@0.18.0/public/@types/vtex.store-icons"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/store/contentSchemas.json from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "definitions": {
    "SliderLayout": {
      "type": "object",
      "properties": {
        "infinite": {
          "default": true,
          "title": "admin/editor.slider-layout.infinite",
          "type": "boolean"
        },
        "showNavigationArrows": {
          "default": "always",
          "enum": ["mobileOnly", "desktopOnly", "always", "never"],
          "enumNames": [
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyMobileOnly",
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyDesktopOnly",
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyAlways",
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyNever"
          ],
          "title": "admin/editor.slider-layout.showNavigation",
          "type": "string"
        },
        "showPaginationDots": {
          "default": "always",
          "enum": ["mobileOnly", "desktopOnly", "always", "never"],
          "enumNames": [
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyMobileOnly",
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyDesktopOnly",
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyAlways",
            "admin/editor.slider-layout.sliderNavigationAndPaginationPropertyNever"
          ],
          "title": "admin/editor.slider-layout.showPaginationDots",
          "type": "string"
        },
        "usePagination": {
          "default": true,
          "title": "admin/editor.slider-layout.usePagination",
          "type": "boolean"
        },
        "fullWidth": {
          "default": true,
          "title": "admin/editor.slider-layout.sliderFullWidth",
          "description": "admin/editor.slider-layout.sliderFullWidthDescription",
          "type": "boolean"
        }
      }
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/store/interfaces.json from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "slider-layout": {
    "component": "SliderLayout",
    "composition": "children",
    "content": {
      "$ref": "#/definitions/SliderLayout"
    }
  },
  "slider-layout-group": {
    "component": "SliderLayoutGroup",
    "composition": "children"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__fixtures__/CssHandlesHelper.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { CssHandlesTypes } from 'vtex.css-handles'

export const mockedUseContextCssHandlesFn = (
  CSS_HANDLES: CssHandlesTypes.CssHandlesList
) => ({
  handles: CSS_HANDLES.reduce<Record<string, string>>((acc, handle) => {
    acc[handle] = handle

    return acc
  }, {}),
  withModifiers: (handle: string, modifier: string | string[]) =>
    `${handle} ${handle}--${modifier}`,
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__fixtures__/SliderStateContext.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'

export const mockInitialSlides = [
  <div key={1}>I am a slide. My index is 1</div>,
  <div key={2}>I am a slide. My index is 2</div>,
  <div key={3}>I am a slide. My index is 3</div>,
  <div key={4}>I am a slide. My index is 4</div>,
  <div key={5}>I am a slide. My index is 5</div>,
  <div key={6}>I am a slide. My index is 6</div>,
  <div key={7}>I am a slide. My index is 7</div>,
  <div key={8}>I am a slide. My index is 8</div>,
  <div key={9}>I am a slide. My index is 9</div>,
  <div key={10}>I am a slide. My index is 10</div>,
]

export const mockInitialInfiniteSliderState = {
  slideWidth: 5,
  slidesPerPage: 5,
  currentSlide: 0,
  transform: -25,
  transformMap: {
    0: -25,
    1: -30,
    2: -35,
    3: -40,
    4: -45,
    5: -50,
    6: -55,
    7: -60,
    8: -65,
    9: -70,
    10: -75,
    11: -80,
    12: -85,
    13: -90,
    14: -95,
    '-5': 0,
    '-4': -5,
    '-3': -10,
    '-2': -15,
    '-1': -20,
  },
  navigationStep: 2,
  slideTransition: {
    speed: 400,
    delay: 0,
    timing: '',
  },
  itemsPerPage: 5,
  label: 'slider',
  totalItems: 10,
  isPageNavigationStep: true,
  isOnTouchMove: false,
  useSlidingTransitionEffect: false,
}

export const mockInitialNonInfiniteSliderState = {
  slideWidth: 10,
  slidesPerPage: 5,
  currentSlide: 0,
  transform: 0,
  transformMap: {
    '0': 0,
    '1': -10,
    '2': -20,
    '3': -30,
    '4': -40,
    '5': -50,
    '6': -60,
    '7': -70,
    '8': -80,
    '9': -90,
  },
  navigationStep: 2,
  slideTransition: {
    speed: 400,
    delay: 0,
    timing: '',
  },
  itemsPerPage: 5,
  label: 'slider',
  totalItems: 10,
  isPageNavigationStep: true,
  isOnTouchMove: false,
  useSlidingTransitionEffect: false,
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/typings/vtex.list-context.d.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
declare module 'vtex.list-context' {
  export const useListContext: () => undefined | { list: unknown[] }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/typings/vtex.store-icons.d.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
declare module 'vtex.store-icons' {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  export const IconCaret: any
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/typings/css.d.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
declare module '*.css' {
  const css: Record<string, string>
  export default css
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/components/Slider.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, useRef, Fragment, ReactNode } from 'react'
import { useDevice } from 'vtex.device-detector'

import { useScreenResize } from '../hooks/useScreenResize'
import { useTouchHandlers } from '../hooks/useTouchHandlers'
import { useAutoplay } from '../hooks/useAutoplay'
import {
  SliderLayoutProps,
  SliderLayoutSiteEditorProps,
  useSliderState,
} from './SliderContext'
import SliderTrack, {
  CSS_HANDLES as SliderTrackCssHandles,
} from './SliderTrack'
import Arrow, { CSS_HANDLES as ArrowCssHandles } from './Arrow'
import PaginationDots, {
  CSS_HANDLES as PaginationDotsCssHandles,
} from './PaginationDots'
import { useContextCssHandles } from '../modules/cssHandles'

interface Props extends SliderLayoutSiteEditorProps {
  arrowSize: number
  totalItems: number
  itemsPerPage: number
  centerMode: SliderLayoutProps['centerMode']
  centerModeSlidesGap?: SliderLayoutProps['centerModeSlidesGap']
  // This type comes from React itself. It is the return type for
  // React.Children.toArray().
  children?: Array<Exclude<ReactNode, boolean | null | undefined>>
}

export const CSS_HANDLES = [
  'sliderLayoutContainer',
  'sliderTrackContainer',
  ...SliderTrackCssHandles,
  ...ArrowCssHandles,
  ...PaginationDotsCssHandles,
] as const

const Slider: FC<Props> = ({
  children,
  totalItems,
  infinite = false,
  showNavigationArrows,
  showPaginationDots,
  usePagination: shouldUsePagination = true,
  arrowSize,
  fullWidth,
  itemsPerPage,
  centerMode,
  centerModeSlidesGap,
}) => {
  const { handles } = useContextCssHandles()
  const { isMobile } = useDevice()
  const { label = 'slider', slidesPerPage } = useSliderState()
  const containerRef = useRef<HTMLDivElement>(null)
  const { onTouchEnd, onTouchStart, onTouchMove } = useTouchHandlers({
    infinite,
    centerMode,
  })

  useAutoplay(infinite, containerRef)
  useScreenResize(infinite, itemsPerPage)

  const shouldBeStaticList = slidesPerPage >= totalItems

  const controls = `${label
    .toLowerCase()
    .trim()
    .replace(/ /g, '-')}-items-${Math.random()
    .toString(36)
    .substring(2, 9)}`

  const shouldShowArrows = Boolean(
    (showNavigationArrows === 'always' ||
      (showNavigationArrows === 'mobileOnly' && isMobile) ||
      (showNavigationArrows === 'desktopOnly' && !isMobile)) &&
      !shouldBeStaticList
  )

  const shouldShowPaginationDots = Boolean(
    (showPaginationDots === 'always' ||
      (showPaginationDots === 'mobileOnly' && isMobile) ||
      (showPaginationDots === 'desktopOnly' && !isMobile)) &&
      !shouldBeStaticList
  )

  const touchStartHandler = (e: React.TouchEvent) =>
    shouldUsePagination && !shouldBeStaticList ? onTouchStart(e) : null

  const touchEndHandler = (e: React.TouchEvent) =>
    shouldUsePagination && !shouldBeStaticList ? onTouchEnd(e) : null

  const touchMoveHandler = (e: React.TouchEvent) =>
    shouldUsePagination && !shouldBeStaticList ? onTouchMove(e) : null

  return (
    <section
      onTouchStart={touchStartHandler}
      onTouchEnd={touchEndHandler}
      onTouchMove={touchMoveHandler}
      aria-label={label}
      id={controls}
      style={{
        WebkitOverflowScrolling: !shouldUsePagination ? 'touch' : undefined,
        paddingLeft: fullWidth ? undefined : arrowSize * 2,
        paddingRight: fullWidth ? undefined : arrowSize * 2,
        touchAction: 'pan-y',
      }}
      className={`w-100 flex items-center relative ${handles.sliderLayoutContainer}`}
    >
      <div
        className={`w-100 ${handles.sliderTrackContainer} ${
          shouldUsePagination ? 'overflow-hidden' : 'overflow-x-scroll'
        }`}
        ref={containerRef}
      >
        <SliderTrack
          centerMode={centerMode}
          centerModeSlidesGap={centerModeSlidesGap}
          infinite={infinite}
          totalItems={totalItems}
          usePagination={shouldUsePagination}
        >
          {children}
        </SliderTrack>
      </div>
      {shouldShowArrows && shouldUsePagination && (
        <Fragment>
          <Arrow
            totalItems={totalItems}
            orientation="left"
            controls={controls}
            infinite={infinite}
            arrowSize={arrowSize}
          />
          <Arrow
            totalItems={totalItems}
            orientation="right"
            controls={controls}
            infinite={infinite}
            arrowSize={arrowSize}
          />
        </Fragment>
      )}
      {shouldShowPaginationDots && shouldUsePagination && (
        <PaginationDots
          totalItems={totalItems}
          controls={controls}
          infinite={infinite}
        />
      )}
    </section>
  )
}

export default Slider

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/components/Arrow.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { memo, FC, ReactNode } from 'react'
import { IconCaret } from 'vtex.store-icons'

import { useSliderState } from './SliderContext'
import useKeyboardArrows from '../hooks/useKeyboardArrows'
import { useSliderControls } from '../hooks/useSliderControls'
import { useContextCssHandles } from '../modules/cssHandles'

interface Props {
  custom?: ReactNode
  orientation: 'left' | 'right'
  controls: string
  totalItems: number
  infinite: boolean
  arrowSize: number
}

export const CSS_HANDLES = [
  'sliderLeftArrow',
  'sliderRightArrow',
  'sliderArrows',
] as const

const Arrow: FC<Props> = ({
  custom,
  orientation,
  controls,
  totalItems,
  infinite,
  arrowSize,
}) => {
  const { currentSlide, slidesPerPage } = useSliderState()
  const { goBack, goForward } = useSliderControls(infinite)

  const { handles } = useContextCssHandles()

  const isLeftEndReached = currentSlide === 0
  const isRightEndReached = !(currentSlide + 1 + slidesPerPage <= totalItems)
  const disabled =
    !infinite &&
    ((orientation === 'left' && isLeftEndReached) ||
      (orientation === 'right' && isRightEndReached))

  useKeyboardArrows(goBack, goForward)

  function handleArrowClick(
    event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) {
    if (event) {
      event.stopPropagation()
      event.preventDefault()
    }

    if (orientation === 'left') {
      goBack()
    }

    if (orientation === 'right') {
      goForward()
    }
  }

  return (
    <button
      className={`${
        orientation === 'left'
          ? `${handles.sliderLeftArrow} left-0`
          : `${handles.sliderRightArrow} right-0`
      } ${
        handles.sliderArrows
      } absolute transparent ma2 flex items-center justify-center bn outline-0 pointer`}
      style={{ background: 'transparent' }}
      onClick={handleArrowClick}
      aria-controls={controls}
      aria-label={`${orientation === 'left' ? 'Previous' : 'Next'} Slide`}
      disabled={disabled}
    >
      {custom ?? <IconCaret size={arrowSize} orientation={orientation} thin />}
    </button>
  )
}

export default memo(Arrow)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/components/PaginationDots.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { memo, FC } from 'react'

import { useContextCssHandles } from '../modules/cssHandles'
import { useSliderState } from './SliderContext'
import { useSliderControls } from '../hooks/useSliderControls'

const DOTS_DEFAULT_SIZE = 0.625

interface Props {
  controls: string
  totalItems: number
  infinite: boolean
}

export const CSS_HANDLES = ['paginationDotsContainer', 'paginationDot'] as const

const getSelectedDot = (
  passVisibleSlides: boolean,
  currentSlide: number,
  slidesToShow: number
): number => {
  const realCurrentSlide = passVisibleSlides
    ? currentSlide + (slidesToShow - 1)
    : currentSlide

  return passVisibleSlides
    ? Math.floor(realCurrentSlide / slidesToShow)
    : realCurrentSlide
}

const getSlideIndices = (
  slidesToShow: number,
  passVisibleSlides: boolean,
  totalItems: number
): number[] =>
  slidesToShow
    ? [
        ...Array(
          passVisibleSlides ? Math.ceil(totalItems / slidesToShow) : totalItems
        ).keys(),
      ]
    : []

const PaginationDots: FC<Props> = ({ controls, totalItems, infinite }) => {
  const { slidesPerPage, currentSlide, navigationStep } = useSliderState()
  const { goBack, goForward } = useSliderControls(infinite)
  const { handles, withModifiers } = useContextCssHandles()
  const passVisibleSlides = navigationStep === slidesPerPage

  const slideIndexes = getSlideIndices(
    slidesPerPage,
    passVisibleSlides,
    totalItems
  )

  const handleDotClick = (
    event: React.KeyboardEvent | React.MouseEvent,
    index: number
  ) => {
    if (event) {
      event.stopPropagation()
      if ('key' in event && event.key !== 'Tab') {
        event.preventDefault()
      }
    }

    // Considering that each pagination dot represents a page, pageDelta
    // represents how many pages did the user "skip" by clicking in the dot.
    const pageDelta =
      index - getSelectedDot(passVisibleSlides, currentSlide, slidesPerPage)

    const slidesToPass = Math.abs(pageDelta) * navigationStep

    pageDelta > 0 ? goForward(slidesToPass) : goBack(slidesToPass)
  }

  return (
    <div
      className={`${handles.paginationDotsContainer} flex absolute justify-center pa0 ma0 bottom-0 left-0 right-0`}
      role="group"
      aria-label="Slider pagination dots"
    >
      {slideIndexes.map(index => {
        const isActive =
          index ===
          getSelectedDot(passVisibleSlides, currentSlide, slidesPerPage)

        return (
          <div
            className={`${withModifiers(
              'paginationDot',
              isActive ? 'isActive' : ''
            )} ${
              isActive ? 'bg-emphasis' : 'bg-muted-3'
            } grow dib br-100 pa2 mr2 ml2 bw0 pointer outline-0`}
            style={{
              height: `${DOTS_DEFAULT_SIZE}rem`,
              width: `${DOTS_DEFAULT_SIZE}rem`,
            }}
            key={index}
            tabIndex={isActive ? 0 : -1}
            onKeyDown={event => handleDotClick(event, index)}
            onClick={event => handleDotClick(event, index)}
            role="button"
            aria-controls={controls}
            aria-label={`Dot ${index + 1} of ${slideIndexes.length}`}
            data-testid="paginationDot"
          />
        )
      })}
    </div>
  )
}

export default memo(PaginationDots)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/components/SliderTrack.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { cloneElement, FC, ReactElement, ReactNode } from 'react'
import { useIntl } from 'react-intl'

import {
  useSliderState,
  useSliderDispatch,
  SliderLayoutProps,
} from './SliderContext'
import { useSliderGroupDispatch } from '../SliderLayoutGroup'
import { useSliderVisibility } from '../hooks/useSliderVisibility'
import { useContextCssHandles } from '../modules/cssHandles'

export const CSS_HANDLES = [
  'sliderTrack',
  'slide',
  'slideChildrenContainer',
] as const

interface Props {
  totalItems: number
  infinite: boolean
  usePagination: boolean
  centerMode: SliderLayoutProps['centerMode']
  centerModeSlidesGap?: SliderLayoutProps['centerModeSlidesGap']
  // This type comes from React itself. It is the return type for
  // React.Children.toArray().
  children?: Array<Exclude<ReactNode, boolean | null | undefined>>
}

interface ResolveAriaAttributesProps {
  visible: boolean
  index: number
  totalItems: number
  intl: any
}

const resolveAriaAttributes = ({
  visible,
  index,
  totalItems,
  intl,
}: ResolveAriaAttributesProps) => {
  if (index < 0 || index >= totalItems) {
    return {
      'aria-hidden': !visible,
      role: 'none presentation',
    }
  }

  return {
    'aria-hidden': !visible,
    role: 'group',
    'aria-roledescription': 'slide',
    'aria-label': `${intl.formatMessage(
      { id: 'store/slider-layout.sliderTrack.aria-label' },
      { slide: index + 1, total: totalItems }
    )}`,
  }
}

const getFirstOrLastVisible = (slidesPerPage: number, index: number) => {
  // every multiple of the number of slidesPerPage is a first (e.g. 0,3,6 if slidesPerPage is 3)
  if (index % slidesPerPage === 0) {
    return 'firstVisible'
  }

  // every slide before  the multiple of the number of slidesPerPage is a last (e.g. 2,5,8 if slidesPerPage is 3)
  if ((index + 1) % slidesPerPage === 0) {
    return 'lastVisible'
  }

  return ''
}

const removeAnalyticsProperties = (children: ReactElement[]) => {
  return React.Children.toArray(
    React.Children.map(children, child =>
      typeof child === 'string' || typeof child === 'number'
        ? child
        : cloneElement(child, {
            ...child.props,
            // Tells the component it is being duplicated. Each component should handle it
            __isDuplicated: true,
          })
    )
  )
}

const SliderTrack: FC<Props> = ({
  infinite,
  usePagination,
  centerMode,
  centerModeSlidesGap,
  totalItems,
  children,
}) => {
  const {
    slideWidth,
    slidesPerPage,
    currentSlide,
    isOnTouchMove,
    useSlidingTransitionEffect,
    slideTransition: { speed, timing, delay },
    transformMap,
    transform,
  } = useSliderState()

  const dispatch = useSliderDispatch()
  const intl = useIntl()
  const groupDispatch = useSliderGroupDispatch()
  const { handles, withModifiers } = useContextCssHandles()

  const { shouldRenderItem, isItemVisible } = useSliderVisibility({
    currentSlide,
    slidesPerPage,
    totalItems,
    centerMode,
  })

  const postRenderedSlides =
    infinite && children
      ? removeAnalyticsProperties(children as ReactElement[]).slice(
          0,
          slidesPerPage
        )
      : []

  const preRenderedSlides =
    infinite && children
      ? removeAnalyticsProperties(children as ReactElement[]).slice(
          children.length - slidesPerPage
        )
      : []

  const slides = preRenderedSlides.concat(children ?? [], postRenderedSlides)

  const trackWidth =
    slidesPerPage <= totalItems
      ? `${(slides.length * 100) / slidesPerPage}%`
      : '100%'

  return (
    <div
      data-testid="slider-track"
      className={`${handles.sliderTrack} flex ${
        centerMode !== 'disabled' ? '' : 'justify-around'
      } relative pa0 ma0`}
      style={{
        transition:
          isOnTouchMove || !useSlidingTransitionEffect
            ? undefined
            : `transform ${speed}ms ${timing} ${delay}ms`,
        transform: `translate3d(${
          isOnTouchMove ? transform : transformMap[currentSlide]
        }%, 0, 0)`,
        width: trackWidth,
      }}
      onTransitionEnd={() => {
        dispatch({ type: 'DISABLE_TRANSITION' })

        if (currentSlide >= totalItems) {
          dispatch({
            type: 'ADJUST_CURRENT_SLIDE',
            payload: {
              currentSlide: 0,
              transform: transformMap[0],
            },
          })
          groupDispatch?.({
            type: 'SLIDE',
            payload: {
              currentSlide: 0,
              transform: transformMap[0],
            },
          })
        }

        if (currentSlide < 0) {
          dispatch({
            type: 'ADJUST_CURRENT_SLIDE',
            payload: {
              currentSlide: currentSlide + totalItems,
              transform: transformMap[currentSlide + totalItems],
            },
          })
          groupDispatch?.({
            type: 'SLIDE',
            payload: {
              currentSlide: currentSlide + totalItems,
              transform: transformMap[currentSlide + totalItems],
            },
          })
        }
      }}
      aria-atomic="false"
      aria-live="polite"
    >
      {slides.map((child, index) => {
        // This is to take into account that there is a clone of the last page
        // in the left, to enable the infinite loop effect in case infinite
        // is set to true.
        const adjustedIndex = index - (infinite ? slidesPerPage : 0)
        const slideContainerStyles = {
          width: `${slideWidth}%`,
          marginLeft:
            centerMode !== 'disabled' && !centerModeSlidesGap
              ? `${slideWidth / (8 * slidesPerPage)}%`
              : undefined,
          marginRight:
            centerMode !== 'disabled' && !centerModeSlidesGap
              ? `${slideWidth / (8 * slidesPerPage)}%`
              : undefined,
          paddingLeft:
            centerMode !== 'disabled' && centerModeSlidesGap
              ? centerModeSlidesGap / 2
              : undefined,
          paddingRight:
            centerMode !== 'disabled' && centerModeSlidesGap
              ? centerModeSlidesGap / 2
              : undefined,
        }

        return (
          <div
            key={adjustedIndex}
            {...resolveAriaAttributes({
              visible: isItemVisible(adjustedIndex),
              index: adjustedIndex,
              totalItems,
              intl,
            })}
            className={`${withModifiers('slide', [
              getFirstOrLastVisible(slidesPerPage, adjustedIndex),
              isItemVisible(adjustedIndex) ? 'visible' : 'hidden',
            ])} flex relative`}
            data-index={
              adjustedIndex >= 0 && adjustedIndex < totalItems
                ? adjustedIndex + 1
                : undefined
            }
            style={slideContainerStyles}
          >
            <div
              className={`${handles.slideChildrenContainer} flex justify-center items-center w-100`}
            >
              {!usePagination || shouldRenderItem(adjustedIndex) ? child : null}
            </div>
          </div>
        )
      })}
    </div>
  )
}

export default SliderTrack

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/components/SliderContext.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, {
  createContext,
  useReducer,
  useContext,
  FC,
  useMemo,
  useState,
} from 'react'
import { ResponsiveValuesTypes } from 'vtex.responsive-values'

import { useSliderGroupState } from '../SliderLayoutGroup'

interface AdjustOnResizeAction {
  type: 'ADJUST_ON_RESIZE'
  payload: {
    shouldCorrectItemPosition: boolean
    slidesPerPage: number
    navigationStep: number
  }
}

interface SlideAction {
  type: 'SLIDE'
  payload: {
    transform?: number
    currentSlide: number
  }
}

interface TouchAction {
  type: 'TOUCH'
  payload: {
    transform?: number
    isOnTouchMove: boolean
  }
}

interface DisableTransitionAction {
  type: 'DISABLE_TRANSITION'
}

interface AdjustCurrentSlideAction {
  type: 'ADJUST_CURRENT_SLIDE'
  payload: {
    currentSlide: number
    transform?: number
  }
}

interface SyncSliderGroupAction {
  type: 'SYNC_SLIDER_GROUP'
  payload: {
    currentSlide: number
    transform?: number
  }
}

interface AdjustContextValuesAction {
  type: 'ADJUST_CONTEXT_VALUES'
  payload: {
    transformMap: State['transformMap']
    slideWidth: State['slideWidth']
    slidesPerPage: State['slidesPerPage']
    transform: State['transform']
    navigationStep: State['navigationStep']
    totalItems: State['totalItems']
  }
}

export interface SliderLayoutSiteEditorProps {
  infinite?: boolean
  showNavigationArrows?: 'mobileOnly' | 'desktopOnly' | 'always' | 'never'
  showPaginationDots?: 'mobileOnly' | 'desktopOnly' | 'always' | 'never'
  usePagination?: boolean
  fullWidth?: boolean
  arrowSize?: ResponsiveValuesTypes.ResponsiveValue<number>
}

export interface SliderLayoutProps {
  totalItems?: number
  label?: string
  slideTransition?: {
    /** Transition speed in ms */
    speed: number
    /** Transition delay in ms */
    delay: number
    timing: string
  }
  autoplay?: {
    /** Timeout duration in ms */
    timeout: number
    stopOnHover?: boolean
  }
  navigationStep?: number | 'page'
  itemsPerPage?: ResponsiveValuesTypes.ResponsiveValue<number>
  centerMode?: ResponsiveValuesTypes.ResponsiveValue<
    'center' | 'to-the-left' | 'disabled'
  >
  centerModeSlidesGap?: number
}

interface State extends Partial<SliderLayoutProps> {
  /** Width of each slide */
  slideWidth: number
  /** Number of slides to show per page */
  slidesPerPage: number
  /** Index of the leftmost slide of the current page */
  currentSlide: number
  /** Current transform value */
  transform: number
  /** Total number of slides */
  totalItems: number
  /** Number of slides to slide in navigation */
  navigationStep: number
  /** Whether or not navigationStep prop is set to 'page' */
  isPageNavigationStep: boolean
  /** Whether or not a touchmove event is happening */
  isOnTouchMove: boolean
  useSlidingTransitionEffect: boolean
  transformMap: Record<number, number>
  slideTransition: Exclude<SliderLayoutProps['slideTransition'], undefined>
}

interface SliderContextProps extends SliderLayoutProps {
  totalItems: number
  itemsPerPage: number
  infinite: SliderLayoutSiteEditorProps['infinite']
}

type Action =
  | AdjustOnResizeAction
  | SlideAction
  | TouchAction
  | DisableTransitionAction
  | AdjustCurrentSlideAction
  | AdjustContextValuesAction
  | SyncSliderGroupAction
type Dispatch = (action: Action) => void

const SliderStateContext = createContext<State | undefined>(undefined)
const SliderDispatchContext = createContext<Dispatch | undefined>(undefined)

function sliderContextReducer(state: State, action: Action): State {
  switch (action.type) {
    case 'ADJUST_ON_RESIZE':
      return {
        ...state,
        slidesPerPage: action.payload.slidesPerPage,
        navigationStep: action.payload.navigationStep,
        transform: action.payload.shouldCorrectItemPosition
          ? state.transformMap[state.currentSlide]
          : state.transform,
      }

    case 'SLIDE':
      return {
        ...state,
        transform: action.payload.transform ?? state.transform,
        currentSlide: action.payload.currentSlide,
        useSlidingTransitionEffect: true,
      }

    case 'TOUCH':
      return {
        ...state,
        transform: action.payload.transform ?? state.transform,
        isOnTouchMove: action.payload.isOnTouchMove,
      }

    case 'DISABLE_TRANSITION':
      return {
        ...state,
        useSlidingTransitionEffect: false,
      }

    case 'ADJUST_CURRENT_SLIDE':
      return {
        ...state,
        currentSlide: action.payload.currentSlide,
        transform: action.payload.transform ?? state.transform,
      }

    case 'SYNC_SLIDER_GROUP':
      return {
        ...state,
        currentSlide: action.payload.currentSlide,
        transform: action.payload.transform ?? state.transform,
        useSlidingTransitionEffect: true,
      }

    case 'ADJUST_CONTEXT_VALUES':
      return {
        ...state,
        transformMap: action.payload.transformMap,
        slideWidth: action.payload.slideWidth,
        slidesPerPage: action.payload.slidesPerPage,
        transform: action.payload.transform,
        navigationStep: action.payload.navigationStep,
        totalItems: action.payload.totalItems,
      }

    default:
      return state
  }
}

const SliderContextProvider: FC<SliderContextProps> = ({
  autoplay,
  children,
  totalItems,
  label = 'slider',
  navigationStep = 'page',
  infinite = false,
  itemsPerPage,
  centerMode,
  slideTransition = {
    speed: 400,
    delay: 0,
    timing: '',
  },
  centerModeSlidesGap,
}) => {
  const sliderGroupState = useSliderGroupState()

  // This enables us to support dynamic slider-layouts
  const [prevProps, setPrevProps] = useState<{
    itemsPerPage: SliderContextProps['itemsPerPage'] | null
    totalItems: SliderContextProps['totalItems'] | null
  }>({
    itemsPerPage: null,
    totalItems: null,
  })

  const resolvedNavigationStep: number =
    navigationStep === 'page' ? itemsPerPage : navigationStep

  const resolvedSlidesPerPage: number =
    totalItems <= itemsPerPage ? totalItems : itemsPerPage

  const hiddenSlides = infinite ? resolvedSlidesPerPage * 2 : 0

  const newTotalItems = hiddenSlides + totalItems

  const slideWidth = useMemo(() => {
    const baseSlideWidth = 100 / newTotalItems

    let resultingSlideWidth = baseSlideWidth

    if (centerMode !== 'disabled') {
      resultingSlideWidth =
        (resolvedSlidesPerPage / (resolvedSlidesPerPage + 1)) * baseSlideWidth

      if (centerMode === 'to-the-left' && centerModeSlidesGap) {
        resultingSlideWidth =
          (baseSlideWidth * resolvedSlidesPerPage) /
          (resolvedSlidesPerPage + 1 / 2)
      }
    }

    return resultingSlideWidth
  }, [newTotalItems, centerMode, centerModeSlidesGap, resolvedSlidesPerPage])

  const transformMap = useMemo(() => {
    const currentMap: Record<number, number> = {}

    for (let idx = 0; idx < newTotalItems; ++idx) {
      const currIdx = infinite ? idx - resolvedSlidesPerPage : idx
      let transformValue = -(slideWidth * idx)

      if (centerMode !== 'disabled') {
        // This represents the new value for each transformValue taking into
        // account the changes made to slideWidth's value due to the fact that
        // centerMode is enabled.
        const adjustedTransformValue = -(
          (1 + 1 / (4 * resolvedSlidesPerPage)) *
          slideWidth *
          idx
        )

        transformValue = adjustedTransformValue

        if (centerMode === 'center') {
          // This is a correction factor to center the slides when centerMode
          // is enabled and set to 'center'.
          const transformCenterCorrection =
            centerMode === 'center' ? (slideWidth * 3) / 8 : 0

          transformValue += transformCenterCorrection
        }

        if (centerModeSlidesGap) {
          transformValue =
            centerMode === 'center'
              ? -(slideWidth * (idx - 1 / 2))
              : -(slideWidth * idx)
        }
      }

      currentMap[currIdx] = transformValue
    }

    return currentMap
  }, [
    slideWidth,
    newTotalItems,
    resolvedSlidesPerPage,
    infinite,
    centerMode,
    centerModeSlidesGap,
  ])

  const initialSlide = useMemo(() => sliderGroupState?.currentSlide ?? 0, [
    sliderGroupState,
  ])

  const initialTransform = useMemo(
    () => sliderGroupState?.transform ?? transformMap[initialSlide],
    [transformMap, initialSlide, sliderGroupState]
  )

  const [state, dispatch] = useReducer(sliderContextReducer, {
    slideWidth,
    slidesPerPage: resolvedSlidesPerPage,
    currentSlide: initialSlide,
    transform: initialTransform,
    transformMap,
    navigationStep: resolvedNavigationStep,
    slideTransition,
    itemsPerPage,
    label,
    autoplay,
    totalItems,
    isPageNavigationStep: navigationStep === 'page',
    isOnTouchMove: false,
    useSlidingTransitionEffect: false,
  })

  if (
    itemsPerPage !== prevProps.itemsPerPage ||
    totalItems !== prevProps.totalItems
  ) {
    dispatch({
      type: 'ADJUST_CONTEXT_VALUES',
      payload: {
        transformMap,
        slideWidth,
        slidesPerPage: resolvedSlidesPerPage,
        transform: transformMap[state.currentSlide],
        navigationStep: resolvedNavigationStep,
        totalItems,
      },
    })
    setPrevProps({ itemsPerPage, totalItems })
  }

  if (
    sliderGroupState &&
    sliderGroupState.currentSlide !== state.currentSlide
  ) {
    const newCurrentSlide = sliderGroupState?.currentSlide ?? state.currentSlide
    const newTransformValue =
      sliderGroupState?.transform ?? transformMap[newCurrentSlide]

    dispatch({
      type: 'SYNC_SLIDER_GROUP',
      payload: {
        currentSlide: newCurrentSlide,
        transform: newTransformValue,
      },
    })
  }

  return (
    <SliderStateContext.Provider value={state}>
      <SliderDispatchContext.Provider value={dispatch}>
        {children}
      </SliderDispatchContext.Provider>
    </SliderStateContext.Provider>
  )
}

function useSliderState() {
  const context = useContext(SliderStateContext)

  if (context === undefined) {
    throw new Error(
      'useSliderState must be used within a SliderContextProvider'
    )
  }

  return context
}

function useSliderDispatch() {
  const context = useContext(SliderDispatchContext)

  if (context === undefined) {
    throw new Error(
      'useSliderDispatch must be used within a SliderContextProvider'
    )
  }

  return context
}

export { SliderContextProvider, useSliderDispatch, useSliderState }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/PaginationDots.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { render, fireEvent } from '@vtex/test-tools/react'

import PaginationDots, { CSS_HANDLES } from '../components/PaginationDots'
import { useContextCssHandles } from '../modules/cssHandles'
import { mockedUseContextCssHandlesFn } from '../__fixtures__/CssHandlesHelper'

const mockGoForward = jest.fn()
const mockGoBack = jest.fn()

jest.mock('../components/SliderContext', () => {
  return {
    useSliderState: () => ({
      slideWidth: 20,
      slidesPerPage: 5,
      currentSlide: 0,
      transform: 0,
      navigationStep: 5,
      slideTransition: {
        speed: 400,
        delay: 0,
        timing: '',
      },
      itemsPerPage: {
        desktop: 5,
        tablet: 5,
        phone: 5,
      },
      label: 'slider',
      totalItems: 20,
      isPageNavigationStep: true,
      isOnTouchMove: false,
    }),
  }
})

jest.mock('../hooks/useSliderControls', () => {
  return {
    useSliderControls: () => ({
      goForward: mockGoForward,
      goBack: mockGoBack,
    }),
  }
})

jest.mock('../modules/cssHandles', () => ({
  useContextCssHandles: jest.fn(),
}))

const mockedUseContextCssHandles = useContextCssHandles as jest.Mock<
  ReturnType<typeof useContextCssHandles>
>

mockedUseContextCssHandles.mockImplementation(() =>
  mockedUseContextCssHandlesFn(CSS_HANDLES)
)

describe('Basic rendering', () => {
  it('should render the correct number of pagination dots based on the number of slider pages', () => {
    const { queryAllByTestId } = render(
      <PaginationDots
        controls="pagination-dots-test"
        infinite
        totalItems={20}
      />
    )

    const numberOfPagesBasedOnTotalItems = 4
    const testId = 'paginationDot'

    expect(queryAllByTestId(testId)).toHaveLength(
      numberOfPagesBasedOnTotalItems
    )
  })
})

describe('Accessibility', () => {
  it('should have correct aria role and label in its container', () => {
    const { queryByRole, queryByLabelText } = render(
      <PaginationDots
        controls="pagination-dots-test"
        infinite
        totalItems={20}
      />
    )

    const expectedContainerRole = 'group'
    const expectedContainerAriaLabel = 'Slider pagination dots'

    expect(queryByRole(expectedContainerRole)).toBeTruthy()
    expect(queryByLabelText(expectedContainerAriaLabel)).toBeTruthy()
  })

  it('should have correct aria attributes in each dot', () => {
    const { queryAllByRole, queryByLabelText } = render(
      <PaginationDots
        controls="pagination-dots-test"
        infinite
        totalItems={20}
      />
    )

    const expectedDotsRole = 'button'
    const expectedNumberOfRenderedDots = 4

    expect(queryByLabelText('Dot 1 of 4')).toBeTruthy()
    expect(queryByLabelText('Dot 2 of 4')).toBeTruthy()
    expect(queryByLabelText('Dot 3 of 4')).toBeTruthy()
    expect(queryByLabelText('Dot 4 of 4')).toBeTruthy()

    expect(queryAllByRole(expectedDotsRole)).toHaveLength(
      expectedNumberOfRenderedDots
    )
  })
})

describe('Behavior upon interaction', () => {
  it('should call goForward() and goBack() methods with correct arguments as the user clicks in a dot', () => {
    const { queryAllByTestId } = render(
      <PaginationDots
        controls="pagination-dots-test"
        infinite
        totalItems={20}
      />
    )

    const renderedDots = queryAllByTestId('paginationDot')

    expect(renderedDots).toHaveLength(4)

    // Since the initial active dot is always index 0,
    // this should simulate a user clicking in the third
    // pagination dot.
    fireEvent.click(renderedDots[2])

    // Notice that this call does NOT update the sliderState,
    // so in the next assertion, the PaginationDots component
    // will behave as if it were still with the first dot active.
    expect(mockGoForward).toBeCalledWith(10)

    // Simulate the user clicking in the first pagination dot.
    fireEvent.click(renderedDots[0])
    expect(mockGoBack).toBeCalledWith(0)
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/hooks.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { hooks } from '@vtex/test-tools/react'

import { useSliderControls } from '../hooks/useSliderControls'
import {
  mockInitialInfiniteSliderState,
  mockInitialNonInfiniteSliderState,
} from '../__fixtures__/SliderStateContext'

const { renderHook, act } = hooks

const mockDispatch = jest.fn()
let mockSliderInitialState: any

jest.mock('../components/SliderContext', () => ({
  useSliderState: () => mockSliderInitialState,
  useSliderDispatch: () => mockDispatch,
}))

beforeEach(() => {
  mockDispatch.mockClear()
})

describe('useAutoplay', () => {
  it.todo('should call goForward() after the correct interval')
  it.todo('should stop if user is hovering the slider')
  it.todo(
    'should trigger autoplay again after user moves the mouse away from the slider'
  )
})

describe('useHovering', () => {
  it.todo('should return true if user is hovering the slider')
  it.todo('should return false if user is not hovering the slider')
})

describe('useKeyboardArrows', () => {
  it.todo('should call onRight() function when user presses ArrowRight key')
  it.todo('should call onLeft() function when user presses ArrowLeft key')
})

describe('useScreenResize', () => {
  it.todo('should perform an ADJUST_ON_RESIZE dispatch call upon screen resize')
  it.todo(
    'should calculate the correct number of slides per page after a change in device'
  )
  it.todo(
    'should calculate the correct navigation step after a change in device'
  )
  it.todo(
    `should use the total number of slides for slides per page when that number
     is smaller than the resolved slides per page based on device type`
  )
  it.todo(
    `should use the total number of slides for navigation step when that number
     is smaller than the resolved slides per page based on device type`
  )
})

describe('useSliderControls', () => {
  describe('goBack()', () => {
    it('should dispatch a SLIDE action to go back the navigationStep when goBack() is called with no arguments', async () => {
      mockSliderInitialState = mockInitialInfiniteSliderState
      const { result } = renderHook(() => useSliderControls(true))

      await act(() => Promise.resolve())
      result.current.goBack()

      expect(mockDispatch).toHaveBeenCalledWith({
        type: 'SLIDE',
        payload: {
          currentSlide: -2,
          transform: mockInitialInfiniteSliderState.transformMap[-2],
        },
      })
    })

    it('should dispatch a SLIDE action to go back `step` pages when goBack() is called', async () => {
      mockSliderInitialState = mockInitialInfiniteSliderState
      const { result } = renderHook(() => useSliderControls(true))

      await act(() => Promise.resolve())
      result.current.goBack(3)

      expect(mockDispatch).toHaveBeenCalledWith({
        type: 'SLIDE',
        payload: {
          currentSlide: -3,
          transform: mockInitialInfiniteSliderState.transformMap[-3],
        },
      })
    })

    it('should prevent SLIDE action to set `currentSlide` to a negative number if the slider is not an infinite one', async () => {
      mockSliderInitialState = mockInitialNonInfiniteSliderState
      const { result } = renderHook(() => useSliderControls(false))

      await act(() => Promise.resolve())
      result.current.goBack()

      expect(mockDispatch).toHaveBeenCalledWith({
        type: 'SLIDE',
        payload: {
          currentSlide: 0,
          transform: 0,
        },
      })
    })
  })

  describe('goForward()', () => {
    it('should dispatch a SLIDE action to go forward exactly one page when goForward() is called with no arguments', async () => {
      mockSliderInitialState = mockInitialInfiniteSliderState
      const { result } = renderHook(() => useSliderControls(true))

      await act(() => Promise.resolve())
      result.current.goForward()

      expect(mockDispatch).toHaveBeenCalledWith({
        type: 'SLIDE',
        payload: {
          currentSlide: 2,
          transform: mockInitialInfiniteSliderState.transformMap[2],
        },
      })
    })
    it('should dispatch a SLIDE action to go forward `step` pages when goForward() is called', async () => {
      mockSliderInitialState = mockInitialInfiniteSliderState
      const { result } = renderHook(() => useSliderControls(true))

      await act(() => Promise.resolve())
      result.current.goForward(4)

      expect(mockDispatch).toHaveBeenCalledWith({
        type: 'SLIDE',
        payload: {
          currentSlide: 4,
          transform: mockInitialInfiniteSliderState.transformMap[4],
        },
      })
    })
    it(`should dispatch SLIDE action to set \`currentSlide\` to the start of the last page if slider is not an infinite one`, async () => {
      mockSliderInitialState = mockInitialNonInfiniteSliderState
      mockSliderInitialState.currentSlide = 7
      const { result } = renderHook(() => useSliderControls(false))

      await act(() => Promise.resolve())
      result.current.goForward(4)

      expect(mockDispatch).toHaveBeenCalledWith({
        type: 'SLIDE',
        payload: {
          currentSlide: 5,
          transform: mockInitialInfiniteSliderState.transformMap[5],
        },
      })
    })
  })
})

describe('useTouchHandlers', () => {
  it.todo('should update transform value accordingly to touch movement')
  it.todo(
    "should not call slider controls if the user's touch movement delta was below the threshold"
  )
  it.todo(
    'should trigger forward slide movement if a user swipes from right to left'
  )
  it.todo(
    'should trigger backward slide movement if a user swipes from left to right'
  )
})

describe('useSliderVisibility', () => {
  it.todo(
    'should return ({ shouldRenderItem: false, isItemVisible: false }) if slide has never been seen'
  )
  it.todo(
    `should return ({ shouldRenderItem: true, isItemVisible: false }) if slide has been seen,
     but is not currently visible`
  )
  it.todo(
    'should return ({ shouldRenderItem: true, isItemVisible: true }) if slide is currently visible'
  )
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/SliderLayoutGroup.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import {
  render,
  getByLabelText,
  getByText,
  fireEvent,
} from '@vtex/test-tools/react'
import { useResponsiveValue } from 'vtex.responsive-values'

import SliderLayoutGroup from '../SliderLayoutGroup'
import SliderLayout from '../SliderLayout'

const mockedUseResponsiveValue = useResponsiveValue as jest.Mock<
  number | string
>

mockedUseResponsiveValue.mockImplementation(args => {
  if (typeof args === 'string') return args

  return 1
})

test('should move all sliders in a group together', () => {
  const { container } = render(
    <SliderLayoutGroup>
      <div id="first-slider">
        <SliderLayout showPaginationDots="never">
          <div>Slider 1 Item 1</div>
          <div>Slider 1 Item 2</div>
          <div>Slider 1 Item 3</div>
        </SliderLayout>
      </div>
      <div id="second-slider">
        <SliderLayout showPaginationDots="never">
          <div>Slider 2 Item 1</div>
          <div>Slider 2 Item 2</div>
          <div>Slider 2 Item 3</div>
        </SliderLayout>
      </div>
    </SliderLayoutGroup>
  )

  const firstSlider = container.querySelector('#first-slider') as HTMLElement
  const secondSlider = container.querySelector('#second-slider') as HTMLElement

  const nextArrow = getByLabelText(firstSlider, 'Next Slide')
  const previousArrow = getByLabelText(secondSlider, 'Previous Slide')

  // Go to the right
  fireEvent.click(nextArrow)

  let visibleSlideFirstSlider: HTMLElement | null = firstSlider.querySelector(
    '[aria-hidden=false]'
  )

  let visibleSlideSecondSlider: HTMLElement | null = secondSlider.querySelector(
    '[aria-hidden=false]'
  )

  if (!visibleSlideFirstSlider || !visibleSlideSecondSlider) {
    throw new Error('Could not find any visible slides')
  }

  let secondSlide1 = getByText(visibleSlideFirstSlider, 'Slider 1 Item 2')
  let secondSlide2 = getByText(visibleSlideSecondSlider, 'Slider 2 Item 2')

  expect(secondSlide1).toBeDefined()
  expect(secondSlide2).toBeDefined()

  // Go to the left
  fireEvent.click(previousArrow)

  visibleSlideFirstSlider = firstSlider.querySelector('[aria-hidden=false]')

  visibleSlideSecondSlider = secondSlider.querySelector('[aria-hidden=false]')

  if (!visibleSlideFirstSlider || !visibleSlideSecondSlider) {
    throw new Error('Could not find any visible slides')
  }

  secondSlide1 = getByText(visibleSlideFirstSlider, 'Slider 1 Item 1')
  secondSlide2 = getByText(visibleSlideSecondSlider, 'Slider 2 Item 1')

  expect(secondSlide1).toBeDefined()
  expect(secondSlide2).toBeDefined()
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/CssHandles.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
describe('CSS Handles API', () => {
  it.todo('should render with all expected CSS handles')
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/SliderTrack.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { render, fireEvent } from '@vtex/test-tools/react'

import SliderTrack, { CSS_HANDLES } from '../components/SliderTrack'
import { useSliderState } from '../components/SliderContext'
import {
  mockInitialSlides,
  mockInitialInfiniteSliderState,
  mockInitialNonInfiniteSliderState,
} from '../__fixtures__/SliderStateContext'
import { useContextCssHandles } from '../modules/cssHandles'
import { mockedUseContextCssHandlesFn } from '../__fixtures__/CssHandlesHelper'

const mockDispatch = jest.fn()

const mockedUseSliderState = useSliderState as jest.Mock

jest.mock('../components/SliderContext', () => ({
  useSliderState: jest.fn(),
  useSliderDispatch: () => mockDispatch,
}))

jest.mock('../modules/cssHandles', () => ({
  useContextCssHandles: jest.fn(),
}))

const mockedUseContextCssHandles = useContextCssHandles as jest.Mock<
  ReturnType<typeof useContextCssHandles>
>

mockedUseContextCssHandles.mockImplementation(() =>
  mockedUseContextCssHandlesFn(CSS_HANDLES)
)

describe('Basic rendering', () => {
  mockedUseSliderState.mockImplementation(() => mockInitialInfiniteSliderState)

  it('should render with correct width based on slides to render', () => {
    const TOTAL_ITEMS = 10
    const SLIDES_PER_PAGE = 5

    // slidesPerPage < totalItems
    const { getByTestId, rerender } = render(
      <SliderTrack
        centerMode="disabled"
        usePagination
        totalItems={TOTAL_ITEMS}
        infinite
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    let renderedSliderTrack = getByTestId('slider-track')

    // Since this slider is infinite, track width should be calculated
    // based on 2 * TOTAL_ITEMS.
    expect(renderedSliderTrack.style.width).toEqual(
      `${(2 * TOTAL_ITEMS * 100) / SLIDES_PER_PAGE}%`
    )

    // slidesPerPage === totalItems
    rerender(
      <SliderTrack centerMode="disabled" usePagination totalItems={5} infinite>
        {mockInitialSlides}
      </SliderTrack>
    )
    renderedSliderTrack = getByTestId('slider-track')
    expect(renderedSliderTrack.style.width).toEqual(
      `${(2 * TOTAL_ITEMS * 100) / SLIDES_PER_PAGE}%`
    )

    // slidesPerPage > totalItems
    rerender(
      <SliderTrack centerMode="disabled" usePagination totalItems={3} infinite>
        {mockInitialSlides}
      </SliderTrack>
    )
    renderedSliderTrack = getByTestId('slider-track')
    expect(renderedSliderTrack.style.width).toEqual('100%')
  })

  it('should render with correct translate3d(x, y, z), set to the initial slide', () => {
    // infinite slider
    const { getByTestId, rerender } = render(
      <SliderTrack centerMode="disabled" usePagination totalItems={10} infinite>
        {mockInitialSlides}
      </SliderTrack>
    )

    let renderedSliderTrack = getByTestId('slider-track')

    expect(renderedSliderTrack.style.transform).toEqual(
      `translate3d(-25%, 0, 0)`
    )

    // non-infinite slider
    mockedUseSliderState.mockImplementationOnce(
      () => mockInitialNonInfiniteSliderState
    )

    rerender(
      <SliderTrack
        centerMode="disabled"
        usePagination
        totalItems={10}
        infinite={false}
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    renderedSliderTrack = getByTestId('slider-track')
    expect(renderedSliderTrack.style.transform).toEqual(`translate3d(0%, 0, 0)`)
  })

  it('should render slides with correct indexes', () => {
    const TOTAL_ITEMS = 10
    const SLIDES_PER_PAGE = 5

    // infinite slider
    const { getByTestId, rerender } = render(
      <SliderTrack
        centerMode="disabled"
        usePagination
        totalItems={TOTAL_ITEMS}
        infinite
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    let renderedSliderTrack = getByTestId('slider-track')

    renderedSliderTrack.childNodes.forEach((element, idx) => {
      // This condition is due to the fact that this slider is infinite, so the
      // first SLIDES_PER_PAGE elements are a copy of the slider's last page,
      // and the last SLIDES_PER_PAGE elements are a copy of the first page.
      if (idx >= TOTAL_ITEMS + SLIDES_PER_PAGE || idx < SLIDES_PER_PAGE) {
        expect(element).not.toHaveAttribute('data-index')

        return
      }

      expect(element).toHaveAttribute(
        'data-index',
        `${idx + 1 - SLIDES_PER_PAGE}`
      )
    })

    // non-infinite slider
    mockedUseSliderState.mockImplementationOnce(
      () => mockInitialNonInfiniteSliderState
    )

    rerender(
      <SliderTrack
        centerMode="disabled"
        usePagination
        totalItems={TOTAL_ITEMS}
        infinite={false}
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    renderedSliderTrack = getByTestId('slider-track')

    renderedSliderTrack.childNodes.forEach((element, idx) => {
      expect(element).toHaveAttribute('data-index', `${idx + 1}`)
    })
  })

  it('should only render visible slides on initial mount', () => {
    const TOTAL_ITEMS = 10
    const SLIDES_PER_PAGE = 5
    const INDEXES = Array.from(new Array(10), (_, i) => i + 1)

    // infinite slider
    const { rerender, queryByText } = render(
      <SliderTrack
        centerMode="disabled"
        usePagination
        totalItems={TOTAL_ITEMS}
        infinite
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    let renderedSlides = INDEXES.map(idx =>
      queryByText(`I am a slide. My index is ${idx}`)
    ).filter(Boolean)

    expect(renderedSlides).toHaveLength(SLIDES_PER_PAGE)

    // non-infinite slider
    mockedUseSliderState.mockImplementationOnce(
      () => mockInitialNonInfiniteSliderState
    )

    rerender(
      <SliderTrack
        centerMode="disabled"
        usePagination
        totalItems={TOTAL_ITEMS}
        infinite={false}
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    renderedSlides = INDEXES.map(idx =>
      queryByText(`I am a slide. My index is ${idx}`)
    ).filter(Boolean)

    expect(renderedSlides).toHaveLength(SLIDES_PER_PAGE)
  })

  it('should render all slides on initial mount when slider is not using pagination', () => {
    const TOTAL_ITEMS = 10
    const INDEXES = Array.from(new Array(10), (_, i) => i + 1)

    // infinite slider
    const { rerender, queryByText, queryAllByText } = render(
      <SliderTrack
        centerMode="disabled"
        usePagination={false}
        totalItems={TOTAL_ITEMS}
        infinite
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    let renderedSlides: Array<HTMLElement | null | void> = INDEXES.map(idx =>
      expect(queryAllByText(`I am a slide. My index is ${idx}`)).toHaveLength(2)
    )

    expect(renderedSlides).toHaveLength(TOTAL_ITEMS)

    // non-infinite slider
    mockedUseSliderState.mockImplementationOnce(
      () => mockInitialNonInfiniteSliderState
    )

    rerender(
      <SliderTrack
        centerMode="disabled"
        usePagination={false}
        totalItems={TOTAL_ITEMS}
        infinite={false}
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    renderedSlides = INDEXES.map(idx =>
      queryByText(`I am a slide. My index is ${idx}`)
    ).filter(Boolean)

    expect(renderedSlides).toHaveLength(TOTAL_ITEMS)
  })
})

describe('Behavior upon interaction', () => {
  it('should correct slider state after infinite loop transitions', () => {
    const TOTAL_ITEMS = 10

    // This is a state where the slider is in its first page clone
    mockedUseSliderState.mockImplementation(() => ({
      ...mockInitialInfiniteSliderState,
      currentSlide: 10,
      transform: mockInitialInfiniteSliderState.transformMap[10],
    }))

    const { getByTestId, rerender } = render(
      <SliderTrack
        centerMode="disabled"
        totalItems={TOTAL_ITEMS}
        infinite
        usePagination
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    let renderedTrack = getByTestId('slider-track')

    fireEvent.transitionEnd(renderedTrack)

    expect(mockDispatch).toHaveBeenCalledWith({ type: 'DISABLE_TRANSITION' })
    expect(mockDispatch).toHaveBeenCalledWith({
      type: 'ADJUST_CURRENT_SLIDE',
      payload: {
        currentSlide: 0,
        transform: mockInitialInfiniteSliderState.transformMap[0],
      },
    })

    // This is a state where the slider is in its last page clone
    mockedUseSliderState.mockImplementation(() => ({
      ...mockInitialInfiniteSliderState,
      currentSlide: -1,
      transform: mockInitialInfiniteSliderState.transformMap[-1],
    }))

    rerender(
      <SliderTrack
        centerMode="disabled"
        totalItems={TOTAL_ITEMS}
        infinite
        usePagination
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    renderedTrack = getByTestId('slider-track')
    fireEvent.transitionEnd(renderedTrack)

    expect(mockDispatch).toHaveBeenCalledWith({ type: 'DISABLE_TRANSITION' })
    expect(mockDispatch).toHaveBeenCalledWith({
      type: 'ADJUST_CURRENT_SLIDE',
      payload: {
        currentSlide: 9,
        transform: mockInitialInfiniteSliderState.transformMap[9],
      },
    })
  })

  mockedUseSliderState.mockClear()
})

describe('Accessibility', () => {
  it('should have correct aria attributes set for each slide', () => {
    mockedUseSliderState.mockImplementation(
      () => mockInitialInfiniteSliderState
    )

    const TOTAL_ITEMS = 10
    const SLIDES_PER_PAGE = 5
    const INDEXES = Array.from(new Array(10), (_, i) => i + 1)

    const { getByTestId, getByLabelText } = render(
      <SliderTrack
        centerMode="disabled"
        usePagination
        totalItems={TOTAL_ITEMS}
        infinite
      >
        {mockInitialSlides}
      </SliderTrack>
    )

    const renderedSliderTrack = getByTestId('slider-track')

    const clones = renderedSliderTrack.querySelectorAll(
      "[role='none presentation']"
    )

    expect(clones).toHaveLength(SLIDES_PER_PAGE * 2)

    const visibleSlides = renderedSliderTrack.querySelectorAll(
      '[aria-hidden=false]'
    )

    expect(visibleSlides).toHaveLength(SLIDES_PER_PAGE)

    const nonCloneSlides = renderedSliderTrack.querySelectorAll(
      '[aria-roledescription=slide]'
    )

    expect(nonCloneSlides).toHaveLength(TOTAL_ITEMS)

    const slidesWithLabels = INDEXES.map(idx =>
      getByLabelText(`Current slide is ${idx} of ${TOTAL_ITEMS}`)
    ).filter(Boolean)

    expect(slidesWithLabels).toHaveLength(TOTAL_ITEMS)
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/SliderContext.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { renderHook } from '@vtex/test-tools/react'

import { useSliderDispatch, useSliderState } from '../components/SliderContext'

describe('Initialization', () => {
  it.todo(
    'should correctly resolve initial navigationStep based on received props'
  )
  it.todo(
    'should correctly resolve initial slidesPerPage based on received props'
  )
  it.todo('should correctly initiate transformMap')
  it.todo('should correctly create clones if slider is infinite')
  it.todo('should correctly initiate transformMap in an infinite slider')
})

/**
 * Most of the actions are pretty simple, since they just update the state using the
 * values from their payloads directly.
 * That being said, if there's an issue in the slider's state behavior, it's likely
 * caused by dispatch callers, and not the reducer itself. Check './hooks.test.tsx'.
 */
describe('Reducer actions', () => {
  it.todo('SLIDE')
  it.todo('TOUCH')
  it.todo('ADJUST_ON_RESIZE')
  it.todo('DISABLE_TRANSITION')
  it.todo('ADJUST_CURRENT_SLIDE')
  it.todo('ADJUST_CONTEXT_VALUES')
})

describe('useSliderState and useSliderDispatch hooks', () => {
  it('should throw an error when used outside SliderContext', () => {
    const { result: sliderStateResult } = renderHook(() => useSliderState())

    const expectedUseStateError = new Error(
      'useSliderState must be used within a SliderContextProvider'
    )

    expect(sliderStateResult.error).toEqual(expectedUseStateError)

    const { result: sliderDispatchResult } = renderHook(() =>
      useSliderDispatch()
    )

    const expectedUseDispatchError = new Error(
      'useSliderDispatch must be used within a SliderContextProvider'
    )

    expect(sliderDispatchResult.error).toEqual(expectedUseDispatchError)
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/Arrows.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { Fragment } from 'react'
import { render, fireEvent } from '@vtex/test-tools/react'

import { useSliderState } from '../components/SliderContext'
import Arrow, { CSS_HANDLES } from '../components/Arrow'
import { useContextCssHandles } from '../modules/cssHandles'
import { mockedUseContextCssHandlesFn } from '../__fixtures__/CssHandlesHelper'

const mockGoForward = jest.fn()
const mockGoBack = jest.fn()

const mockedUseSliderState = useSliderState as jest.Mock

jest.mock('../components/SliderContext', () => ({
  useSliderState: jest.fn(),
}))

jest.mock('../hooks/useSliderControls', () => ({
  useSliderControls: () => ({
    goForward: mockGoForward,
    goBack: mockGoBack,
  }),
}))

jest.mock('../modules/cssHandles', () => ({
  useContextCssHandles: jest.fn(),
}))

const mockedUseContextCssHandles = useContextCssHandles as jest.Mock<
  ReturnType<typeof useContextCssHandles>
>

mockedUseContextCssHandles.mockImplementation(() =>
  mockedUseContextCssHandlesFn(CSS_HANDLES)
)

beforeEach(() => {
  mockGoForward.mockClear()
  mockGoBack.mockClear()
  // eslint-disable-next-line prettier/prettier
})

describe('Accessibility', () => {
  mockedUseSliderState.mockImplementation(() => ({
    currentSlide: 0,
    slidesPerPage: 5,
    navigationStep: 5,
  }))

  it('should render with correct aria attributes in each arrow', () => {
    const expectedLabelLeft = 'Previous Slide'
    const expectedLabelRight = 'Next Slide'
    const expectedContainerRole = 'button'

    const { queryAllByRole, queryByLabelText } = render(
      <Fragment>
        <Arrow
          infinite
          arrowSize={25}
          totalItems={20}
          orientation="left"
          controls="slider-items"
        />
        <Arrow
          infinite
          arrowSize={25}
          totalItems={20}
          orientation="right"
          controls="slider-items"
        />
      </Fragment>
    )

    expect(queryAllByRole(expectedContainerRole)).toHaveLength(2)
    expect(queryByLabelText(expectedLabelLeft)).toBeTruthy()
    expect(queryByLabelText(expectedLabelRight)).toBeTruthy()
  })
})

describe('Behavior upon interaction', () => {
  mockedUseSliderState.mockImplementation(() => ({
    currentSlide: 0,
    slidesPerPage: 5,
    navigationStep: 5,
  }))

  it('should go to the next page if right arrow is clicked', () => {
    const { getByTestId } = render(
      <Arrow
        infinite
        arrowSize={25}
        totalItems={20}
        orientation="right"
        controls="slider-items"
      />
    )

    const rightArrow = getByTestId('icon-caret-right')

    fireEvent.click(rightArrow)
    expect(mockGoForward).toHaveBeenCalledTimes(1)
  })

  it('should go to the previous page if left arrow is clicked', () => {
    const { getByTestId } = render(
      <Arrow
        infinite
        arrowSize={25}
        totalItems={20}
        orientation="left"
        controls="slider-items"
      />
    )

    const leftArrow = getByTestId('icon-caret-left')

    fireEvent.click(leftArrow)
    expect(mockGoBack).toHaveBeenCalledTimes(1)
  })

  it('should not go back on click if the slider is not infinite and left-end is reached', () => {
    const { getByTestId } = render(
      <Arrow
        infinite={false}
        arrowSize={25}
        totalItems={10}
        orientation="left"
        controls="slider-items"
      />
    )

    const leftArrow = getByTestId('icon-caret-left')

    fireEvent.click(leftArrow)
    expect(mockGoBack).toHaveBeenCalledTimes(0)
  })

  it('should not go forward on click if if the slider is not infinite and right-end is reached', () => {
    mockedUseSliderState.mockImplementation(() => ({
      currentSlide: 5,
      slidesPerPage: 5,
      navigationStep: 5,
    }))

    const { getByTestId } = render(
      <Arrow
        infinite={false}
        arrowSize={25}
        totalItems={10}
        orientation="right"
        controls="slider-items"
      />
    )

    const rightArrow = getByTestId('icon-caret-right')

    fireEvent.click(rightArrow)
    expect(mockGoForward).toHaveBeenCalledTimes(0)
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/__tests__/Slider.test.tsx from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { render } from '@vtex/test-tools/react'

import Slider, { CSS_HANDLES } from '../components/Slider'
import { mockInitialInfiniteSliderState } from '../__fixtures__/SliderStateContext'
import { mockedUseContextCssHandlesFn } from '../__fixtures__/CssHandlesHelper'
import { useContextCssHandles } from '../modules/cssHandles'

let mockDeviceDetectorReturn = { device: 'desktop', isMobile: false }

jest.mock('../hooks/useTouchHandlers', () => ({
  useTouchHandlers: () => ({
    onTouchStart: jest.fn(),
    onTouchMove: jest.fn(),
    onTouchEnd: jest.fn(),
  }),
}))

jest.mock('../components/SliderContext', () => ({
  useSliderState: () => mockInitialInfiniteSliderState,
  useSliderDispatch: () => jest.fn(),
}))

jest.mock('vtex.device-detector', () => ({
  useDevice: jest.fn(() => mockDeviceDetectorReturn),
}))

jest.mock('../modules/cssHandles', () => ({
  useContextCssHandles: jest.fn(),
}))

const mockedUseContextCssHandles = useContextCssHandles as jest.Mock<
  ReturnType<typeof useContextCssHandles>
>

mockedUseContextCssHandles.mockImplementation(() =>
  mockedUseContextCssHandlesFn(CSS_HANDLES)
)

describe('Basic rendering', () => {
  it('should render complete slider, with arrows and pagination dots on default settings', () => {
    const { getByTestId, getByLabelText } = render(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="always"
        showPaginationDots="always"
        arrowSize={25}
        fullWidth
        infinite
        usePagination
      />
    )

    const expectedCarouselRole = getByLabelText('slider')
    const leftArrow = getByTestId('icon-caret-left')
    const rightArrow = getByTestId('icon-caret-right')
    const paginationDots = getByLabelText('Slider pagination dots')

    expect(expectedCarouselRole).toBeInTheDocument()
    expect(leftArrow).toBeInTheDocument()
    expect(rightArrow).toBeInTheDocument()
    expect(paginationDots).toBeInTheDocument()
  })

  it('should render without pagination features and use x-scrolling if usePagination is set to false', () => {
    const { getByLabelText, queryByTestId, queryByLabelText } = render(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="always"
        showPaginationDots="always"
        usePagination={false}
        arrowSize={25}
        fullWidth
        infinite
      />
    )

    const renderedSlider = getByLabelText('slider')
    const sliderTrackContainer = renderedSlider.firstChild

    const leftArrow = queryByTestId('icon-caret-left')
    const rightArrow = queryByTestId('icon-caret-right')
    const paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).not.toBeInTheDocument()
    expect(rightArrow).not.toBeInTheDocument()
    expect(paginationDots).not.toBeInTheDocument()
    expect(sliderTrackContainer).toHaveClass('overflow-x-scroll')
  })

  it('should render without pagination features if there are not enough slides to fill a page', () => {
    const { getByLabelText, queryByTestId, queryByLabelText } = render(
      <Slider
        centerMode="disabled"
        totalItems={3}
        itemsPerPage={5}
        showNavigationArrows="always"
        showPaginationDots="always"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    const leftArrow = queryByTestId('icon-caret-left')
    const rightArrow = queryByTestId('icon-caret-right')
    const paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).not.toBeInTheDocument()
    expect(rightArrow).not.toBeInTheDocument()
    expect(paginationDots).not.toBeInTheDocument()

    const renderedSlider = getByLabelText('slider')
    const sliderTrackContainer = renderedSlider.firstChild

    // Make sure that the slider renders without x-scrolling, since usePagination is set to true
    expect(sliderTrackContainer).not.toHaveClass('overflow-x-scroll')
  })

  it('should add appropriate padding in the x-axis if slider is not a full width one', () => {
    const { getByLabelText } = render(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="always"
        showPaginationDots="always"
        fullWidth={false}
        arrowSize={25}
        usePagination
        infinite
      />
    )

    const sliderSectionElement = getByLabelText('slider')

    expect(sliderSectionElement).toHaveStyle({
      paddingLeft: '50px',
      paddingRight: '50px',
    })
  })
})

describe('Behavior upon interaction', () => {
  it('should correctly toggle arrows and pagination dots visibility on mobile devices', () => {
    mockDeviceDetectorReturn = { device: 'phone', isMobile: true }

    // 'mobileOnly'
    const { queryByTestId, queryByLabelText, rerender } = render(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="mobileOnly"
        showPaginationDots="mobileOnly"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    let leftArrow = queryByTestId('icon-caret-left')
    let rightArrow = queryByTestId('icon-caret-right')
    let paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).toBeInTheDocument()
    expect(rightArrow).toBeInTheDocument()
    expect(paginationDots).toBeInTheDocument()

    // 'desktopOnly'
    rerender(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="desktopOnly"
        showPaginationDots="desktopOnly"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    leftArrow = queryByTestId('icon-caret-left')
    rightArrow = queryByTestId('icon-caret-right')
    paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).not.toBeInTheDocument()
    expect(rightArrow).not.toBeInTheDocument()
    expect(paginationDots).not.toBeInTheDocument()

    // 'always'
    rerender(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="always"
        showPaginationDots="always"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    leftArrow = queryByTestId('icon-caret-left')
    rightArrow = queryByTestId('icon-caret-right')
    paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).toBeInTheDocument()
    expect(rightArrow).toBeInTheDocument()
    expect(paginationDots).toBeInTheDocument()

    // 'never'
    rerender(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="never"
        showPaginationDots="never"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    leftArrow = queryByTestId('icon-caret-left')
    rightArrow = queryByTestId('icon-caret-right')
    paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).not.toBeInTheDocument()
    expect(rightArrow).not.toBeInTheDocument()
    expect(paginationDots).not.toBeInTheDocument()
  })

  it('should correctly toggle arrows and pagination dots visibility on desktop devices', () => {
    mockDeviceDetectorReturn = { device: 'desktop', isMobile: false }

    // 'mobileOnly'
    const { queryByTestId, queryByLabelText, rerender } = render(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="mobileOnly"
        showPaginationDots="mobileOnly"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    let leftArrow = queryByTestId('icon-caret-left')
    let rightArrow = queryByTestId('icon-caret-right')
    let paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).not.toBeInTheDocument()
    expect(rightArrow).not.toBeInTheDocument()
    expect(paginationDots).not.toBeInTheDocument()

    // 'desktopOnly'
    rerender(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="desktopOnly"
        showPaginationDots="desktopOnly"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    leftArrow = queryByTestId('icon-caret-left')
    rightArrow = queryByTestId('icon-caret-right')
    paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).toBeInTheDocument()
    expect(rightArrow).toBeInTheDocument()
    expect(paginationDots).toBeInTheDocument()

    // 'always'
    rerender(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="always"
        showPaginationDots="always"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    leftArrow = queryByTestId('icon-caret-left')
    rightArrow = queryByTestId('icon-caret-right')
    paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).toBeInTheDocument()
    expect(rightArrow).toBeInTheDocument()
    expect(paginationDots).toBeInTheDocument()

    // 'never'
    rerender(
      <Slider
        centerMode="disabled"
        totalItems={10}
        itemsPerPage={5}
        showNavigationArrows="never"
        showPaginationDots="never"
        arrowSize={25}
        usePagination
        fullWidth
        infinite
      />
    )

    leftArrow = queryByTestId('icon-caret-left')
    rightArrow = queryByTestId('icon-caret-right')
    paginationDots = queryByLabelText('Slider pagination dots')

    expect(leftArrow).not.toBeInTheDocument()
    expect(rightArrow).not.toBeInTheDocument()
    expect(paginationDots).not.toBeInTheDocument()
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/hooks/useTouchHandlers.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useState } from 'react'

import { useSliderControls } from './useSliderControls'
import {
  SliderLayoutProps,
  useSliderDispatch,
  useSliderState,
} from '../components/SliderContext'

const SWIPE_THRESHOLD = 75
const TOUCH_MOVE_DAMPING = 25

export const useTouchHandlers = ({
  infinite,
  centerMode,
}: {
  infinite: boolean
  centerMode: SliderLayoutProps['centerMode']
}) => {
  const dispatch = useSliderDispatch()
  const { transform } = useSliderState()
  const { goForward, goBack } = useSliderControls(infinite)

  const [touchState, setTouchState] = useState({
    touchStartX: 0,
    touchInitialTransform: transform,
  })

  const onTouchStart = (e: React.TouchEvent) => {
    const startX = e.touches[0].clientX

    setTouchState({ touchStartX: startX, touchInitialTransform: transform })
  }

  const onTouchMove = (e: React.TouchEvent) => {
    const currentX = e.touches[0].clientX
    const touchMoveDelta = currentX - touchState.touchStartX

    const newTransform =
      touchState.touchInitialTransform +
      touchMoveDelta /
        ((centerMode !== 'disabled' ? 2 : 1) * TOUCH_MOVE_DAMPING)

    dispatch({
      type: 'TOUCH',
      payload: { transform: newTransform, isOnTouchMove: true },
    })
  }

  const onTouchEnd = (e: React.TouchEvent) => {
    const endX = e.changedTouches[0].clientX
    const delta = endX - touchState.touchStartX

    if (Math.abs(delta) > SWIPE_THRESHOLD) {
      // Swipe from left to right
      if (delta > 0) {
        goBack()
      }

      // Swipe from right to left
      if (delta < 0) {
        goForward()
      }
    } else {
      // Ignore the swipe if SWIPE_THRESHOLD is not reached
      dispatch({
        type: 'TOUCH',
        payload: {
          transform: touchState.touchInitialTransform,
          isOnTouchMove: false,
        },
      })
    }

    setTouchState({ touchStartX: 0, touchInitialTransform: transform })
    dispatch({
      type: 'TOUCH',
      payload: {
        isOnTouchMove: false,
      },
    })
  }

  return { onTouchEnd, onTouchStart, onTouchMove }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/hooks/useScreenResize.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useEffect } from 'react'

import { useSliderDispatch, useSliderState } from '../components/SliderContext'

export const useScreenResize = (infinite: boolean, itemsPerPage: number) => {
  const { navigationStep, isPageNavigationStep, totalItems } = useSliderState()
  const dispatch = useSliderDispatch()

  useEffect(() => {
    const newSlidesPerPage =
      totalItems <= itemsPerPage ? totalItems : itemsPerPage

    const newNavigationStep = isPageNavigationStep
      ? newSlidesPerPage
      : navigationStep

    const setNewState = (shouldCorrectItemPosition: boolean) => {
      dispatch({
        type: 'ADJUST_ON_RESIZE',
        payload: {
          shouldCorrectItemPosition,
          slidesPerPage: newSlidesPerPage,
          navigationStep: newNavigationStep,
        },
      })
    }

    const onResize = (value?: UIEvent): void => {
      setNewState(!value || infinite)
    }

    setNewState(false)

    window.addEventListener('resize', onResize)

    return () => window.removeEventListener('resize', onResize)
  }, [
    infinite,
    dispatch,
    totalItems,
    itemsPerPage,
    isPageNavigationStep,
    navigationStep,
  ])
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/hooks/useAutoplay.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useEffect } from 'react'

import { useSliderState } from '../components/SliderContext'
import { useSliderControls } from './useSliderControls'
import useHovering from './useHovering'

export const useAutoplay = (
  infinite: boolean,
  containerRef: React.RefObject<HTMLDivElement>
) => {
  const { autoplay } = useSliderState()
  const { isHovering } = useHovering(containerRef)

  const shouldStop = autoplay?.stopOnHover && isHovering

  const { goForward } = useSliderControls(infinite)

  useEffect(() => {
    if (!autoplay) {
      return
    }

    const timeout = setTimeout(() => {
      goForward()
    }, autoplay.timeout)

    shouldStop && clearTimeout(timeout)

    return () => clearTimeout(timeout)
  }, [goForward, shouldStop, autoplay])
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/hooks/useSliderControls.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useSliderDispatch, useSliderState } from '../components/SliderContext'
import { useSliderGroupDispatch } from '../SliderLayoutGroup'

export const useSliderControls = (infinite: boolean) => {
  const {
    currentSlide,
    slidesPerPage,
    totalItems,
    navigationStep,
    transformMap,
  } = useSliderState()

  const dispatch = useSliderDispatch()
  const groupDispatch = useSliderGroupDispatch()

  const goBack = (step?: number) => {
    let nextSlide = 0
    let nextTransformValue = 0
    const activeNavigationStep = step ?? navigationStep

    const nextMaximumSlides = currentSlide - activeNavigationStep

    if (nextMaximumSlides >= 0) {
      /** Have more slides hidden on left */
      nextSlide = nextMaximumSlides
      nextTransformValue = transformMap[nextSlide]
    } else if (currentSlide !== 0) {
      /** Prevent over-slide */
      nextSlide = 0
      nextTransformValue = 0
    } else if (infinite) {
      /** Have more slides hidden on left */
      nextSlide = nextMaximumSlides
      nextTransformValue = transformMap[nextSlide]
    }

    if (groupDispatch) {
      groupDispatch({
        type: 'SLIDE',
        payload: {
          currentSlide: nextSlide,
          transform: nextTransformValue,
        },
      })
    }

    dispatch({
      type: 'SLIDE',
      payload: {
        transform: nextTransformValue,
        currentSlide: nextSlide,
      },
    })
  }

  const goForward = (step?: number) => {
    let nextSlide = 0
    let nextTransformValue = 0
    const activeNavigationStep = step ?? navigationStep

    const nextMaximumSlides =
      currentSlide + 1 + slidesPerPage + activeNavigationStep

    if (nextMaximumSlides <= totalItems) {
      /** There are some slides hidden on the right */
      nextSlide = currentSlide + activeNavigationStep
      nextTransformValue = transformMap[nextSlide]
    } else if (!infinite || currentSlide < totalItems - slidesPerPage) {
      /** Prevent over-slide */
      nextSlide = totalItems - slidesPerPage
      nextTransformValue = transformMap[nextSlide]
    } else if (infinite) {
      nextSlide = currentSlide + activeNavigationStep
      nextTransformValue = transformMap[nextSlide]
    }

    if (groupDispatch) {
      groupDispatch({
        type: 'SLIDE',
        payload: {
          currentSlide: nextSlide,
          transform: nextTransformValue,
        },
      })
    }

    dispatch({
      type: 'SLIDE',
      payload: {
        transform: nextTransformValue,
        currentSlide: nextSlide,
      },
    })
  }

  return { goForward, goBack }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/hooks/useSliderVisibility.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useEffect, useRef } from 'react'

import { SliderLayoutProps } from '../components/SliderContext'

const isSlideVisible = ({
  index,
  currentSlide,
  slidesToShow,
  totalItems,
  centerMode,
}: {
  index: number
  currentSlide: number
  slidesToShow: number
  totalItems: number
  centerMode: SliderLayoutProps['centerMode']
}): boolean => {
  const isClonedSlide = currentSlide < 0 || currentSlide >= totalItems
  let isVisible = index >= currentSlide && index < currentSlide + slidesToShow

  if (centerMode !== 'disabled') {
    isVisible =
      isVisible ||
      (index + 1 >= currentSlide && index + 1 < currentSlide + slidesToShow) ||
      (index - 1 >= currentSlide && index - 1 < currentSlide + slidesToShow)
  }

  return isVisible || isClonedSlide
}

export const useSliderVisibility = ({
  currentSlide,
  slidesPerPage,
  totalItems,
  centerMode,
}: {
  currentSlide: number
  slidesPerPage: number
  totalItems: number
  centerMode: SliderLayoutProps['centerMode']
}) => {
  /** Keeps track of slides that have been visualized before.
   * We want to keep rendering them because the issue is mostly rendering
   * slides that might never be viewed; On the other hand, hiding slides
   * that were visible causes visual glitches */
  const visitedSlides = useRef<Set<number>>(new Set())

  useEffect(() => {
    for (let i = 0; i < slidesPerPage; i++) {
      visitedSlides.current.add(currentSlide + i)
    }
  }, [currentSlide, slidesPerPage])

  const isItemVisible = (index: number) =>
    isSlideVisible({
      index,
      currentSlide,
      slidesToShow: slidesPerPage,
      totalItems,
      centerMode,
    })

  const shouldRenderItem = (index: number) => {
    return visitedSlides.current.has(index) || isItemVisible(index)
  }

  return { shouldRenderItem, isItemVisible }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of slider-layout/react/hooks/useKeyboardArrows.ts from the slider-layout VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useEffect } from 'react'

/**
 * Adds event listener to call passed functions by pressing the left or right arrows
 * @param onLeft Function that is called on left arrow key press
 * @param onRight Function that is called on arrow arrow key press
 */
const useKeyboardArrows = (onLeft: () => void, onRight: () => void) => {
  useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      event.code === 'ArrowLeft' && onLeft()
      event.code === 'ArrowRight' && onRight()
    }

    window.addEventListener('keydown', onKeyDown)

    return () => {
      window.removeEventListener('keydown', onKeyDown)
    }
  }, [onLeft, onRight])
}

export default useKeyboardArrows

```