[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 store-components/react/components/BuyButton/hooks/useMarketingSessionParams.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useEffect, useState } from 'react'
import { path, pathOr } from 'ramda'

const getUtmParams = publicFields => ({
  source: path(['utm_source', 'value'], publicFields),
  medium: path(['utm_medium', 'value'], publicFields),
  campaign: path(['utm_campaign', 'value'], publicFields),
})

const getUtmiParams = publicFields => ({
  campaign: path(['utmi_cp', 'value'], publicFields),
  page: path(['utmi_p', 'value'], publicFields),
  part: path(['utmi_pc', 'value'], publicFields),
})

const getSessionPromiseFromWindow = () =>
  !window.__RENDER_8_SESSION__ || !window.__RENDER_8_SESSION__.sessionPromise
    ? Promise.resolve(null)
    : window.__RENDER_8_SESSION__.sessionPromise

const useMarketingSessionParams = () => {
  const [utmParams, setUtmParams] = useState(undefined)
  const [utmiParams, setUtmiParams] = useState(undefined)

  useEffect(() => {
    getSessionPromiseFromWindow()
      .then(data => {
        const publicFields = pathOr(
          {},
          ['response', 'namespaces', 'public'],
          data
        )

        if (Object.keys(publicFields).length === 0) {
          return
        }

        setUtmParams(getUtmParams(publicFields))
        setUtmiParams(getUtmiParams(publicFields))
      })
      .catch(() => {
        // Do nothing!
      })
  }, [])

  return { utmParams, utmiParams }
}

export default useMarketingSessionParams

```


[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 store-components/react/components/Greeting/queries/orderForm.gql from the store-components VTEX app.

[Assistant]
Here's the implementation:

```graphql
query orderFormQuery {
  minicart @client {
    orderForm
  }
}

```


[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 store-components/react/components/Slider/utils/ItemsPerPage.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { path } from 'ramda'

function getItemWidth(slick, maxWidth) {
  const slidesNodeList = path(
    ['innerSlider', 'list', 'childNodes', '0', 'childNodes'],
    slick
  )

  let itemWidth = null

  if (slidesNodeList) {
    const slidesArray = Array.prototype.slice.call(slidesNodeList)

    // eslint-disable-next-line array-callback-return
    slidesArray.map(slide => {
      const attributes = Array.prototype.slice.call(slide.attributes)

      // eslint-disable-next-line array-callback-return
      attributes.map(attr => {
        // eslint-disable-next-line vtex/prefer-early-return
        if (attr.nodeName === 'data-index' && attr.nodeValue === '0') {
          itemWidth = path(['childNodes', '0', 'clientWidth'], slide)
          if (maxWidth && maxWidth < itemWidth) itemWidth = maxWidth
        }
      })
    })
  }

  return itemWidth || maxWidth
}

/**
 * Returns the correct number of items to be inside the slider without reduce the item width.
 */
// eslint-disable-next-line max-params
export default function getItemsPerPage(
  slick,
  slideWidth,
  defaultItemWidth,
  actualItemsPerPage
) {
  if (slideWidth) {
    const shelfItemWidth = getItemWidth(slick, defaultItemWidth)
    const maxItemsPerPage = Math.floor(slideWidth / shelfItemWidth)

    if (actualItemsPerPage >= maxItemsPerPage) {
      return maxItemsPerPage || 1
    }
  }

  return actualItemsPerPage
}

```


[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 store-components/react/components/Slider/components/Arrow.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

/**
 * Arrow component. It's an overriden component of react-slick that controls
 * the slide transition.
 */
// eslint-disable-next-line react/prefer-stateless-function
export default class Arrow extends Component {
  render() {
    const { className, style, onClick, cssClass, customClasses } = this.props
    const arrowClasses = classNames({
      [`${className}`]: className,
      [`${cssClass}`]: cssClass,
      [`${customClasses}`]: customClasses,
    })

    return (
      // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
      <div
        className={`${arrowClasses}`}
        style={{ ...style }}
        onClick={onClick}
      />
    )
  }
}

Arrow.propTypes = {
  /** (react-slick prop) Css class of the element. */
  className: PropTypes.string,
  /** (react-slick prop) Custom style of the element. */
  style: PropTypes.object,
  /** (react-slick prop) Function to be called when the arrow is clicked. */
  onClick: PropTypes.func,
  /** Specifies wich css class the arrow will receive. */
  cssClass: PropTypes.string.isRequired,
  /** Custom classes */
  customClasses: PropTypes.string,
}

```


[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 store-components/react/components/Slider/components/Dots.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

/**
 * Dots component. It's an overriden component of react-slick that controls
 * the slide transition;
 */
// eslint-disable-next-line react/prefer-stateless-function
export default class Dots extends Component {
  render() {
    const { className, style, dots, cssClass, customClass } = this.props
    const dotsClasses = classNames([className, cssClass, customClass])

    return (
      <div className={dotsClasses}>
        <ul className="ma0 pa0" style={{ ...style }}>
          {dots}
        </ul>
      </div>
    )
  }
}

Dots.propTypes = {
  /** (react-slick prop) Css class of the element. */
  className: PropTypes.string,
  /** Dots custom classes passed by props. */
  customClass: PropTypes.string,
  /** (react-slick prop) Custom style of the element. */
  style: PropTypes.object,
  /** (react-slick prop) Dots that will be displayed */
  dots: PropTypes.node.isRequired,
  /** Specifies wich css the dots will receive. */
  cssClass: PropTypes.string.isRequired,
}

```


[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 store-components/react/components/ProductImages/utils/generateUrl.d.ts from the store-components VTEX app.

[Assistant]
Here's the implementation:

```typescript
export function changeImageUrlSize(
  url: string,
  width: number | string,
  height: number | string
): string

```


[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 store-components/react/components/ProductImages/utils/aspectRatioUtil.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { changeImageUrlSize } from './generateUrl'

type AspectRatio = string | number

/** Parses ratio values into a multiplier to set the image height.
 * For example, turns "3:4" into 1.333, so the image height will be
 * 1.333 times its width.
 */
const parseAspectRatio = (input?: AspectRatio | null) => {
  if (!input) {
    return null
  }

  if (typeof input === 'string') {
    if (input === 'auto') {
      return null
    }

    const separator = ':'
    const data = input.split(separator)

    if (data.length !== 2) {
      return null
    }

    const [width, height] = data
    const ratio = parseFloat(height) / parseFloat(width)

    if (typeof ratio !== 'number' || window.isNaN(ratio)) {
      return null
    }

    return ratio
  }

  if (typeof input === 'number') {
    return input
  }

  return null
}

export const imageUrl = (
  src: string,
  size: number,
  maxSize: number,
  aspectRatio?: AspectRatio
  // eslint-disable-next-line max-params
) => {
  let width = size
  let height: number | string = 'auto'

  if (aspectRatio && aspectRatio !== 'auto') {
    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
    height = size * (parseAspectRatio(aspectRatio) || 1)

    if (width > maxSize) {
      height /= width / maxSize
      width = maxSize
    }

    if (height > maxSize) {
      width /= height / maxSize
      height = maxSize
    }

    width = Math.round(width)
    height = Math.round(height)
  } else {
    width = Math.min(maxSize, width)
  }

  return changeImageUrlSize(src, width, height)
}

```


[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 store-components/react/components/ProductImages/utils/enums.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
export const THUMBS_POSITION_HORIZONTAL = {
  LEFT: 'left',
  RIGHT: 'right',
}

export const THUMBS_ORIENTATION = {
  VERTICAL: 'vertical',
  HORIZONTAL: 'horizontal',
}

export const DISPLAY_MODE = {
  CAROUSEL: 'carousel',
  LIST: 'list',
  FIRST_IMAGE: 'first-image',
}

export const THUMBS_VISIBILITY = {
  VISIBLE: 'visible',
  HIDDEN: 'hidden',
}

export const DEFAULT_EXCLUDE_IMAGE_WITH = 'skuvariation'

```


[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 store-components/react/components/ProductImages/utils/generateUrl.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
export const DEFAULT_WIDTH = 'auto'
export const DEFAULT_HEIGHT = 'auto'
export const MAX_WIDTH = 3000
export const MAX_HEIGHT = 4000

/**
 * Having the url below as base for the LEGACY file manager,
 * https://storecomponents.vteximg.com.br/arquivos/ids/155472/Frame-3.jpg?v=636793763985400000
 * the following regex will match https://storecomponents.vteximg.com.br/arquivos/ids/155472
 *
 * Also matches urls with defined sizes like:
 * https://storecomponents.vteximg.com.br/arquivos/ids/155473-160-auto
 * @type {RegExp}
 *
 * On the new vtex.file-manager isn't necessary replace the URL, just add the param on the querystring, like:
 * "?width=WIDTH&height=HEIGHT&aspect=true"
 *
 */
const baseUrlRegex = new RegExp(/.+ids\/(\d+)/)

function getParamFromUrl(url, name) {
  return (url?.split(`${name}=`)[1] || '')?.split('&')[0]
}

export function cleanImageUrl(imageUrl) {
  const cleanUrlResult = baseUrlRegex.exec(imageUrl)
  const vParam = getParamFromUrl(imageUrl, 'v')

  if (cleanUrlResult && cleanUrlResult.length > 0) {
    return {
      cleanUrl: cleanUrlResult[0],
      vParam,
    }
  }

  return { cleanUrl: imageUrl }
}

function replaceLegacyFileManagerUrl(imageUrl, width, height) {
  const legacyUrlPattern = '/arquivos/ids/'
  const isLegacyUrl = imageUrl.includes(legacyUrlPattern)

  if (!isLegacyUrl) return imageUrl

  const { vParam, cleanUrl } = cleanImageUrl(imageUrl)

  return vParam
    ? `${cleanUrl}-${width}-${height}?v=${vParam}`
    : `${cleanUrl}-${width}-${height}`
}

export function changeImageUrlSize(
  imageUrl,
  width = DEFAULT_WIDTH,
  height = DEFAULT_HEIGHT
) {
  if (!imageUrl) return
  typeof width === 'number' && (width = Math.min(width, MAX_WIDTH))
  typeof height === 'number' && (height = Math.min(height, MAX_HEIGHT))

  const normalizedImageUrl = replaceLegacyFileManagerUrl(
    imageUrl,
    width,
    height
  )

  const queryStringSeparator = normalizedImageUrl.includes('?') ? '&' : '?'

  return `${normalizedImageUrl}${queryStringSeparator}width=${width}&height=${height}&aspect=true`
}

```


[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 store-components/react/components/ProductImages/utils/generateImageConfig.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { changeImageUrlSize } from './generateUrl'

const thumbnailSize = 160

export default function generateImageConfig(image) {
  return {
    imageUrl: image.imageUrl,
    thumbnailUrl: changeImageUrlSize(image.imageUrl, thumbnailSize),
    imageText: image.imageText,
    imageLabel: image.imageLabel,
  }
}

```


[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 store-components/react/components/ProductImages/components/ProductImageContext.ts from the store-components VTEX app.

[Assistant]
Here's the implementation:

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

export interface State {
  src: string
  alt: string
}

const ProductImageContext = createContext<State | Record<string, any>>({})

export default ProductImageContext

```


[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 store-components/react/components/ProductImages/components/ProductImage.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, useMemo, useRef } from 'react'
import { Modal } from 'vtex.modal-layout'
import { useCssHandles, applyModifiers } from 'vtex.css-handles'

import Zoomable, { ZoomMode } from './Zoomable'
import { imageUrl } from '../utils/aspectRatioUtil'
import ProductImageContext, {
  State as ProductImageState,
} from './ProductImageContext'

const IMAGE_SIZES = [600, 800, 1200]
const DEFAULT_SIZE = 800
const MAX_SIZE = 2048

interface Props {
  index: number
  src: string
  alt: string
  imageLabel?: string
  zoomMode: ZoomMode
  zoomFactor: number
  aspectRatio?: AspectRatio
  maxHeight?: number | string
  ModalZoomElement?: typeof Modal
}

type AspectRatio = string | number

const CSS_HANDLES = ['productImage', 'productImageTag', 'productImageLabel']

const ProductImage: FC<Props> = ({
  index,
  src,
  alt,
  imageLabel,
  zoomFactor = 2,
  maxHeight = 600,
  ModalZoomElement,
  aspectRatio = 'auto',
  zoomMode = 'in-place-click',
}) => {
  const srcSet = useMemo(
    () =>
      IMAGE_SIZES.map(
        size => `${imageUrl(src, size, MAX_SIZE, aspectRatio)} ${size}w`
      ).join(','),
    [src, aspectRatio]
  )

  const { handles } = useCssHandles(CSS_HANDLES)
  const imageRef = useRef(null)

  const imageContext: ProductImageState = useMemo(
    () => ({
      src,
      alt,
    }),
    [alt, src]
  )

  return (
    <ProductImageContext.Provider value={imageContext}>
      <div className={handles.productImage}>
        {imageLabel && (
          <div className={`tc ${handles.productImageLabel}`}>{imageLabel}</div>
        )}
        <Zoomable
          mode={zoomMode}
          factor={zoomFactor}
          ModalZoomElement={ModalZoomElement}
          zoomContent={
            // eslint-disable-next-line jsx-a11y/alt-text
            <img
              // This img element is just for zoom
              role="presentation"
              src={imageUrl(
                src,
                DEFAULT_SIZE * zoomFactor,
                MAX_SIZE,
                aspectRatio
              )}
              className={`${applyModifiers(handles.productImageTag, 'zoom')}`}
              style={{
                // Resets possible resizing done via CSS
                maxWidth: 'unset',
                width: `${zoomFactor * 100}%`,
                height: `${zoomFactor * 100}%`,
                objectFit: 'contain',
              }}
              // See comment regarding sizes below
              sizes="(max-width: 64.1rem) 100vw, 50vw"
            />
          }
        >
          <img
            ref={imageRef}
            className={`${applyModifiers(handles.productImageTag, 'main')}`}
            style={{
              width: '100%',
              height: '100%',
              maxHeight: maxHeight || 'unset',
              objectFit: 'contain',
            }}
            src={imageUrl(src, DEFAULT_SIZE, MAX_SIZE, aspectRatio)}
            srcSet={srcSet}
            alt={alt}
            title={alt}
            loading={index === 0 ? 'eager' : 'lazy'}
            {...(index === 0 && { fetchPriority: 'high' })}
            // WIP
            // The value of the "sizes" attribute means: if the window has at most 64.1rem of width,
            // the image will be of a width of 100vw. Otherwise, the
            // image will be 50vw wide.
            // This size is used for picking the best available size
            // given the ones from the srcset above.
            //
            // This is WIP because it is a guess: we are assuming
            // the image will be of a certain size, but it should be
            // probably be gotten from flex-layout or something.
            sizes="(max-width: 64.1rem) 100vw, 50vw"
          />
        </Zoomable>
      </div>
    </ProductImageContext.Provider>
  )
}

export default ProductImage

```


[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 store-components/react/components/ProductImages/components/HighQualityProductImage.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useContext } from 'react'
import classnames from 'classnames'
import { useCssHandles } from 'vtex.css-handles'

import styles from '../styles.css'
import { imageUrl } from '../utils/aspectRatioUtil'
import ProductImageContext from './ProductImageContext'
import Zoomable, { ZoomMode as ZoomModeComplete } from './Zoomable'

type AspectRatio = string | number
type ZoomMode = Exclude<ZoomModeComplete, 'open-modal'>

interface Props {
  zoomMode?: ZoomMode
  zoomFactor?: number
  aspectRatio?: AspectRatio
  imageSizes?: number[]
  defaultSize?: number
  maxSize?: number
}

function validateZoomMode(zoomMode: ZoomModeComplete): ZoomMode {
  if (
    zoomMode !== 'disabled' &&
    zoomMode !== 'in-place-click' &&
    zoomMode !== 'in-place-hover'
  ) {
    console.warn(
      `You passed a wrong value to prop zoomMode \`${zoomMode}\` using 'disabled' instead`
    )

    return 'disabled'
  }

  return zoomMode
}

const CSS_HANDLES = [
  'imgZoom',
  'productImageTag',
  'highQualityContainer',
] as const

function HighQualityProductImage(props: Props) {
  const {
    imageSizes,
    maxSize = 4096,
    zoomFactor = 2,
    defaultSize = 1200,
    aspectRatio = 'auto',
    zoomMode = 'disabled',
  } = props

  const { handles } = useCssHandles(CSS_HANDLES)
  const context = useContext(ProductImageContext)

  if (!context) {
    console.warn(
      "You're using a HighQualityProductImage out of a ProductImageContext"
    )

    return null
  }

  if (!('src' in context)) {
    return null
  }

  const { alt, src } = context
  const srcSet = imageSizes
    ?.map(size => `${imageUrl(src, size, maxSize, aspectRatio)} ${size}w`)
    .join(',')

  const containerClasses = classnames(
    handles.highQualityContainer,
    'w-100 h-100 overflow-hidden'
  )

  const imgClasses = classnames(
    handles.productImageTag,
    styles.highQualityProductImageImgElement,
    'w-100 h-100'
  )

  return (
    <div className={containerClasses}>
      <Zoomable
        mode={validateZoomMode(zoomMode)}
        factor={zoomFactor}
        zoomContent={
          // eslint-disable-next-line jsx-a11y/alt-text
          <img
            // This img element is just for zoom
            role="presentation"
            className={handles.imgZoom}
            style={{
              // Resets possible resizing done via CSS
              maxWidth: 'unset',
              width: `${zoomFactor * 100}%`,
              height: `${zoomFactor * 100}%`,
              objectFit: 'contain',
            }}
            // See comment regarding sizes below
            sizes="(max-width: 64.1rem) 100vw, 50vw"
            src={
              imageSizes
                ? imageUrl(src, defaultSize * zoomFactor, maxSize, aspectRatio)
                : src
            }
          />
        }
      >
        <img
          alt={alt}
          title={alt}
          loading="lazy"
          srcSet={srcSet}
          className={imgClasses}
          // See comment regarding sizes below
          sizes="(max-width: 64.1rem) 100vw, 50vw"
          src={
            imageSizes
              ? imageUrl(src, defaultSize * zoomFactor, maxSize, aspectRatio)
              : src
          }
        />
      </Zoomable>
    </div>
  )
}

export default HighQualityProductImage

```


[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 store-components/react/components/ProductImages/components/Video/Vimeo.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Vimeo extends Component {
  constructor(props) {
    super(props)

    this.state = { iframe: {} }

    const { loop, autoplay, width, height, showTitle, url } = this.props
    const params = `autoplay=${autoplay}&loop=${loop}&title=${showTitle}&width=${width}&height=${height}`
    const getUrl = `https://vimeo.com/api/oembed.json?url=${url}&${params}`

    this.iframeRef = React.createRef()

    fetch(getUrl)
      .then(response => response.json())
      .then(response => {
        const { height: heightDiv, width: widthDiv, html, title } = response

        const thumbUrl = Vimeo.thumbUrlFromResp(response, props.thumbWidth)

        props.setThumb && props.setThumb(thumbUrl, title)

        const [, src] = html.match(/src= *" *([^"]*) *"/) // Get url from response

        this.setState({
          iframe: {
            divStyle: { padding: `${(100 * heightDiv) / widthDiv}% 0 0 0` },
            src,
          },
        })
      })
  }

  static getThumbUrl = url => {
    const getUrl = `https://vimeo.com/api/oembed.json?url=${url}`

    return fetch(getUrl)
      .then(response => response.json())
      .then(response => response.thumbnail_url)
  }

  static thumbUrlFromResp(response, thumbWidth) {
    const { height, width } = response
    const thumb = response.thumbnail_url_with_play_button

    thumbWidth = thumbWidth || response.thumbnail_width
    const thumbHeight = Math.ceil((thumbWidth * height) / width)

    return thumb.replace(
      /_[0123456789]*x[0123456789]*./,
      `_${thumbWidth}x${thumbHeight}.`
    )
  }

  executeCommand = command => () => {
    if (!this.frameReady) return

    const vimeoCommand = JSON.stringify({ method: command })

    this.iframeRef.contentWindow.postMessage(
      vimeoCommand,
      'https://player.vimeo.com'
    )
  }

  render() {
    const { iframe } = this.state
    const { className, id, cssHandles } = this.props

    this.props.playing
      ? this.executeCommand('play')()
      : this.executeCommand('pause')()

    return (
      <div
        style={iframe.divStyle}
        className={`relative ${className} ${cssHandles.videoContainer}`}
      >
        <iframe
          ref={this.iframeRef}
          title={id}
          className={`${cssHandles.video} absolute top-0 left-0 w-100 h-100`}
          src={iframe.src}
          frameBorder="0"
          allowFullScreen
          allow="autoplay"
        />
      </div>
    )
  }
}

Vimeo.propTypes = {
  url: PropTypes.string.isRequired,
  id: PropTypes.number.isRequired, // Unique ID for iframe title
  setThumb: PropTypes.func,
  thumbWidth: PropTypes.number,
  className: PropTypes.string,
  loop: PropTypes.bool,
  autoplay: PropTypes.bool,
  showTitle: PropTypes.bool,
  width: PropTypes.number,
  height: PropTypes.number,
  playing: PropTypes.bool,
  cssHandles: PropTypes.shape({
    video: PropTypes.string.isRequired,
    videoContainer: PropTypes.string.isRequired,
  }).isRequired,
}

Vimeo.defaultProps = {
  loop: true,
  autoplay: false,
  width: null,
  height: null,
  showTitle: false,
  className: '',
}

export default Vimeo

```


[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 store-components/react/components/ProductImages/components/Video/index.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import PropTypes from 'prop-types'
import { match, compose, isEmpty, complement } from 'ramda'
import { useCssHandles } from 'vtex.css-handles'
import { NoSSR } from 'vtex.render-runtime'

import Vimeo from './Vimeo'
import YouTube from './Youtube'

const isNotEmpty = complement(isEmpty)

const isVimeo = compose(isNotEmpty, match(/vimeo/))
const isYoutube = compose(isNotEmpty, match(/youtube|youtu.be/))

const CSS_HANDLES = ['productVideo', 'videoContainer', 'video']

export function getThumbUrl(url, thumbWidth) {
  if (isVimeo(url)) {
    return Vimeo.getThumbUrl(url, thumbWidth)
  }

  if (isYoutube(url)) {
    return YouTube.getThumbUrl(url, thumbWidth)
  }
}

function Video(props) {
  const { url } = props
  const { handles } = useCssHandles(CSS_HANDLES)

  return (
    <div className={handles.productVideo}>
      {isVimeo(url) && (
        <NoSSR>
          <Vimeo key={url} {...props} cssHandles={handles} />
        </NoSSR>
      )}
      {isYoutube(url) && <YouTube key={url} {...props} cssHandles={handles} />}
    </div>
  )
}

Video.propsTypes = {
  url: PropTypes.string.isRequired,
  id: PropTypes.number.isRequired,
  setThumb: PropTypes.func,
  thumbWidth: PropTypes.number,
  className: PropTypes.string,
  play: PropTypes.bool,
}

export default Video

```


[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 store-components/react/components/ProductImages/components/Video/Youtube.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Youtube extends Component {
  constructor(props) {
    super(props)

    const { loop, autoplay, title, url } = this.props
    const params = `autoplay=${autoplay}&loop=${loop}&title=${title}&enablejsapi=1&iv_load_policy=3&modestbranding=1`
    const videoId = Youtube.extractVideoID(url)

    this.iframeRef = React.createRef()
    this.state = {
      iframe: {
        src: `https://www.youtube.com/embed/${videoId}?${params}`,
      },
    }
  }

  static getThumbUrl = url => {
    const videoId = Youtube.extractVideoID(url)

    return Promise.resolve(`https://img.youtube.com/vi/${videoId}/default.jpg`)
  }

  static extractVideoID = url => {
    const regExp =
      /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?)|(shorts\/))\??v?=?([^#&?]*).*/

    const match = url.match(regExp)

    if (match && match[8].length === 11) return match[8]

    return null
  }

  executeCommand = command => () => {
    if (!this.frameReady) return

    const youtubeCommand = JSON.stringify({ event: 'command', func: command })

    this.iframeRef.contentWindow.postMessage(
      youtubeCommand,
      'https://www.youtube.com'
    )
  }

  render() {
    const { iframe } = this.state
    const { className, id, cssHandles } = this.props

    this.props.playing
      ? this.executeCommand('playVideo')()
      : this.executeCommand('pauseVideo')()

    return (
      <div
        className={`relative ${className} ${cssHandles.videoContainer}`}
        style={{ padding: '30%' }}
      >
        <iframe
          ref={this.iframeRef}
          title={id}
          className={`${cssHandles.video} absolute top-0 left-0 w-100 h-100`}
          src={iframe.src}
          frameBorder="0"
          allowFullScreen
          allow="autoplay"
        />
      </div>
    )
  }
}

Youtube.propTypes = {
  url: PropTypes.string.isRequired,
  id: PropTypes.number.isRequired, // Unique ID for iframe title
  setThumb: PropTypes.func,
  thumbWidth: PropTypes.number,
  className: PropTypes.string,
  loop: PropTypes.bool,
  autoplay: PropTypes.bool,
  showTitle: PropTypes.bool,
  width: PropTypes.number,
  height: PropTypes.number,
  playing: PropTypes.bool,
  title: PropTypes.string,
  cssHandles: PropTypes.shape({
    video: PropTypes.string.isRequired,
    videoContainer: PropTypes.string.isRequired,
  }).isRequired,
}

Youtube.defaultProps = {
  loop: true,
  autoplay: false,
  width: null,
  height: null,
  title: false,
  className: '',
}

export default Youtube

```


[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 store-components/react/components/ProductImages/components/Gallery/global.css from the store-components VTEX app.


[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 store-components/react/components/ProductImages/components/Gallery/index.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import Gallery from './Gallery'

export default Gallery

```


[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 store-components/react/components/ProductImages/components/Gallery/Gallery.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useMemo } from 'react'
import { PhotoSwipe } from 'react-photoswipe'

import './global.css'

const Gallery = ({ items, isOpen, index, handleClose, bgOpacity }) => {
  const toPswItem = item => ({
    src: item.urls[item.bestUrlIndex],
    w: 1280,
    h: 1280,
    title: item.alt,
    thumbnail: item.thumbUrl,
  })

  return (
    <PhotoSwipe
      isOpen={isOpen}
      items={useMemo(() => items.map(toPswItem), [items])}
      onClose={handleClose}
      options={{ index, bgOpacity, history: false }}
    />
  )
}

export default Gallery

```


[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 store-components/react/components/ProductImages/components/Zoomable/index.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, ReactElement } from 'react'
import { Modal } from 'vtex.modal-layout'
import { useDevice } from 'vtex.device-detector'

import ZoomInPlace from './ZoomInPlace'
import ModalZoom from './ModalZoom'

export type ZoomMode =
  | 'in-place-click'
  | 'in-place-hover'
  | 'disabled'
  | 'open-modal'

interface Props {
  mode?: ZoomMode
  zoomContent?: ReactElement
  factor?: number
  ModalZoomElement?: typeof Modal
}

const Zoomable: FC<Props> = ({
  children,
  factor = 2,
  zoomContent,
  ModalZoomElement,
  mode = 'in-place-click',
}) => {
  const { isMobile } = useDevice()

  if (isMobile && mode !== 'disabled' && mode !== 'open-modal') {
    // TODO: Good enough for now, but needs to be a gallery in the future.
    // Preferably photoswipe.com
    return (
      <ZoomInPlace type="click" factor={factor} zoomContent={zoomContent}>
        {children}
      </ZoomInPlace>
    )
  }

  switch (mode) {
    case 'in-place-hover':
      return (
        <ZoomInPlace type="hover" factor={factor} zoomContent={zoomContent}>
          {children}
        </ZoomInPlace>
      )

    case 'in-place-click':
      return (
        <ZoomInPlace type="click" factor={factor} zoomContent={zoomContent}>
          {children}
        </ZoomInPlace>
      )

    case 'open-modal': {
      if (ModalZoomElement) {
        return (
          <ModalZoom ModalZoomElement={ModalZoomElement}>{children}</ModalZoom>
        )
      }
    }
    // falls through

    case 'disabled':
    // falls through

    default:
      return <>{children}</>
  }
}

export default Zoomable

```


[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 store-components/react/components/ProductImages/components/Zoomable/ModalZoom.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { ModalTrigger, Modal } from 'vtex.modal-layout'

interface Props {
  children: React.ReactNode
  ModalZoomElement: typeof Modal
}

function ModalZoom(props: Props) {
  const { children, ModalZoomElement } = props

  return (
    <ModalTrigger>
      {children}
      <ModalZoomElement />
    </ModalTrigger>
  )
}

export default ModalZoom

```


[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 store-components/react/components/ProductImages/components/Zoomable/ZoomInPlace.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, useRef, useState, useEffect, ReactElement } from 'react'
import { clamp } from 'ramda'

interface ContainerBounds {
  x: number
  y: number
  width: number
  height: number
}

type ZoomType = 'click' | 'hover'

interface Props {
  type: ZoomType
  zoomContent?: ReactElement
  factor: number
}

type ReactMouseEvent = MouseEvent | React.MouseEvent<HTMLElement, MouseEvent>

const getBounds = (element?: Element | null): ContainerBounds | null => {
  if (!element) return null

  const bounds = element.getBoundingClientRect()

  return {
    x: bounds.left,
    y: bounds.top,
    width: bounds.width,
    height: bounds.height,
  }
}

const getMousePositionFromEvent = (
  event: ReactMouseEvent,
  bounds?: ContainerBounds | null
): { x: number; y: number; isOutOfBounds?: boolean } => {
  if (!bounds) {
    return { x: 0, y: 0 }
  }

  const [x, y] = [event.clientX - bounds.x, event.clientY - bounds.y]

  const threshold = 20
  /* Uses out-of-bounds detection instead of simply a "mouse-out" event
   * to prevent from zooming out when the mouse hovers a button or suchlike */
  const isOutOfBounds =
    x < -threshold ||
    y < -threshold ||
    x > bounds.width + threshold ||
    y > bounds.height + threshold

  // Values larger than 0 increase mouse movement sensivity
  const boost = 0.1

  return {
    x: clamp(0, bounds.width, -bounds.width * boost + x * (1 + boost * 2)),
    y: clamp(0, bounds.height, -bounds.height * boost + y * (1 + boost * 2)),
    isOutOfBounds,
  }
}

const ZoomInPlace: FC<Props> = ({ children, zoomContent, type, factor }) => {
  const [isZoomedIn, setZoom] = useState(false)

  const containerRef = useRef<HTMLDivElement>(null)
  const contentRef = useRef<HTMLDivElement>(null)

  const containerBounds = useRef<ContainerBounds | null>(null)

  const setPositionAndScale = (x: number, y: number, scale: number) => {
    const contentElement = contentRef.current

    if (!contentElement) {
      return
    }

    contentElement.style.transform = `scale(${scale}, ${scale}) translate3d(${
      -x / scale
    }px, ${-y / scale}px, 0)`
  }

  const getContainerBounds = () =>
    containerBounds.current ?? getBounds(containerRef.current)

  const handleMouseOver = () => {
    setZoom(true)
  }

  const handleClick = (event: ReactMouseEvent) => {
    setZoom(!isZoomedIn)

    // eslint-disable-next-line vtex/prefer-early-return
    if (!isZoomedIn) {
      const mousePosition = getMousePositionFromEvent(
        event,
        getContainerBounds()
      )

      setPositionAndScale(mousePosition.x, mousePosition.y, factor)
    }
  }

  // Resets position when the image is zoomed out
  useEffect(() => {
    // eslint-disable-next-line vtex/prefer-early-return
    if (!isZoomedIn) {
      setPositionAndScale(0, 0, 1)
      containerBounds.current = null
    }
  }, [isZoomedIn])

  /* Adds mouse event handlers to the entire document, so that
   * mouse movement is not restricted to just the content element */
  const handleClickOutside = (event: MouseEvent) => {
    if (!isZoomedIn) {
      return
    }

    const isDescendant =
      containerRef.current &&
      event.target &&
      containerRef.current.contains?.(event.target as Node)

    if (isDescendant) {
      return
    }

    setZoom(false)
  }

  const handleMouseMove = (event: ReactMouseEvent) => {
    if (!isZoomedIn) {
      return
    }

    const mousePosition = getMousePositionFromEvent(event, getContainerBounds())

    /* Uses out-of-bounds detection instead of simply a "mouse-out" event
     * to prevent from zooming out when the mouse hovers a button or suchlike */
    if (type === 'hover' && mousePosition.isOutOfBounds) {
      setZoom(false)
    } else {
      setPositionAndScale(mousePosition.x, mousePosition.y, factor)
    }
  }

  useEffect(() => {
    if (document) {
      document.addEventListener('mousemove', handleMouseMove)
      if (type === 'click') {
        document.addEventListener('click', handleClickOutside)
      }
    }

    return () => {
      // eslint-disable-next-line vtex/prefer-early-return
      if (document) {
        document.removeEventListener('mousemove', handleMouseMove)
        if (type === 'click') {
          document.removeEventListener('click', handleClickOutside)
        }
      }
    }
  })

  return (
    // eslint-disable-next-line jsx-a11y/click-events-have-key-events
    <div
      ref={containerRef}
      onMouseOver={type === 'hover' ? handleMouseOver : undefined}
      onClick={type === 'click' ? handleClick : undefined}
      className="relative"
    >
      <div
        ref={contentRef}
        style={{
          transformOrigin: '0 0',
          fontSize: 0,
          /** Prevents accidental whitespaces on the content from stretching
           * the container, and consequently the zoomed image */
        }}
      >
        {children}
        {zoomContent && isZoomedIn && (
          <div
            className="absolute top-0 left-0 right-0 bottom-0"
            style={{
              transformOrigin: '0 0',
              transform: `scale(${1 / factor})`,
              fontSize: 0,
            }}
          >
            {zoomContent}
          </div>
        )}
      </div>
    </div>
  )
}

export default ZoomInPlace

```


[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 store-components/react/components/ProductImages/components/Carousel/ThumbnailSwiper.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Fragment, useMemo } from 'react'
import classNames from 'classnames'
import { useCssHandles, applyModifiers } from 'vtex.css-handles'
import { Swiper, SwiperSlide } from 'swiper/react'
import { IconCaret } from 'vtex.store-icons'

import { THUMB_SIZE } from '../../../module/images'
import { THUMBS_POSITION_HORIZONTAL } from '../../utils/enums'
import { imageUrl } from '../../utils/aspectRatioUtil'
import styles from '../../styles.css'

const THUMB_MAX_SIZE = 256

const CSS_HANDLES = [
  'figure',
  'thumbImg',
  'thumbVid',
  'productImagesThumb',
  'carouselThumbBorder',
  'carouselGaleryThumbs',
  'productImagesThumbCaret',
]

const Thumbnail = props => {
  const { alt, isVideo, thumbUrl, handles, aspectRatio = 'auto' } = props

  return (
    <>
      <figure
        className={`${applyModifiers(handles.figure, isVideo ? 'video' : '')}`}
        itemProp="associatedMedia"
        itemScope
        itemType="http://schema.org/ImageObject"
      >
        <img
          className={`${applyModifiers(
            handles.thumbImg,
            isVideo ? 'video' : ''
          )} w-100 h-auto db`}
          itemProp="thumbnail"
          alt={alt}
          src={imageUrl(thumbUrl, THUMB_SIZE, THUMB_MAX_SIZE, aspectRatio)}
        />
      </figure>
      <div
        className={`absolute absolute--fill b--solid b--muted-2 bw0 ${handles.carouselThumbBorder}`}
      />
    </>
  )
}

const navigationConfig = {
  prevEl: '.swiper-thumbnails-caret-prev',
  nextEl: '.swiper-thumbnails-caret-next',
  disabledClass: `c-disabled o-0 pointer-events-none ${styles.carouselCursorDefault}`,
  hiddenClass: 'dn',
}

const ThumbnailSwiper = props => {
  const { handles } = useCssHandles(CSS_HANDLES)

  const {
    isThumbsVertical,
    slides,
    thumbUrls,
    position,
    thumbnailAspectRatio,
    thumbnailMaxHeight,
    displayThumbnailsArrows,
    ...swiperProps
  } = props

  const hasThumbs = slides.length > 1

  const thumbClassName = classNames(
    `${handles.carouselGaleryThumbs} dn h-auto`,
    {
      'db-ns': hasThumbs,
      mt3: !isThumbsVertical,
      'w-20 bottom-0 top-0 absolute': isThumbsVertical,
      'left-0':
        isThumbsVertical && position === THUMBS_POSITION_HORIZONTAL.LEFT,
      'right-0':
        isThumbsVertical && position === THUMBS_POSITION_HORIZONTAL.RIGHT,
    }
  )

  const itemContainerClassName = classNames(
    handles.productImagesThumb,
    'mb5 pointer',
    {
      'w-20': !isThumbsVertical,
      'w-100': isThumbsVertical,
    }
  )

  const arrows = useMemo(() => {
    if (!displayThumbnailsArrows) {
      return null
    }

    const thumbCaretSize = 24
    const thumbCaretClassName = `${handles.productImagesThumbCaret} absolute z-2 pointer c-action-primary flex pv2`
    const thumbCaretStyle = { transition: 'opacity 0.2s' }

    const nextBtnClassName = classNames(
      'swiper-thumbnails-caret-next',
      thumbCaretClassName,
      {
        [`bottom-0 pt7 left-0 justify-center w-100 ${styles.gradientBaseBottom}`]:
          isThumbsVertical,
        [`right-0 top-0 items-center h-100 pl6 ${styles.gradientBaseRight}`]:
          !isThumbsVertical,
      }
    )

    const prevBtnClassName = classNames(
      'swiper-thumbnails-caret-prev top-0 left-0',
      thumbCaretClassName,
      isThumbsVertical && `pb7 justify-center w-100 ${styles.gradientBaseTop}`,
      !isThumbsVertical && `items-center h-100 pr6 ${styles.gradientBaseLeft}`
    )

    return (
      <Fragment key="navigation-arrows">
        <span className={nextBtnClassName} style={thumbCaretStyle}>
          <IconCaret
            orientation={isThumbsVertical ? 'down' : 'right'}
            size={thumbCaretSize}
          />
        </span>
        <span className={prevBtnClassName} style={thumbCaretStyle}>
          <IconCaret
            orientation={isThumbsVertical ? 'up' : 'left'}
            size={thumbCaretSize}
          />
        </span>
      </Fragment>
    )
  }, [
    displayThumbnailsArrows,
    handles.productImagesThumbCaret,
    isThumbsVertical,
  ])

  return (
    <div className={thumbClassName} data-testid="thumbnail-swiper">
      <Swiper
        className={`h-100 ${handles.productImagesThumbsSwiperContainer}`}
        slidesPerView="auto"
        touchRatio={1}
        threshold={8}
        navigation={navigationConfig}
        /* Slides are grouped when thumbnails arrows are enabled
         * so that clicking on next/prev will scroll more than
         * one thumbnail */
        slidesPerGroup={displayThumbnailsArrows ? 4 : 1}
        freeMode={false}
        mousewheel={false}
        zoom={false}
        watchSlidesVisibility
        watchSlidesProgress
        preloadImages
        updateOnWindowResize
        direction={isThumbsVertical ? 'vertical' : 'horizontal'}
        {...swiperProps}
      >
        {slides.map((slide, i) => {
          return (
            <SwiperSlide
              key={`${i}-${slide.alt}`}
              className={itemContainerClassName}
              style={{
                height: isThumbsVertical ? 'auto' : '115px',
                maxHeight: thumbnailMaxHeight || 'unset',
                position: 'relative',
              }}
            >
              <Thumbnail
                isVideo={slide.type === 'video'}
                index={i}
                handles={handles}
                alt={slide.alt}
                thumbUrl={slide.thumbUrl || thumbUrls[i]}
                aspectRatio={thumbnailAspectRatio}
              />
            </SwiperSlide>
          )
        })}
        {arrows}
      </Swiper>
    </div>
  )
}

export default ThumbnailSwiper

```


[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 store-components/react/components/ProductImages/components/Carousel/swiper.scoped.css from the store-components VTEX app.

[Assistant]
Here's the implementation:

```css
.carouselGaleryCursor {
  cursor: url(./cursor.svg) 8 8, default;
}

.carouselGaleryThumbs :global(.swiper-slide) {
  height: auto;
}

.carouselIconCaretRight,
.carouselIconCaretLeft {
  stroke: white;
  stroke-width: 0.5px;
  -webkit-filter: drop-shadow(0px 1px 1px #cacbcc);
  filter: drop-shadow(0px 1px 1px #cacbcc);
}

.carouselCursorDefault:hover {
  cursor: default;
}

.carouselImagePlaceholder {
}

/* pagination */
.swiper-pagination {
  position: absolute;
  text-align: center;
  transition: 300ms opacity;
  transform: translate3d(0, 0, 0);
  z-index: 10;
}

.swiper-pagination:global(.swiper-pagination-hidden) {
  opacity: 0;
}

/* Bullets */
.swiperBullet {
  width: 8px;
  height: 8px;
  display: inline-block;
  border-radius: 100%;
  background: #000;
  opacity: 0.2;
}

button.swiperBullet {
  border: none;
  margin: 0;
  padding: 0;
  box-shadow: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

.swiperPaginationClickable .swiperBullet {
  cursor: pointer;
}

.swiperBullet--active {
  opacity: 1;
  background-color: currentColor;
}

:global(.swiper-container-vertical > .swiper-pagination-bullets) {
  right: 10px;
  top: 50%;
  transform: translate3d(0px, -50%, 0);
}

:global(.swiper-container-vertical > .swiper-pagination-bullets) .swiperBullet {
  margin: 6px 0;
  display: block;
}

:global(.swiper-container-horizontal > .swiper-pagination-bullets)
  .swiperBullet {
  margin: 0 4px;
}

```


[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 store-components/react/components/ProductImages/components/Carousel/ImagePlaceholder.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

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

import style from '../../styles.css'

const ImagePlaceholder: FC = () => {
  return (
    <div className="relative" data-testid="default-image-placeholder">
      <div
        className={`${style.carouselImagePlaceholder} absolute w-100 contain bg-center`}
      />
      <svg
        width="100%"
        viewBox="0 0 512 512"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        <rect width="512" height="512" fill="#F2F2F2" />
        <rect
          x="183.857"
          y="180.2"
          width="144.286"
          height="150.474"
          stroke="#CACBCC"
          strokeWidth="2"
        />
        <path d="M183.78 303.688H328.214" stroke="#CACBCC" strokeWidth="2" />
        <path
          d="M205.082 279.563L223.599 240.507L242.116 260.035L269.892 220.979L306.926 279.563H205.082Z"
          stroke="#CACBCC"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
        <path
          d="M252.225 213.939C252.225 219.822 247.66 224.52 242.114 224.52C236.569 224.52 232.004 219.822 232.004 213.939C232.004 208.057 236.569 203.359 242.114 203.359C247.66 203.359 252.225 208.057 252.225 213.939Z"
          stroke="#CACBCC"
          strokeWidth="2"
        />
      </svg>
    </div>
  )
}

export default ImagePlaceholder

```


[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 store-components/react/components/ProductImages/components/Carousel/overrides.global.css from the store-components VTEX app.

[Assistant]
Here's the implementation:

```css
/**
 * This is a quick-and-dirty fix to avoid `w-20` token from being
 * overwritten by `width: 100%;` set by `swiper-slide`.
*/
.w-20.w-20.w-20 {
  width: 20%;
}

@media screen and (min-width: 40em) {
  .ml-20-ns {
    margin-left: 20%;
  }
}

.center-all {
  margin: auto;
}

.border-box {
  box-sizing: border-box;
}

.top-50 {
  top: 50%;
}

.translate--50y {
  transform: translate(0, -50%);
}

.pointer-events-none {
  pointer-events: none;
}

```


[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 store-components/react/components/ProductImages/components/Carousel/swiper.global.css from the store-components VTEX app.

[Assistant]
Here's the implementation:

```css
/**
 * Swiper 6.1.1
 * Most modern mobile touch slider and framework with hardware accelerated transitions
 * http://swiperjs.com
 *
 * Copyright 2014-2020 Vladimir Kharlampidi
 *
 * Released under the MIT License
 *
 * Released on: July 31, 2020
 */

.swiper-container {
  margin-left: auto;
  margin-right: auto;
  position: relative;
  overflow: hidden;
  list-style: none;
  padding: 0;
  /* Fix of Webkit flickering */
  z-index: 1;
}

.swiper-container-vertical > .swiper-wrapper {
  flex-direction: column;
  align-items: center;
}

.swiper-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: flex;
  transition-property: transform;
  box-sizing: content-box;
}

.swiper-container-android .swiper-slide,
.swiper-wrapper {
  transform: translate3d(0px, 0, 0);
}

.swiper-container-multirow > .swiper-wrapper {
  flex-wrap: wrap;
}

.swiper-container-multirow-column > .swiper-wrapper {
  flex-wrap: wrap;
  flex-direction: column;
}

.swiper-container-free-mode > .swiper-wrapper {
  transition-timing-function: ease-out;
  margin: 0 auto;
}

.swiper-slide {
  flex-shrink: 0;
  width: 100%;
  height: 100%;
  position: relative;
  transition-property: transform;
}

.swiper-slide-invisible-blank {
  visibility: hidden;
}

/* Auto Height */
.swiper-container-autoheight,
.swiper-container-autoheight .swiper-slide {
  height: auto;
}

.swiper-container-autoheight .swiper-wrapper {
  align-items: flex-start;
  transition-property: transform, height;
}

.swiper-button-prev,
.swiper-button-next {
  position: absolute;
  top: 50%;
  width: 27px;
  height: 44px;
  margin-top: -22px;
  z-index: 10;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #0f3e99;
}

.swiper-button-prev.swiper-button-disabled,
.swiper-button-next.swiper-button-disabled {
  opacity: 0.35;
  cursor: auto;
  pointer-events: none;
}

.swiper-button-prev,
.swiper-container-rtl .swiper-button-next {
  left: 10px;
  right: auto;
}

.swiper-button-next,
.swiper-container-rtl .swiper-button-prev {
  right: 10px;
  left: auto;
}

.swiper-button-lock {
  display: none;
}

.swiper-container-horizontal > .swiper-pagination-bullets {
  bottom: 10px;
  left: 0;
  width: 100%;
}

/* Preloader */
.swiper-lazy-preloader {
  width: 42px;
  height: 42px;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -21px;
  margin-top: -21px;
  z-index: 10;
  transform-origin: 50%;
  animation: swiper-preloader-spin 1s infinite linear;
  box-sizing: border-box;
  border: 4px solid #0f3e99;
  border-radius: 50%;
  border-top-color: transparent;
}

@keyframes swiper-preloader-spin {
  100% {
    transform: rotate(360deg);
  }
}

```


[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 store-components/react/components/ProductImages/components/Carousel/index.js from the store-components VTEX app.

[Assistant]
Here's the implementation:

```javascript
/* eslint-disable react/prop-types */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { path, equals } from 'ramda'
import { IconCaret } from 'vtex.store-icons'
import { withCssHandles } from 'vtex.css-handles'
import SwiperCore, { Thumbs, Navigation, Pagination } from 'swiper'
import { Swiper, SwiperSlide } from 'swiper/react'

import Video, { getThumbUrl } from '../Video'
import ProductImage from '../ProductImage'
import ThumbnailSwiper from './ThumbnailSwiper'
import ImagePlaceholder from './ImagePlaceholder'
import {
  THUMBS_ORIENTATION,
  THUMBS_POSITION_HORIZONTAL,
  THUMBS_VISIBILITY,
} from '../../utils/enums'
import styles from './swiper.scoped.css'

import './swiper.global.css'
import './overrides.global.css'

const CARET_ICON_SIZE = 24
const CARET_CLASSNAME =
  'pv8 absolute top-50 translate--50y z-2 pointer c-action-primary'

// install Swiper's Thumbs component
SwiperCore.use([Thumbs, Navigation, Pagination])

const CSS_HANDLES = [
  'carouselContainer',
  'productImagesThumbsSwiperContainer',
  'productImagesThumbActive',
  'productImagesGallerySwiperContainer',
  'productImagesGallerySlide',
  'swiperCaret',
  'swiperCaretNext',
  'swiperCaretPrev',
]

const initialState = {
  thumbUrl: [],
  alt: [],
  activeIndex: 0,
}

class Carousel extends Component {
  state = {
    ...initialState,
    thumbSwiper: null,
    gallerySwiper: null,
  }

  isVideo = []

  get hasGallerySwiper() {
    return Boolean(this.state.gallerySwiper)
  }

  get hasThumbSwiper() {
    return Boolean(this.state.thumbSwiper)
  }

  setInitialVariablesState() {
    const slides = this.props.slides || []

    this.isVideo = []

    slides.forEach(async (slide, i) => {
      if (slide.type === 'video') {
        const thumbUrl = await getThumbUrl(slide.src, slide.thumbWidth)

        this.isVideo[i] = true
        this.setVideoThumb(i)(thumbUrl)
      } else {
        // Image object doesn't exist when it's being rendered in the server side
        if (!window.navigator) {
          return
        }

        const image = new Image()

        image.src = slide.thumbUrl
      }
    })
  }

  componentDidMount() {
    this.setInitialVariablesState()
  }

  componentDidUpdate(prevProps) {
    const { activeIndex } = this.state
    const { isVideo } = this

    if (!equals(prevProps.slides, this.props.slides)) {
      this.setInitialVariablesState()

      if (
        !this.props.slides ||
        this.state.gallerySwiper?.destroyed ||
        this.state.thumbSwiper?.destroyed
      ) {
        return
      }

      this.state.gallerySwiper?.slideTo(initialState.activeIndex)
      this.state.thumbSwiper?.slideTo(initialState.activeIndex)

      this.setState({
        ...initialState,
      })

      return
    }

    const paginationElement = path(
      ['swiper', 'pagination', 'el'],
      this.state.gallerySwiper
    )

    if (paginationElement) {
      paginationElement.hidden = isVideo[activeIndex]
    }
  }

  handleSlideChange = () => {
    this.setState(prevState => {
      if (!this.hasGallerySwiper) {
        return
      }

      const { activeIndex } = prevState.gallerySwiper

      return { activeIndex, sliderChanged: true }
    })
  }

  setVideoThumb = i => (url, title) => {
    this.setState(prevState => {
      const thumbUrl = { ...prevState.thumbUrl }
      const alt = { ...prevState.alt }

      thumbUrl[i] = url
      alt[i] = title

      return { thumbUrl, alt }
    })
  }

  renderSlide = (slide, i) => {
    const {
      aspectRatio,
      maxHeight,
      zoomMode,
      zoomFactor,
      ModalZoomElement,
      zoomProps: legacyZoomProps,
    } = this.props

    // Backwards compatibility
    const { zoomType: legacyZoomType } = legacyZoomProps || {}
    const isZoomDisabled =
      legacyZoomType === 'no-zoom' || zoomMode === 'disabled'

    switch (slide.type) {
      case 'image':
        return (
          <ProductImage
            index={i}
            src={slide.url}
            alt={slide.alt}
            imageLabel={slide.imageLabel}
            maxHeight={maxHeight}
            zoomFactor={zoomFactor}
            aspectRatio={aspectRatio}
            ModalZoomElement={ModalZoomElement}
            zoomMode={isZoomDisabled ? 'disabled' : zoomMode}
          />
        )

      case 'video':
        return i === this.state.activeIndex ? (
          <Video
            url={slide.src}
            setThumb={this.setVideoThumb(i)}
            playing={i === this.state.activeIndex}
            id={i}
          />
        ) : (
          <div />
        )

      default:
        return null
    }
  }

  get galleryParams() {
    const { handles, slides = [], showPaginationDots = true } = this.props

    const params = {}

    if (slides.length > 1 && showPaginationDots) {
      params.pagination = {
        el: `.${styles['swiper-pagination']}`,
        clickable: true,
        clickableClass: styles.swiperPaginationClickable,
        bulletClass: styles.swiperBullet,
        bulletActiveClass: styles['swiperBullet--active'],
        renderBullet(_index, className) {
          return `<span class="${className} c-action-primary"></span>`
        },
      }
    }

    if (slides.length > 1) {
      params.navigation = {
        prevEl: '.swiper-caret-prev',
        nextEl: '.swiper-caret-next',
        disabledClass: `c-disabled ${styles.carouselCursorDefault}`,
      }
    }

    params.thumbs = {
      swiper: this.state.thumbSwiper,
      multipleActiveThumbs: false,
      slideThumbActiveClass: handles.productImagesThumbActive,
    }

    return params
  }

  render() {
    const {
      aspectRatio,
      maxHeight,
      placeholder,
      position,
      handles,
      slides = [],
      thumbnailMaxHeight,
      thumbnailAspectRatio,
      thumbnailsOrientation,
      zoomProps: { zoomType },
      showPaginationDots = true,
      showNavigationArrows = true,
      thumbnailVisibility,
      displayThumbnailsArrows = false,
    } = this.props

    const hasSlides = slides && slides.length > 0

    const isThumbsVertical =
      thumbnailsOrientation === THUMBS_ORIENTATION.VERTICAL

    const hasThumbs = slides && slides.length > 1

    const galleryCursor = {
      'in-page': styles.carouselGaleryCursor,
      'no-zoom': '',
    }

    const imageClasses = classNames(
      'w-100 border-box',
      galleryCursor[hasSlides ? zoomType : 'no-zoom'],
      {
        'ml-20-ns w-80-ns pl5-ns':
          isThumbsVertical &&
          position === THUMBS_POSITION_HORIZONTAL.LEFT &&
          ((hasThumbs && thumbnailVisibility === THUMBS_VISIBILITY.VISIBLE) ||
            !hasSlides),
        'mr-20-ns w-80-ns pr5-ns':
          isThumbsVertical &&
          position === THUMBS_POSITION_HORIZONTAL.RIGHT &&
          ((hasThumbs && thumbnailVisibility === THUMBS_VISIBILITY.VISIBLE) ||
            !hasSlides),
      }
    )

    if (!hasSlides) {
      return (
        <div className={imageClasses}>
          {placeholder ? (
            <ProductImage
              src={placeholder}
              alt="Product image placeholder"
              maxHeight={maxHeight}
              aspectRatio={aspectRatio}
              zoomMode="disabled"
            />
          ) : (
            <ImagePlaceholder />
          )}
        </div>
      )
    }

    const containerClasses = classNames(
      handles.carouselContainer,
      'relative overflow-hidden w-100',
      {
        'flex-ns justify-end-ns':
          isThumbsVertical &&
          position === THUMBS_POSITION_HORIZONTAL.LEFT &&
          hasThumbs &&
          thumbnailVisibility === THUMBS_VISIBILITY.VISIBLE,
        'flex-ns justify-start-ns':
          isThumbsVertical &&
          position === THUMBS_POSITION_HORIZONTAL.RIGHT &&
          hasThumbs &&
          thumbnailVisibility === THUMBS_VISIBILITY.VISIBLE,
      }
    )

    const thumbnailSwiper = (
      <ThumbnailSwiper
        onSwiper={instance => this.setState({ thumbSwiper: instance })}
        isThumbsVertical={isThumbsVertical}
        thumbnailAspectRatio={thumbnailAspectRatio}
        thumbnailMaxHeight={thumbnailMaxHeight}
        thumbUrls={this.state.thumbUrl}
        displayThumbnailsArrows={displayThumbnailsArrows}
        slides={slides}
        position={position}
      />
    )

    return (
      <div className={containerClasses} aria-hidden="true">
        {isThumbsVertical &&
          thumbnailVisibility === THUMBS_VISIBILITY.VISIBLE &&
          thumbnailSwiper}
        <div className={imageClasses}>
          {!this.state.thumbSwiper?.destroyed && (
            <Swiper
              onSwiper={instance => this.setState({ gallerySwiper: instance })}
              className={handles.productImagesGallerySwiperContainer}
              threshold={10}
              resistanceRatio={slides.length > 1 ? 0.85 : 0}
              onSlideChange={this.handleSlideChange}
              updateOnWindowResize
              {...this.galleryParams}
            >
              {slides.map((slide, i) => (
                <SwiperSlide
                  key={`slider-${i}`}
                  className={`${handles.productImagesGallerySlide} swiper-slide center-all`}
                >
                  {this.renderSlide(slide, i)}
                </SwiperSlide>
              ))}

              <div
                key="pagination"
                className={classNames(styles['swiper-pagination'], {
                  dn: slides.length === 1 || !showPaginationDots,
                })}
              />

              <div
                className={classNames({
                  dn: slides.length === 1 || !showNavigationArrows,
                })}
              >
                <span
                  key="caret-next"
                  className={`swiper-caret-next pl7 pr2 right-0 ${CARET_CLASSNAME} ${handles.swiperCaret} ${handles.swiperCaretNext}`}
                >
                  <IconCaret
                    orientation="right"
                    size={CARET_ICON_SIZE}
                    className={styles.carouselIconCaretRight}
                  />
                </span>
                <span
                  key="caret-prev"
                  className={`swiper-caret-prev pr7 pl2 left-0 ${CARET_CLASSNAME} ${handles.swiperCaret} ${handles.swiperCaretPrev}`}
                >
                  <IconCaret
                    orientation="left"
                    size={CARET_ICON_SIZE}
                    className={styles.carouselIconCaretLeft}
                  />
                </span>
              </div>
            </Swiper>
          )}

          {!isThumbsVertical &&
            thumbnailVisibility === THUMBS_VISIBILITY.VISIBLE &&
            thumbnailSwiper}
        </div>
      </div>
    )
  }
}

Carousel.propTypes = {
  slides: PropTypes.arrayOf(
    PropTypes.shape({
      type: PropTypes.string,
      url: PropTypes.string,
      alt: PropTypes.string,
      thumbUrl: PropTypes.string,
      bestUrlIndex: PropTypes.number,
    })
  ),
  ModalZoomElement: PropTypes.any,
  displayThumbnailsArrows: PropTypes.bool,
  thumbnailVisibility: PropTypes.oneOf([
    THUMBS_VISIBILITY.VISIBLE,
    THUMBS_VISIBILITY.HIDDEN,
  ]),
}

export default withCssHandles(CSS_HANDLES)(Carousel)

```


[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 store-components/react/components/SKUSelector/types/index.ts from the store-components VTEX app.

[Assistant]
Here's the implementation:

```typescript
export type InitialSelectionType = 'complete' | 'image' | 'empty'

export type DisplayMode = 'select' | 'default' | 'slider'

export interface SelectorProductItem extends Omit<ProductItem, 'variations'> {
  variations: string[]
  variationValues: Record<string, string>
}

export type SelectedVariations = Record<string, string | null>

export interface CallbackItem {
  name: string
  value: string
  skuId: string | null
  isMainAndImpossible: boolean
  possibleItems: SelectorProductItem[]
}

export interface Image {
  imageId: string
  imageLabel: string | null
  imageTag: string
  imageUrl: string
  imageText: string
}

export type ImageMap = Record<string, Record<string, Image | undefined>>

export interface DisplayOption {
  label: string
  originalName: string
  onSelectItem: () => void
  image: Image | undefined
  available: boolean
  impossible: boolean
  disabled: boolean
}

export interface DisplayVariation {
  name: string
  originalName: string
  options: DisplayOption[]
}

export type Variations = Record<
  string,
  {
    originalName: string
    values: Array<{
      name: string
      originalName: string
    }>
  }
>

```


[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 store-components/react/components/SKUSelector/utils/proptypes.ts from the store-components VTEX app.

[Assistant]
Here's the implementation:

```typescript
import PropTypes from 'prop-types'

export const skuShape = PropTypes.shape({
  /** Name of the SKU Item */
  name: PropTypes.string.isRequired,
  /** Images of the SKU item */
  images: PropTypes.arrayOf(
    PropTypes.shape({
      /** URL of source Image */
      imageUrl: PropTypes.string.isRequired,
      /** Brief description of the image */
      imageLabel: PropTypes.string,
    })
  ).isRequired,
  /** SkuID */
  itemId: PropTypes.string.isRequired,
  /** List of products specifications names */
  variations: PropTypes.arrayOf(
    PropTypes.shape({
      /** Variation Name */
      name: PropTypes.string,
      /** Variation Values */
      values: PropTypes.arrayOf(PropTypes.string),
    })
  ),
})

export const parsedSkuShape = PropTypes.shape({
  ...skuShape,
  /** List of products specifications names */
  variations: PropTypes.arrayOf(PropTypes.string),
})

export const variationShape = PropTypes.shape({
  /** Variation Name */
  name: PropTypes.string,
  /** Options Array */
  options: PropTypes.arrayOf(
    PropTypes.shape({
      image: PropTypes.shape({
        imageLabel: PropTypes.string,
        imageUrl: PropTypes.string,
      }),
      available: PropTypes.bool,
      label: PropTypes.string,
      onSelectItem: PropTypes.func,
      impossible: PropTypes.bool,
    })
  ),
})

```


[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 store-components/react/components/SKUSelector/utils/index.ts from the store-components VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { clone, filter, prop, reject } from 'ramda'
import slugify from 'slugify'

import { getDefaultSeller } from '../../../utils/sellers'
import { SelectedVariations, SelectorProductItem } from '../types'

/**
 * Return the maximum sku price
 * @param {array of sku's} items
 */
export const getMaxSkuPrice = (items: SelectorProductItem[]) => {
  if (!items) return 0

  return items.reduce((max, sku) => {
    const seller = getDefaultSeller(sku.sellers)
    const price = seller?.commertialOffer.Price ?? 0

    return Math.max(max, price)
  }, 0)
}

/**
 * Remove the 'https' from the given url
 * @param {string} url
 */
export const stripUrl = (url: string) => url.replace(/^https?:/, '')

/**
 * Parse the variations field in the sku object
 * @param {sku} sku
 */
export const parseSku = (sku: ProductItem) => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const result = clone(sku) as any
  const variationValues = {} as Record<string, string>

  for (const variation of sku.variations) {
    variationValues[variation.name] = variation.values[0]
  }

  const variations = sku.variations.map(prop('name'))

  result.variationValues = variationValues
  result.variations = variations

  return result as SelectorProductItem
}

/**
 * Verifies if the variation is color
 * @param {string} variation
 */
export const isColor = (variation: string) => {
  if (!variation) return false

  const possibleValues = [
    'cor',
    'color',
    'colour',
    'colore',
    'farbe',
    'couleur',
    'kleuren',
    'culoare',
    'värit',
    'kolory',
    'farve',
    'färger',
    'farby',
    'boje',
    'colori',
    'tsvyat',
    'Цвят',
  ]

  return possibleValues.includes(variation.toLowerCase())
}

/**
 * Choose wich variation will be the main one.
 * @param {Array[string]} variations
 */
export const getMainVariationName = (variations: string[]) => {
  for (let i = 0; i < variations.length; i++) {
    if (isColor(variations[i])) return variations[i]
  }

  return variations[0]
}

/**
 * Given a selectedVariations, find the first item that has those variations selected
 * selectedVariations format: { "color": "black", size: "small", fabric: null }
 * items: skuItems parsed with variations fields
 * Output: item or null, if not present
 */
export const findItemWithSelectedVariations = (
  items: SelectorProductItem[],
  selectedVariations: SelectedVariations
) => {
  const selectedNotNull = filter(
    Boolean,
    selectedVariations
  ) as SelectedVariationsNotNull

  const selectedCount = Object.keys(selectedNotNull).length

  if (selectedCount === 0) {
    // may return any item, return first element
    return items[0]
  }

  // eslint-disable-next-line @typescript-eslint/no-use-before-define
  return items.find(isSkuSelected(selectedNotNull))
}

/**
 * Given a selectedVariations, find items that have those variations selected
 * selectedVariations format: { "color": "black", size: "small", fabric: null }
 * items: skuItems parsed with variations fields
 * Output: list of items with those variations
 */
export const findListItemsWithSelectedVariations = (
  items: SelectorProductItem[],
  selectedVariations: SelectedVariations
) => {
  const selectedNotNull = filter(
    Boolean,
    selectedVariations
  ) as SelectedVariationsNotNull

  const selectedCount = Object.keys(selectedNotNull).length

  if (selectedCount === 0) {
    // return all
    return items
  }

  // eslint-disable-next-line @typescript-eslint/no-use-before-define
  return items.filter(isSkuSelected(selectedNotNull))
}

export const uniqueOptionToSelect = (
  items: SelectorProductItem[],
  selectedVariations: SelectedVariations,
  isMainAndImpossible: boolean
) => {
  const possibleItems = !isMainAndImpossible
    ? items
    : findListItemsWithSelectedVariations(items, selectedVariations)

  const unselected = reject(Boolean, selectedVariations)
  const unselectedNames = Object.keys(unselected)
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
  const availableOptions = buildAvailableVariations(
    possibleItems,
    unselectedNames
  )

  const variationsWithOne = filter(
    setValues => setValues.size === 1,
    availableOptions
  )

  const variationsNames = Object.keys(variationsWithOne)
  const finalUniqueVariations = {} as Record<string, string>

  // Transform set to plain value
  for (const variationName of variationsNames) {
    const { value } = variationsWithOne[variationName].values().next()

    finalUniqueVariations[variationName] = value
  }

  return finalUniqueVariations
}

export function slug(str: string) {
  // eslint-disable-next-line no-useless-escape
  const replaced = str?.replace(/[*+~.()'`’"!:@&\[\]]/g, '') || ''
  const slugified = slugify(replaced, { lower: true }) || ''

  return slugified
}

type SelectedVariationsNotNull = Record<string, string>

/** Private functions */
const isSkuSelected =
  (selectedNotNull: SelectedVariationsNotNull) =>
  (sku: SelectorProductItem) => {
    const hasAll = Object.keys(selectedNotNull).every(variationName => {
      const selectedValue = selectedNotNull[variationName]

      return sku.variationValues[variationName] === selectedValue
    })

    return hasAll
  }

const buildAvailableVariations = (
  items: SelectorProductItem[],
  variationNames: string[]
) => {
  const result = {} as Record<string, Set<string>>

  for (const variationName of variationNames) {
    result[variationName] = new Set()
  }

  for (const item of items) {
    for (const variationName of variationNames) {
      const variationValue = item.variationValues[variationName]
      const currentSet = result[variationName]

      if (variationValue) {
        currentSet.add(variationValue)
      }
    }
  }

  return result
}

export const DEFAULT_WIDTH = 'auto'
export const DEFAULT_HEIGHT = 'auto'
export const MAX_WIDTH = 3000
export const MAX_HEIGHT = 4000

/**
 * Having the url below as base for the LEGACY file manager,
 * https://storecomponents.vteximg.com.br/arquivos/ids/155472/Frame-3.jpg?v=636793763985400000
 * the following regex will match https://storecomponents.vteximg.com.br/arquivos/ids/155472
 *
 * Also matches urls with defined sizes like:
 * https://storecomponents.vteximg.com.br/arquivos/ids/155473-160-auto
 * @type {RegExp}
 *
 * On the new vtex.file-manager isn't necessary replace the URL, just add the param on the querystring, like:
 * "?width=WIDTH&height=HEIGHT&aspect=true"
 *
 */
const baseUrlRegex = new RegExp(/.+ids\/(\d+)/)

export function cleanImageUrl(imageUrl: string) {
  const result = baseUrlRegex.exec(imageUrl) ?? []

  return result.length > 0 ? result[0] : null
}

function replaceLegacyFileManagerUrl(
  imageUrl: string,
  width: number | string,
  height: number | string
) {
  const legacyUrlPattern = '/arquivos/ids/'
  const isLegacyUrl = imageUrl.includes(legacyUrlPattern)

  if (!isLegacyUrl) return imageUrl

  return `${cleanImageUrl(imageUrl)}-${width}-${height}`
}

export function changeImageUrlSize(
  imageUrl: string,
  width: string | number = DEFAULT_WIDTH,
  height: string | number = DEFAULT_HEIGHT
) {
  if (!imageUrl) return
  typeof width === 'number' && (width = Math.min(width, MAX_WIDTH))
  typeof height === 'number' && (height = Math.min(height, MAX_HEIGHT))

  const normalizedImageUrl = replaceLegacyFileManagerUrl(
    imageUrl,
    width,
    height
  )

  const queryStringSeparator = normalizedImageUrl.includes('?') ? '&' : '?'

  return `${normalizedImageUrl}${queryStringSeparator}width=${width}&height=${height}&aspect=true`
}

export const DEFAULT_BOTTOM_MARGIN = 7
const MAX_POSSIBLE_MARGIN = 11
const MIN_POSSIBLE_MARGIN = 0

export function getValidMarginBottom(variationsSpacing?: number) {
  if (typeof variationsSpacing === 'undefined') {
    return DEFAULT_BOTTOM_MARGIN
  }

  return Math.max(
    Math.min(MAX_POSSIBLE_MARGIN, variationsSpacing),
    MIN_POSSIBLE_MARGIN
  )
}

export const NO_BOTTOM_MARGIN = 'none'

```


[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 store-components/react/components/SKUSelector/components/ErrorMessage.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { FormattedMessage } from 'react-intl'

import { useSKUSelectorCssHandles } from '../SKUSelectorCssHandles'

export const CSS_HANDLES = ['errorMessage'] as const

function ErrorMessage() {
  const { handles } = useSKUSelectorCssHandles()
  const className = `${handles.errorMessage} c-danger`

  return (
    <FormattedMessage id="store/sku-selector.variation.select-an-option">
      {message => (
        <>
          {' '}
          {/* this space is necessary */}
          <span className={className}>{message}</span>
        </>
      )}
    </FormattedMessage>
  )
}

export default ErrorMessage

```


[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 store-components/react/components/SKUSelector/components/SKUSelector.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useCallback, memo, useState, useMemo } from 'react'
import { filter, clone } from 'ramda'
import { ResponsiveValuesTypes } from 'vtex.responsive-values'

import styles from '../styles.css'
import {
  isColor,
  getValidMarginBottom,
  findItemWithSelectedVariations,
  findListItemsWithSelectedVariations,
} from '../utils'
import {
  SelectorProductItem,
  CallbackItem,
  SelectedVariations,
  ImageMap,
  DisplayOption,
  DisplayVariation,
  Variations,
  DisplayMode,
} from '../types'
import Variation from './Variation'
import useEffectSkipMount from './hooks/useEffectSkipMount'
import { useSKUSelectorCssHandles } from '../SKUSelectorCssHandles'
import { getDefaultSeller } from '../../../utils/sellers'

export type ShowValueForVariation = 'none' | 'image' | 'all'

// The boolean type was kept because for backward compatibility
export type ShowVariationsLabels =
  | boolean
  | 'none'
  | 'variation'
  | 'itemValue'
  | 'variationAndItemValue'

function getShowValueForVariation(
  showValueForVariation: ShowValueForVariation,
  variationName: string
) {
  const isImage = isColor(variationName)

  return (
    showValueForVariation === 'all' ||
    (showValueForVariation === 'image' && isImage)
  )
}

interface Props {
  seeMoreLabel: string
  maxItems: number
  variations: Variations
  skuItems: SelectorProductItem[]
  onSelectItem: (callbackItem: CallbackItem) => void
  imagesMap: ImageMap
  selectedVariations: Record<string, string | null>
  hideImpossibleCombinations: boolean
  disableUnavailableSelectOptions: boolean
  showValueForVariation: ShowValueForVariation
  showBorders?: boolean
  imageHeight?: number
  imageWidth?: number
  showVariationsLabels: ShowVariationsLabels
  variationsSpacing?: number
  showVariationsErrorMessage: boolean
  displayMode: DisplayMode
  sliderDisplayThreshold: number
  sliderArrowSize: number
  sliderItemsPerPage: ResponsiveValuesTypes.ResponsiveValue<number>
  sortVariationsByLabel?: boolean
}

function isSkuAvailable(item?: SelectorProductItem) {
  if (!item) {
    return false
  }

  const seller = getDefaultSeller(item.sellers)

  if (!seller) {
    return false
  }

  return seller.commertialOffer?.AvailableQuantity > 0
}

function sortOptionsNumerically(options: DisplayOption[]) {
  return options.sort((a, b) => parseFloat(a.label) - parseFloat(b.label))
}

function sortOptionsAlphabetically(options: DisplayOption[]) {
  return options.sort((a, b) => {
    if (a.label < b.label) return -1
    if (a.label > b.label) return 1

    return 0
  })
}

const showItemAsAvailable = ({
  possibleItems,
  selectedVariations,
  variationCount,
  isSelected,
}: {
  possibleItems: SelectorProductItem[]
  selectedVariations: SelectedVariations
  variationCount: number
  isSelected: boolean
}) => {
  const selectedNotNull = filter(Boolean, selectedVariations)
  const selectedCount = Object.keys(selectedNotNull).length

  if (selectedCount === variationCount && isSelected) {
    const item = findItemWithSelectedVariations(
      possibleItems,
      selectedVariations
    )

    return isSkuAvailable(item)
  }

  return possibleItems.some(isSkuAvailable)
}

interface AvailableVariationParams {
  variations: Variations
  selectedVariations: SelectedVariations
  imagesMap: ImageMap
  onSelectItemMemo: (callbackItem: CallbackItem) => () => void
  skuItems: SelectorProductItem[]
  hideImpossibleCombinations: boolean
  disableUnavailableSelectOptions: boolean
  sortVariationsByLabel: boolean
}

export const orderItemsByAvailability = (
  item1: SelectorProductItem,
  item2: SelectorProductItem
) => {
  const isAvailableItem1 = isSkuAvailable(item1)
  const isAvailableItem2 = isSkuAvailable(item2)

  if (isAvailableItem1 && !isAvailableItem2) {
    return -1
  }

  if (!isAvailableItem1 && isAvailableItem2) {
    return 1
  }

  const defaultSellerItem1 = getDefaultSeller(item1.sellers)
  const defaultSellerItem2 = getDefaultSeller(item2.sellers)

  const availabilityItem1 = defaultSellerItem1
    ? defaultSellerItem1.commertialOffer.AvailableQuantity
    : 0

  const availabilityItem2 = defaultSellerItem2
    ? defaultSellerItem2.commertialOffer.AvailableQuantity
    : 0

  return availabilityItem2 - availabilityItem1
}

const parseOptionNameToDisplayOption =
  ({
    selectedVariations,
    variationName,
    skuItems,
    onSelectItemMemo,
    imagesMap,
    variationCount,
    hideImpossibleCombinations,
    disableUnavailableSelectOptions,
  }: {
    selectedVariations: SelectedVariations
    variationName: string
    skuItems: SelectorProductItem[]
    onSelectItemMemo: (callbackItem: CallbackItem) => () => void
    imagesMap: ImageMap
    variationCount: number
    hideImpossibleCombinations: boolean
    disableUnavailableSelectOptions: boolean
  }) =>
  (variationValue: {
    name: string
    originalName: string
  }): DisplayOption | null => {
    const isSelected = selectedVariations[variationName] === variationValue.name
    const image = imagesMap?.[variationName]?.[variationValue.name]

    const newSelectedVariation = clone(selectedVariations)

    newSelectedVariation[variationName] = isSelected
      ? null
      : variationValue.name

    const possibleItems = findListItemsWithSelectedVariations(
      skuItems,
      newSelectedVariation
    )

    if (possibleItems.length > 0) {
      // This is a valid combination option

      const possibleItemsOrderedByAvailability =
        possibleItems.length > 1
          ? possibleItems.sort(orderItemsByAvailability)
          : possibleItems

      const [item] = possibleItemsOrderedByAvailability

      const callbackFn = onSelectItemMemo({
        name: variationName,
        value: variationValue.name,
        skuId: item.itemId,
        isMainAndImpossible: false,
        possibleItems: possibleItemsOrderedByAvailability,
      })

      return {
        label: variationValue.name,
        originalName: variationValue.originalName,
        onSelectItem: callbackFn,
        image,
        available: showItemAsAvailable({
          possibleItems: possibleItemsOrderedByAvailability,
          selectedVariations,
          variationCount,
          isSelected,
        }),
        impossible: false,
        disabled: disableUnavailableSelectOptions,
      }
    }

    if (!hideImpossibleCombinations) {
      // This is a impossible combination and will only appear if the prop allows.
      return {
        label: variationValue.name,
        originalName: variationValue.originalName,
        // eslint-disable-next-line @typescript-eslint/no-empty-function
        onSelectItem: () => {},
        image,
        available: true,
        impossible: true,
        disabled: disableUnavailableSelectOptions,
      }
    }

    // This is a impossible combination and will be hidden.
    return null
  }

const variationNameToDisplayVariation =
  ({
    variations,
    selectedVariations,
    skuItems,
    onSelectItemMemo,
    imagesMap,
    variationCount,
    hideImpossibleCombinations,
    disableUnavailableSelectOptions,
    sortVariationsByLabel,
  }: {
    variations: Variations
    selectedVariations: SelectedVariations
    skuItems: SelectorProductItem[]
    imagesMap: ImageMap
    onSelectItemMemo: (callbackItem: CallbackItem) => () => void
    variationCount: number
    hideImpossibleCombinations: boolean
    disableUnavailableSelectOptions: boolean
    sortVariationsByLabel: boolean
  }) =>
  (variationName: string): DisplayVariation => {
    const name = variationName
    const { values, originalName } = variations[variationName]
    let options = values
      .map(
        parseOptionNameToDisplayOption({
          selectedVariations,
          variationName,
          skuItems,
          onSelectItemMemo,
          imagesMap,
          variationCount,
          hideImpossibleCombinations,
          disableUnavailableSelectOptions,
        })
      )
      .filter(Boolean) as DisplayOption[]

    if (sortVariationsByLabel) {
      const areAllNumbers = options.every(
        (option: DisplayOption) => !Number.isNaN(Number(option.label))
      )

      const sortedOptions = areAllNumbers
        ? sortOptionsNumerically([...options])
        : sortOptionsAlphabetically([...options])

      options = [...sortedOptions]
    }

    return { name, originalName, options }
  }

// Parameters are explained on PropTypes
const getAvailableVariations = ({
  variations,
  selectedVariations,
  imagesMap,
  onSelectItemMemo,
  skuItems,
  hideImpossibleCombinations,
  disableUnavailableSelectOptions,
  sortVariationsByLabel,
}: AvailableVariationParams): DisplayVariation[] => {
  const variationCount = Object.keys(variations).length

  return Object.keys(variations).map(
    variationNameToDisplayVariation({
      variations,
      selectedVariations,
      skuItems,
      onSelectItemMemo,
      imagesMap,
      variationCount,
      hideImpossibleCombinations,
      disableUnavailableSelectOptions,
      sortVariationsByLabel,
    })
  )
}

const getAvailableVariationsPromise = (
  params: AvailableVariationParams
): Promise<DisplayVariation[]> => {
  return new Promise(resolve => {
    const result = getAvailableVariations(params)

    resolve(result)
  })
}

export const CSS_HANDLES = ['skuSelectorContainer'] as const

/** Renders the main and the secondary variation, if it exists. */
function SKUSelector({
  seeMoreLabel,
  maxItems,
  variations,
  skuItems,
  onSelectItem,
  imagesMap,
  imageHeight,
  imageWidth,
  showBorders,
  displayMode,
  selectedVariations,
  showVariationsLabels,
  showValueForVariation,
  hideImpossibleCombinations,
  disableUnavailableSelectOptions,
  showVariationsErrorMessage,
  variationsSpacing: marginBottomProp,
  sliderDisplayThreshold,
  sliderArrowSize,
  sliderItemsPerPage,
  sortVariationsByLabel = false,
}: Props) {
  const { handles } = useSKUSelectorCssHandles()
  const variationsSpacing = getValidMarginBottom(marginBottomProp)
  const onSelectItemMemo = useCallback(
    ({
        name,
        value,
        skuId,
        isMainAndImpossible,
        possibleItems,
      }: CallbackItem) =>
      () =>
        onSelectItem({
          name,
          value,
          skuId,
          isMainAndImpossible,
          possibleItems,
        }),
    [onSelectItem]
  )

  const availableVariationsPayload = useMemo(
    () => ({
      variations,
      selectedVariations,
      imagesMap,
      onSelectItemMemo,
      skuItems,
      hideImpossibleCombinations,
      disableUnavailableSelectOptions,
      sortVariationsByLabel,
    }),
    [
      variations,
      selectedVariations,
      imagesMap,
      onSelectItemMemo,
      skuItems,
      hideImpossibleCombinations,
      disableUnavailableSelectOptions,
      sortVariationsByLabel,
    ]
  )

  const [displayVariations, setDisplayVariations] = useState<
    DisplayVariation[]
  >(() => getAvailableVariations(availableVariationsPayload))

  useEffectSkipMount(() => {
    let isCurrent = true
    const promise = getAvailableVariationsPromise(availableVariationsPayload)

    promise.then(availableVariations => {
      if (isCurrent) {
        setDisplayVariations(availableVariations)
      }
    })

    return () => {
      isCurrent = false
    }
  }, [availableVariationsPayload])

  const variationClasses = `mb${variationsSpacing}`

  return (
    <div
      className={`${styles.skuSelectorContainer} ${handles.skuSelectorContainer}`}
    >
      {displayVariations.map((variationOption, index) => {
        const selectedItem = selectedVariations[variationOption.name]

        return (
          <Variation
            mode={displayMode}
            maxItems={maxItems}
            imageWidth={imageWidth}
            imageHeight={imageHeight}
            showBorders={showBorders}
            variation={variationOption}
            selectedItem={selectedItem}
            seeMoreLabel={seeMoreLabel}
            showLabel={showVariationsLabels}
            containerClasses={variationClasses}
            key={`${variationOption.name}-${index}`}
            showErrorMessage={showVariationsErrorMessage}
            showValueForVariation={getShowValueForVariation(
              showValueForVariation,
              variationOption.name
            )}
            sliderDisplayThreshold={sliderDisplayThreshold}
            sliderArrowSize={sliderArrowSize}
            sliderItemsPerPage={sliderItemsPerPage}
          />
        )
      })}
    </div>
  )
}

export default memo(SKUSelector)

```


[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 store-components/react/components/SKUSelector/components/Variation.tsx from the store-components VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, memo, useState, Fragment, useCallback } from 'react'
import { Button } from 'vtex.styleguide'
import { IOMessage } from 'vtex.native-types'
import { SliderLayout } from 'vtex.slider-layout'
import { findIndex, propEq } from 'ramda'
import classnames from 'classnames'
import { useProduct } from 'vtex.product-context'
import { ResponsiveValuesTypes } from 'vtex.responsive-values'

import { stripUrl, isColor, slug } from '../utils'
import styles from '../styles.css'
import { DisplayVariation } from '../types'
import { imageUrlForSize, VARIATION_IMG_SIZE } from '../../module/images'
import ErrorMessage from './ErrorMessage'
import SelectModeVariation from './SelectVariationMode'
import SelectorItem from './SelectorItem'
import { ShowVariationsLabels } from './SKUSelector'

interface Props {
  variation: DisplayVariation
  maxSkuPrice?: number | null
  seeMoreLabel: string
  maxItems: number
  selectedItem: string | null
  showValueForVariation: boolean
  imageHeight?: number
  imageWidth?: number
  showBorders?: boolean
  showLabel: ShowVariationsLabels
  containerClasses?: string
  showErrorMessage: boolean
  mode?: string
  sliderDisplayThreshold: number
  sliderArrowSize: number
  sliderItemsPerPage: ResponsiveValuesTypes.ResponsiveValue<number>
}

const ITEMS_VISIBLE_THRESHOLD = 2

const findSelectedOption = (selectedItem: string | null) =>
  findIndex(propEq('label', selectedItem))

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {}

const Variation: FC<Props> = ({
  mode = 'default',
  maxItems,
  showLabel,
  variation,
  imageWidth,
  imageHeight,
  showBorders,
  maxSkuPrice,
  seeMoreLabel,
  selectedItem,
  showErrorMessage,
  showValueForVariation,
  containerClasses: containerClassesProp,
  sliderArrowSize,
  sliderDisplayThreshold,
  sliderItemsPerPage,
}) => {
  const { originalName, name, options } = variation

  const visibleItemsWhenCollapsed = maxItems - ITEMS_VISIBLE_THRESHOLD

  const [showAll, setShowAll] = useState(() => {
    const selectedOptionPosition = findSelectedOption(selectedItem)(options)

    return selectedOptionPosition >= visibleItemsWhenCollapsed
  })

  const {
    buyButton = {
      clicked: false,
    },
  } = useProduct()

  const displayImage = isColor(originalName)

  const shouldCollapse = !showAll && options.length > maxItems

  const overflowQuantity = options.length - visibleItemsWhenCollapsed
  const displayOptions = options.slice(
    0,
    shouldCollapse ? visibleItemsWhenCollapsed : options.length
  )

  const showAllAction = useCallback(() => setShowAll(true), [setShowAll])
  const containerClasses = classnames(
    'flex flex-column',
    containerClassesProp,
    styles.skuSelectorSubcontainer,
    `${styles.skuSelectorSubcontainer}--${slug(originalName)}`
  )

  const shouldUseSlider =
    displayOptions.length > sliderDisplayThreshold && mode === 'slider'

  const sliderConfigurationProps = {
    itemsPerPage: sliderItemsPerPage,
    infinite: true,
    showNavigationArrows: 'always',
    showPaginationDots: 'never',
    arrowSize: sliderArrowSize,
    fullWidth: false,
  }

  // The following code is here to maintain backwards compatibility
  let variationLabel = ''

  if (typeof showLabel === 'boolean') {
    variationLabel = showLabel ? 'variation' : 'none'
  } else {
    variationLabel = showLabel
  }

  const showVariationLabelName =
    variationLabel === 'variation' || variationLabel === 'variationAndItemValue'

  const selectorItemsArray = displayOptions.map(option => {
    return (
      <SelectorItem
        isSelected={option.label === selectedItem}
        key={`${option.label}-${name}`}
        isAvailable={option.available}
        maxPrice={maxSkuPrice}
        onClick={option.impossible ? noop : option.onSelectItem}
        isImage={displayImage}
        variationValue={option.label}
        variationValueOriginalName={option.originalName}
        imageHeight={imageHeight}
        imageWidth={imageWidth}
        showBorders={showBorders}
        imageUrl={
          option.image &&
          imageUrlForSize(stripUrl(option.image.imageUrl), VARIATION_IMG_SIZE)
        }
        imageLabel={option.image?.imageLabel}
        isImpossible={option.impossible}
        variationLabel={variationLabel}
        label={name}
      />
    )
  })

  return (
    <div className={containerClasses}>
      <div className={`${styles.skuSelectorNameContainer} ma1`}>
        <div className={`${styles.skuSelectorTextContainer} db mb3`}>
          {showVariationLabelName && (
            <span
              className={`${styles.skuSelectorName} c-muted-1 t-small overflow-hidden`}
            >
              {name}
              {showErrorMessage && buyButton.clicked && !selectedItem && (
                <ErrorMessage />
              )}
            </span>
          )}
          {selectedItem && showValueForVariation && (
            <Fragment>
              <span
                className={`${styles.skuSelectorNameSeparator} c-muted-1 t-small`}
              >
                :{' '}
              </span>
              <span
                className={`${styles.skuSelectorSelectorImageValue} c-muted-1 t-small`}
              >
                {selectedItem}
              </span>
            </Fragment>
          )}
        </div>
        <div
          className={`${styles.skuSelectorOptionsList} w-100 inline-flex flex-wrap ml2 items-center`}
        >
          {mode === 'select' && !displayImage ? (
            <SelectModeVariation
              selectedItem={selectedItem}
              displayOptions={displayOptions}
            />
          ) : shouldUseSlider ? (
            <SliderLayout {...sliderConfigurationProps}>
              {selectorItemsArray}
            </SliderLayout>
          ) : (
            selectorItemsArray
          )}
          {!showAll && shouldCollapse && (
            <div className={styles.seeMoreButton}>
              <Button
                variation="tertiary"
                onClick={showAllAction}
                size="small"
                collapseLeft
              >
                <IOMessage
                  id={seeMoreLabel}
                  values={{ quantity: overflowQuantity }}
                  data-testid="seeMoreLabel"
                />
              </Button>
            </div>
          )}
        </div>
      </div>
    </div>
  )
}

export default memo(Variation)

```