[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-on-localhost/src/monobloco/components/new/StoreComponents/components/SearchBar/components/SearchBar.js from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useRef, useCallback, useState, useEffect, useMemo } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import Downshift from 'downshift'
import debounce from 'debounce'
import {
  NoSSR,
  useRuntime,
  ExtensionPoint,
  useChildBlock,
} from '../../../../../../../vtex.render-runtime'
import Overlay from '../../../../ReactPortal/Overlay'
import useCssHandles from '../../../../CssHandles/useCssHandles'
import { defineMessages } from 'react-intl'
import styles from '../styles.css'

import AutocompleteResults from '../../AutocompleteResults'

import AutocompleteInput from './AutocompleteInput'

const CSS_HANDLES = ['searchBarInnerContainer']
const SEARCH_DELAY_TIME = 500
const AUTCOMPLETE_EXTENSION_ID = 'autocomplete-result-list'

const messages = defineMessages({
  searchTermTooShort: {
    id: 'store/search.search-term-too-short',
    defaultMessage: '',
  },
})

const SearchBar = ({
  placeholder,
  onInputChange,
  onGoToSearchPage,
  onClearInput,
  inputValue,
  compactMode,
  hasIconLeft,
  iconClasses,
  iconBlockClass,
  autoFocus,
  maxWidth,
  attemptPageTypeSearch,
  customSearchPageUrl,
  autocompleteAlignment,
  openAutocompleteOnFocus,
  blurOnSubmit,
  submitOnIconClick,
  minSearchTermLength,
  autocompleteFullWidth,
  intl,
}) => {
  const container = useRef()
  const { navigate } = useRuntime()
  const handles = useCssHandles(CSS_HANDLES)
  const [searchTerm, setSearchTerm] = useState(inputValue)
  const [inputErrorMessage, setInputErrorMessage] = useState()

  const debouncedSetSearchTerm = useCallback(
    debounce(newValue => {
      setSearchTerm(newValue)
    }, SEARCH_DELAY_TIME),
    []
  )

  useEffect(() => {
    debouncedSetSearchTerm(inputValue)
  }, [debouncedSetSearchTerm, inputValue])

  const onSelect = useCallback(
    element => {
      if (!element) {
        return
      }

      if (element.term) {
        if (attemptPageTypeSearch) {
          window.location.href = `/${element.term}`
          return
        }

        if (customSearchPageUrl) {
          navigate({
            to: customSearchPageUrl.replace(/\$\{term\}/g, element.term),
          })

          return
        }

        navigate({
          page: 'store.search',
          params: { term: element.term },
          query: 'map=ft',
        })
        return
      }

      let page = 'store.product'
      let params = {
        slug: element.slug,
        id: element.productId,
      }
      let query = ''
      const terms = element.slug.split('/')

      if (element.criteria) {
        // This param is only useful to track terms searched
        // See: https://support.google.com/analytics/answer/1012264
        const paramForSearchTracking = '&_c=' + terms[0]

        page = 'store.search'
        params = { term: terms[0] }
        query =
          `map=c,ft&rest=${terms.slice(1).join(',')}` + paramForSearchTracking
      }

      navigate({ page, params, query })
    },
    [navigate, attemptPageTypeSearch, customSearchPageUrl]
  )

  const validateInput = () => {
    if (minSearchTermLength && inputValue.length < minSearchTermLength) {
      return intl.formatMessage(messages.searchTermTooShort)
    }

    return null
  }

  const showInputErrorMessage = inputErrorMessage => {
    setInputErrorMessage(inputErrorMessage)
  }

  const hideInputErrorMessage = () => {
    setInputErrorMessage()
  }

  const fallback = (
    <AutocompleteInput
      placeholder={placeholder}
      onInputChange={onInputChange}
      inputValue={inputValue}
      hasIconLeft={hasIconLeft}
      iconClasses={iconClasses}
      iconBlockClass={iconBlockClass}
      inputErrorMessage={inputErrorMessage}
      onGoToSearchPage={onGoToSearchPage}
    />
  )

  const isAutocompleteDeclared = Boolean(
    useChildBlock({ id: AUTCOMPLETE_EXTENSION_ID })
  )

  const SelectedAutocompleteResults = useMemo(() => {
    return isAutocompleteDeclared
      ? props => <ExtensionPoint id={AUTCOMPLETE_EXTENSION_ID} {...props} />
      : props => <AutocompleteResults {...props} />
  }, [isAutocompleteDeclared])

  return (
    <div
      ref={container}
      className={classNames('w-100 mw7 pv4', styles.searchBarContainer)}
      style={{
        ...(maxWidth && {
          maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth,
        }),
      }}
    >
      <NoSSR onSSR={fallback}>
        <Downshift onSelect={onSelect}>
          {({
            getInputProps,
            getItemProps,
            getMenuProps,
            selectedItem,
            highlightedIndex,
            isOpen,
            closeMenu,
            openMenu,
          }) => (
            <div
              className={classNames(
                'relative-m w-100',
                handles.searchBarInnerContainer
              )}
            >
              <AutocompleteInput
                // eslint-disable-next-line jsx-a11y/no-autofocus
                autoFocus={autoFocus}
                compactMode={compactMode}
                onClearInput={onClearInput}
                hasIconLeft={hasIconLeft}
                iconClasses={iconClasses}
                onGoToSearchPage={onGoToSearchPage}
                submitOnIconClick={submitOnIconClick}
                openAutocompleteOnFocus={openAutocompleteOnFocus}
                openMenu={openMenu}
                inputErrorMessage={inputErrorMessage}
                {...getInputProps({
                  onKeyDown: event => {
                    // Only call default search function if user doesn't
                    // have any item highlighted in the menu options
                    if (event.key === 'Enter' && highlightedIndex === null) {
                      const errorMessage = validateInput()

                      if (errorMessage) {
                        showInputErrorMessage(errorMessage)
                        return
                      }

                      if (blurOnSubmit) {
                        event.currentTarget.blur()
                      }

                      onGoToSearchPage()
                      closeMenu()
                    } else {
                      hideInputErrorMessage()
                    }
                  },
                  placeholder,
                  value: inputValue,
                  onChange: onInputChange,
                  onFocus: openAutocompleteOnFocus ? openMenu : undefined,
                })}
              />
              <Overlay
                alignment={autocompleteAlignment}
                fullWindow={autocompleteFullWidth}
              >
                <SelectedAutocompleteResults
                  parentContainer={container}
                  {...{
                    attemptPageTypeSearch,
                    isOpen,
                    getMenuProps,
                    inputValue: searchTerm,
                    getItemProps,
                    selectedItem,
                    highlightedIndex,
                    closeMenu,
                    onClearInput,
                    customSearchPageUrl,
                  }}
                />
              </Overlay>
            </div>
          )}
        </Downshift>
      </NoSSR>
    </div>
  )
}

SearchBar.propTypes = {
  /** Placeholder to be used on the input */
  placeholder: PropTypes.string.isRequired,
  /** Current value of the input */
  inputValue: PropTypes.string.isRequired,
  /** Function to handle input changes */
  onInputChange: PropTypes.func.isRequired,
  /** Function to direct the user to the searchPage */
  onGoToSearchPage: PropTypes.func.isRequired,
  /** Function to clear the input */
  onClearInput: PropTypes.func.isRequired,
  /** Indentify when use the compact version of the component */
  compactMode: PropTypes.bool,
  /** Identify if the search icon is on left or right position */
  hasIconLeft: PropTypes.bool,
  /** Custom classes for the search icon */
  iconClasses: PropTypes.string,
  /** Block class for the search icon */
  iconBlockClass: PropTypes.string,
  /** Identify if the search input should autofocus or not */
  autoFocus: PropTypes.bool,
  /** Max width of the search bar */
  maxWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /** A template for a custom url. It can have a substring ${term} used as placeholder to interpolate the searched term. (e.g. `/search?query=${term}`) */
  customSearchPageUrl: PropTypes.string,
  /** Uses the term the user has inputed to try to navigate to the proper
   * page type (e.g. a department, a brand, a category)
   */
  attemptPageTypeSearch: PropTypes.bool,
  /* Autocomplete Horizontal alignment */
  autocompleteAlignment: PropTypes.string,
  /** Identify if autocomplete should be open on input focus or not */
  openAutocompleteOnFocus: PropTypes.bool,
  /** Identify if input should blur on submit */
  blurOnSubmit: PropTypes.bool,
  /** Identify if icon should submit on click */
  submitOnIconClick: PropTypes.bool,
  /** Minimum search term length allowed */
  minSearchTermLength: PropTypes.number,
  /** If true, the autocomplete will fill the whole window horizontally */
  autocompleteFullWidth: PropTypes.bool,
}

export default SearchBar

```


[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-on-localhost/src/monobloco/components/new/StoreComponents/components/SearchBar/components/AutocompleteInput.js from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import Input from '../../../../Styleguide/Input'
import { ExtensionPoint, useChildBlock } from '../../../../../../../vtex.render-runtime'
import useCssHandles from '../../../../CssHandles/useCssHandles'

import IconSearch from '../../../../StoreIcons/IconSearch'
import IconClose from '../../../../StoreIcons/IconClose'

/** Midleware component to adapt the styleguide/Input to be used by the Downshift*/
const CSS_HANDLES = [
  'searchBarIcon',
  'compactMode',
  'autoCompleteOuterContainer',
  'paddingInput',
]

const CloseIcon = () => {
  const hasIconBlock = Boolean(useChildBlock({ id: 'icon-close' }))

  if (hasIconBlock) {
    return <ExtensionPoint id="icon-close" size={22} type="line" />
  }

  return <IconClose type="line" size={22} />
}

const SearchIcon = () => {
  const hasIconBlock = Boolean(useChildBlock({ id: 'icon-search' }))

  if (hasIconBlock) {
    return <ExtensionPoint id="icon-search" />
  }

  return <IconSearch />
}

const AutocompleteInput = ({
  onClearInput,
  compactMode,
  value,
  hasIconLeft,
  iconBlockClass,
  iconClasses,
  autoFocus,
  onGoToSearchPage,
  submitOnIconClick,
  openMenu,
  inputErrorMessage,
  ...restProps
}) => {
  const inputRef = useRef(null)
  const handles = useCssHandles(CSS_HANDLES)

  useEffect(() => {
    const changeClassInput = () => {
      if (compactMode) {
        inputRef.current.placeholder = ''
        inputRef.current.classList.add(handles.paddingInput)
      }
    }

    changeClassInput()
    autoFocus && inputRef.current.focus()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const suffix = (
    <button
      className={`${iconClasses || ''} ${
        handles.searchBarIcon
      } flex items-center pointer bn bg-transparent outline-0`}
      onClick={
        submitOnIconClick ? onGoToSearchPage : () => value && onClearInput()
      }
    >
      {value && !submitOnIconClick ? (
        <CloseIcon />
      ) : (
        !hasIconLeft && <SearchIcon />
      )}
    </button>
  )

  const prefix = (
    <span className={`${iconClasses} ${handles.searchBarIcon}`}>
      <SearchIcon />
    </span>
  )

  const classContainer = classNames('w-100', {
    [handles.compactMode]: compactMode,
  })

  return (
    <div className={`${handles.autoCompleteOuterContainer} flex`}>
      <div className={classContainer}>
        <Input
          ref={inputRef}
          size="large"
          value={value}
          prefix={hasIconLeft && prefix}
          suffix={suffix}
          {...restProps}
          error={Boolean(inputErrorMessage)}
          errorMessage={inputErrorMessage}
        />
      </div>
    </div>
  )
}

AutocompleteInput.propTypes = {
  /** Downshift prop to be passed to the input */
  autoComplete: PropTypes.string,
  /** Input ID */
  id: PropTypes.string,
  /** Downshift prop to be passed to the input */
  onBlur: PropTypes.func,
  /** Downshift prop to be passed to the input */
  onChange: PropTypes.func,
  /** Downshift prop to be passed to the input */
  onKeyDown: PropTypes.func,
  /** Downshift prop to be passed to the input */
  value: PropTypes.string,
  /** Downshift func to open the menu */
  openMenu: PropTypes.func,
  /** Placeholder to be used on the input */
  placeholder: PropTypes.string,
  compactMode: PropTypes.bool,
  /** Clears the input */
  onClearInput: PropTypes.func,
  /** Identify if the search icon is on left or right position */
  hasIconLeft: PropTypes.bool,
  /** Custom classes for the search icon */
  iconClasses: PropTypes.string,
  /** Block class for the search icon */
  iconBlockClass: PropTypes.string,
  /** Identify if the search input should autofocus or not */
  autoFocus: PropTypes.bool,
  /** Function to direct the user to the searchPage */
  onGoToSearchPage: PropTypes.func.isRequired,
  /** Identify if icon should submit on click */
  submitOnIconClick: PropTypes.bool,
  /** Error message showed in search input */
  inputErrorMessage: PropTypes.string,
}

export default AutocompleteInput

```


[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-on-localhost/src/monobloco/components/new/StoreComponents/components/BuyButton/mutations/setOpenMinicart.gql from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation setMinicartOpen($isOpen: Boolean!) {
  setMinicartOpen(isOpen: $isOpen) @client
}

```


[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-on-localhost/src/monobloco/components/new/StoreComponents/components/BuyButton/mutations/addToCart.gql from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation addToCart($items: [MinicartItem]) {
  addToCart(items: $items) @client
}

```


[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-on-localhost/src/monobloco/components/new/StoreComponents/components/BuyButton/queries/installedApp.gql from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```graphql
query installedAppPublic($slug: String!) {
  installedAppPublic(slug: $slug) @context(provider: "vtex.apps-graphql") {
    version
  }
}

```


[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-on-localhost/src/monobloco/components/new/StoreComponents/components/BuyButton/hooks/useMarketingSessionParams.js from the store-on-localhost 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-on-localhost/src/monobloco/components/new/StoreComponents/components/Greeting/queries/orderForm.gql from the store-on-localhost 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-on-localhost/src/monobloco/components/new/StoreComponents/components/Slider/utils/ItemsPerPage.js from the store-on-localhost 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)
    slidesArray.map(slide => {
      const attributes = Array.prototype.slice.call(slide.attributes)
      attributes.map(attr => {
        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.
 */
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-on-localhost/src/monobloco/components/new/StoreComponents/components/Slider/components/Arrow.js from the store-on-localhost 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.
 */
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 (
      <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-on-localhost/src/monobloco/components/new/StoreComponents/components/Slider/components/Dots.js from the store-on-localhost 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;
 */
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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/utils/generateUrl.d.ts from the store-on-localhost 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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/utils/aspectRatioUtil.tsx from the store-on-localhost 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' || 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) => {
  let width = size
  let height: number | string = 'auto'

  if (aspectRatio && aspectRatio !== 'auto') {
    height = size * (parseAspectRatio(aspectRatio) || 1)

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

    if (height > maxSize) {
      width = 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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/utils/enums.js from the store-on-localhost 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 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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/utils/generateUrl.js from the store-on-localhost 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+)/)

export function cleanImageUrl(imageUrl) {
  const result = baseUrlRegex.exec(imageUrl)
  if (result.length > 0) return result[0]
}

function replaceLegacyFileManagerUrl(imageUrl, width, height) {
  const legacyUrlPattern = '/arquivos/ids/'
  const isLegacyUrl = imageUrl.includes(legacyUrlPattern)
  if (!isLegacyUrl) return imageUrl
  return `${cleanImageUrl(imageUrl)}-${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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/utils/generateImageConfig.js from the store-on-localhost 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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/ProductImage.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, useMemo, useRef } from 'react'
import Zoomable, { ZoomMode } from './Zoomable'
import { imageUrl } from '../utils/aspectRatioUtil'
import useCssHandles from '../../../../CssHandles/useCssHandles'

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

interface Props {
  src: string
  alt: string
  zoomMode: ZoomMode
  zoomFactor: number
  aspectRatio?: AspectRatio
  maxHeight?: number
}

type AspectRatio = string | number

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

const ProductImage: FC<Props> = ({
  src,
  alt,
  zoomMode = ZoomMode.InPlaceClick,
  zoomFactor = 2,
  aspectRatio='auto',
  maxHeight = 600,
}) => {
  const srcSet = useMemo(() => (
    IMAGE_SIZES
      .map(size => `${imageUrl(src, size, MAX_SIZE, aspectRatio)} ${size}w`)
      .join(',')
  ), [src, aspectRatio, IMAGE_SIZES])
  const handles = useCssHandles(CSS_HANDLES)

  const imageRef = useRef(null)

  return (
    <div className={handles.productImage}>
      <Zoomable
        mode={zoomMode}
        factor={zoomFactor}
        zoomContent={(
          <img
            src={imageUrl(src, DEFAULT_SIZE * zoomFactor, MAX_SIZE, aspectRatio)}
            className={handles.productImageTag}
            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={handles.productImageTag}
              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="lazy"

              // 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>
  )
}

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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Video/Vimeo.js from the store-on-localhost 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, width, html, title } = response

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

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

        this.setState({
          iframe: {
            divStyle: { padding: `${(100 * height) / width}% 0 0 0` },
            src: 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,
}

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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Video/index.js from the store-on-localhost 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 Vimeo from './Vimeo'
import YouTube from './Youtube'

import useCssHandles from '../../../../../CssHandles/useCssHandles'

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)
  }

  else 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) && <Vimeo {...props} cssHandles={handles} />}
      {isYoutube(url) && <YouTube {...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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Video/Youtube.js from the store-on-localhost 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\?))\??v?=?([^#&?]*).*/
    const match = url.match(regExp)
    if (match && match[7].length === 11) return match[7]
    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,
}

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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Gallery/global.css from the store-on-localhost 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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Gallery/index.js from the store-on-localhost 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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Gallery/Gallery.js from the store-on-localhost 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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Zoomable/index.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, ReactElement } from 'react'
import useDevice from '../../../../../DeviceDetector/useDevice'
import ZoomInPlace from './ZoomInPlace'

export enum ZoomMode {
  InPlaceClick = 'in-place-click',
  InPlaceHover = 'in-place-hover',
  Disabled = 'disabled'
}

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

const Zoomable: FC<Props> = ({ children, factor = 2, zoomContent, mode = ZoomMode.InPlaceClick }) => {
  const { isMobile } = useDevice()

  if (isMobile && mode !== ZoomMode.Disabled) {
    // 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 ZoomMode.InPlaceHover:
      return (
        <ZoomInPlace type="hover" factor={factor} zoomContent={zoomContent}>
          {children}
        </ZoomInPlace>
      )
    case ZoomMode.InPlaceClick:
      return (
        <ZoomInPlace type="click" factor={factor} zoomContent={zoomContent}>
          {children}
        </ZoomInPlace>
      )
    case ZoomMode.Disabled:
    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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Zoomable/ZoomInPlace.tsx from the store-on-localhost 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)

    if (!isZoomedIn) {
      const mousePosition = getMousePositionFromEvent(event, getContainerBounds())
      setPositionAndScale(mousePosition.x, mousePosition.y, factor)
    }
  }

  // Resets position when the image is zoomed out
  useEffect(() => {
    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 = () => {
    if (isZoomedIn) {
      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 () => {
      if (document) {
        document.removeEventListener('mousemove', handleMouseMove)
        if (type === 'click') {
          document.removeEventListener('click', handleClickOutside)
        }
      }
    }
  })

  return (
    <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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Carousel/ThumbnailSwiper.js from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import { THUMB_SIZE } from '../../../module/images'
import classNames from 'classnames'

import { THUMBS_POSITION_HORIZONTAL } from '../../utils/enums'
import { imageUrl } from '../../utils/aspectRatioUtil'
import useCssHandles from '../../../../../CssHandles/useCssHandles'

const THUMB_MAX_SIZE = 256

/** Swiper and its modules are imported using require to avoid breaking SSR */
const Swiper = window.navigator
  ? require('react-id-swiper/lib/ReactIdSwiper').default
  : null

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

const Thumbnail = ({
  alt,
  index,
  onThumbClick,
  height,
  thumbUrl,
  handles,
  maxHeight = 150,
  aspectRatio = 'auto',
  itemContainerClasses,
}) => {
  return (
    <div
      className={itemContainerClasses}
      style={{ height, maxHeight: maxHeight || 'unset' }}
      onClick={() => onThumbClick(index)}
    >
      <figure
        className={handles.figure}
        itemProp="associatedMedia"
        itemScope
        itemType="http://schema.org/ImageObject"
      >
        <img
          className={`${handles.thumbImg} 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}`}
      />
    </div>
  )
}

const ThumbnailSwiper = ({
  isThumbsVertical,
  slides,
  swiperParams,
  thumbUrls,
  position,
  onThumbClick,
  activeIndex,
  thumbnailAspectRatio,
  thumbnailMaxHeight,
}) => {
  const hasThumbs = slides.length > 1
  const handles = useCssHandles(CSS_HANDLES)

  const thumbClasses = 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,
  })

  return (
    <div className={thumbClasses} data-testid="thumbnail-swiper">
      <Swiper {...swiperParams} shouldSwiperUpdate>
        {slides.map((slide, i) => {
          const itemContainerClasses = classNames('swiper-slide mb5 pointer',
            handles.productImagesThumb,
            {
              'w-20': !isThumbsVertical,
              'w-100': isThumbsVertical,
              'swiper-slide-active': activeIndex === i,
              [handles.productImagesThumbActive]: activeIndex === i,
            }
          )

          return (
            <Thumbnail
              key={`${i}-${slide.alt}`}
              itemContainerClasses={itemContainerClasses}
              index={i}
              handles={handles}
              height={isThumbsVertical ? 'auto' : '115px'}
              onThumbClick={onThumbClick}
              alt={slide.alt}
              thumbUrl={slide.thumbUrl || thumbUrls[i]}
              aspectRatio={thumbnailAspectRatio}
              maxHeight={thumbnailMaxHeight}
            />
          )
        })}
      </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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Carousel/ImagePlaceholder.js from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import style from '../../styles.css'

export default function ImagePlaceholder() {
  return (
    <div className="relative">
      <div
        className={`${
          style.carouselImagePlaceholder
        } absolute w-100 h-100 contain bg-center`}
      />
      <svg
        width="100%"
        height="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>
  )
}

```


[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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Carousel/global.css from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```css
/**
 * Swiper 4.4.1
 * Most modern mobile touch slider and framework with hardware accelerated transitions
 * http://www.idangero.us/swiper/
 *
 * Copyright 2014-2018 Vladimir Kharlampidi
 *
 * Released under the MIT License
 *
 * Released on: September 14, 2018
 */

/**
 * 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%;
}

.swiper-container {
  margin: 0 auto;
  position: relative;
  overflow: hidden;
  list-style: none;
  padding: 0;
  /* Fix of Webkit flickering */
  z-index: 1;
}
.swiper-container-no-flexbox .swiper-slide {
  float: left;
}
.swiper-container-vertical > .swiper-wrapper {
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
}
.swiper-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-transition-property: -webkit-transform;
  transition-property: -webkit-transform;
  -o-transition-property: transform;
  transition-property: transform;
  transition-property: transform, -webkit-transform;
  -webkit-box-sizing: content-box;
  box-sizing: content-box;
  align-items: center;
}
.swiper-container-android .swiper-slide,
.swiper-wrapper {
  -webkit-transform: translate3d(0px, 0, 0);
  transform: translate3d(0px, 0, 0);
}
.swiper-container-multirow > .swiper-wrapper {
  -webkit-flex-wrap: wrap;
  -ms-flex-wrap: wrap;
  flex-wrap: wrap;
}
.swiper-container-free-mode > .swiper-wrapper {
  -webkit-transition-timing-function: ease-out;
  -o-transition-timing-function: ease-out;
  transition-timing-function: ease-out;
  margin: 0 auto;
}
.swiper-slide {
  -webkit-flex-shrink: 0;
  -ms-flex-negative: 0;
  flex-shrink: 0;
  width: 100%;
  height: 100%;
  position: relative;
  -webkit-transition-property: -webkit-transform;
  transition-property: -webkit-transform;
  -o-transition-property: transform;
  transition-property: transform;
  transition-property: transform, -webkit-transform;
}
.swiper-slide-invisible-blank {
  visibility: hidden;
}
/* Auto Height */
.swiper-container-autoheight,
.swiper-container-autoheight .swiper-slide {
  height: auto;
}
.swiper-container-autoheight .swiper-wrapper {
  -webkit-box-align: start;
  -webkit-align-items: flex-start;
  -ms-flex-align: start;
  align-items: flex-start;
  -webkit-transition-property: height, -webkit-transform;
  transition-property: height, -webkit-transform;
  -o-transition-property: transform, height;
  transition-property: transform, height;
  transition-property: transform, height, -webkit-transform;
}
/* 3D Effects */
.swiper-container-3d {
  -webkit-perspective: 1200px;
  perspective: 1200px;
}
.swiper-container-3d .swiper-wrapper,
.swiper-container-3d .swiper-slide,
.swiper-container-3d .swiper-slide-shadow-left,
.swiper-container-3d .swiper-slide-shadow-right,
.swiper-container-3d .swiper-slide-shadow-top,
.swiper-container-3d .swiper-slide-shadow-bottom,
.swiper-container-3d .swiper-cube-shadow {
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
}
.swiper-container-3d .swiper-slide-shadow-left,
.swiper-container-3d .swiper-slide-shadow-right,
.swiper-container-3d .swiper-slide-shadow-top,
.swiper-container-3d .swiper-slide-shadow-bottom {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 10;
}
.swiper-container-3d .swiper-slide-shadow-left {
  background-image: -webkit-gradient(
    linear,
    right top,
    left top,
    from(rgba(0, 0, 0, 0.5)),
    to(rgba(0, 0, 0, 0))
  );
  background-image: -webkit-linear-gradient(
    right,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: -o-linear-gradient(
    right,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: linear-gradient(
    to left,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
}
.swiper-container-3d .swiper-slide-shadow-right {
  background-image: -webkit-gradient(
    linear,
    left top,
    right top,
    from(rgba(0, 0, 0, 0.5)),
    to(rgba(0, 0, 0, 0))
  );
  background-image: -webkit-linear-gradient(
    left,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: -o-linear-gradient(
    left,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: linear-gradient(
    to right,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
}
.swiper-container-3d .swiper-slide-shadow-top {
  background-image: -webkit-gradient(
    linear,
    left bottom,
    left top,
    from(rgba(0, 0, 0, 0.5)),
    to(rgba(0, 0, 0, 0))
  );
  background-image: -webkit-linear-gradient(
    bottom,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: -o-linear-gradient(
    bottom,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
}
.swiper-container-3d .swiper-slide-shadow-bottom {
  background-image: -webkit-gradient(
    linear,
    left top,
    left bottom,
    from(rgba(0, 0, 0, 0.5)),
    to(rgba(0, 0, 0, 0))
  );
  background-image: -webkit-linear-gradient(
    top,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: -o-linear-gradient(
    top,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
  background-image: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.5),
    rgba(0, 0, 0, 0)
  );
}
/* IE10 Windows Phone 8 Fixes */
.swiper-container-wp8-horizontal,
.swiper-container-wp8-horizontal > .swiper-wrapper {
  -ms-touch-action: pan-y;
  touch-action: pan-y;
}
.swiper-container-wp8-vertical,
.swiper-container-wp8-vertical > .swiper-wrapper {
  -ms-touch-action: pan-x;
  touch-action: pan-x;
}
.swiper-button-prev,
.swiper-button-next {
  position: absolute;
  top: 50%;
  width: 27px;
  height: 44px;
  margin-top: -22px;
  z-index: 10;
  cursor: pointer;
  background-size: 27px 44px;
  background-position: center;
  background-repeat: no-repeat;
}
.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 {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E");
  left: 10px;
  right: auto;
}
.swiper-button-next,
.swiper-container-rtl .swiper-button-prev {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E");
  right: 10px;
  left: auto;
}
.swiper-button-prev.swiper-button-white,
.swiper-container-rtl .swiper-button-next.swiper-button-white {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E");
}
.swiper-button-next.swiper-button-white,
.swiper-container-rtl .swiper-button-prev.swiper-button-white {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E");
}
.swiper-button-prev.swiper-button-black,
.swiper-container-rtl .swiper-button-next.swiper-button-black {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E");
}
.swiper-button-next.swiper-button-black,
.swiper-container-rtl .swiper-button-prev.swiper-button-black {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E");
}
.swiper-button-lock {
  display: none;
}
.swiper-pagination {
  position: absolute;
  text-align: center;
  -webkit-transition: 300ms opacity;
  -o-transition: 300ms opacity;
  transition: 300ms opacity;
  -webkit-transform: translate3d(0, 0, 0);
  transform: translate3d(0, 0, 0);
  z-index: 10;
}
.swiper-pagination.swiper-pagination-hidden {
  opacity: 0;
}
/* Common Styles */
.swiper-pagination-fraction,
.swiper-pagination-custom,
.swiper-container-horizontal > .swiper-pagination-bullets {
  bottom: 0;
  padding-bottom: 10px;
  left: 0;
  width: 100%;
}
/* Bullets */
.swiper-pagination-bullets-dynamic {
  overflow: hidden;
  font-size: 0;
}
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet {
  -webkit-transform: scale(0.33);
  -ms-transform: scale(0.33);
  transform: scale(0.33);
  position: relative;
}
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active {
  -webkit-transform: scale(1);
  -ms-transform: scale(1);
  transform: scale(1);
}
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-main {
  -webkit-transform: scale(1);
  -ms-transform: scale(1);
  transform: scale(1);
}
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev {
  -webkit-transform: scale(0.66);
  -ms-transform: scale(0.66);
  transform: scale(0.66);
}
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev {
  -webkit-transform: scale(0.33);
  -ms-transform: scale(0.33);
  transform: scale(0.33);
}
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next {
  -webkit-transform: scale(0.66);
  -ms-transform: scale(0.66);
  transform: scale(0.66);
}
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next {
  -webkit-transform: scale(0.33);
  -ms-transform: scale(0.33);
  transform: scale(0.33);
}
.swiper-pagination-bullet {
  width: 8px;
  height: 8px;
  display: inline-block;
  border-radius: 100%;
  background: #000;
  opacity: 0.2;
}
button.swiper-pagination-bullet {
  border: none;
  margin: 0;
  padding: 0;
  -webkit-box-shadow: none;
  box-shadow: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}
.swiper-pagination-clickable .swiper-pagination-bullet {
  cursor: pointer;
}
.swiper-pagination-bullet-active {
  opacity: 1;
  background: currentColor;
}
.swiper-container-vertical > .swiper-pagination-bullets {
  right: 10px;
  top: 50%;
  -webkit-transform: translate3d(0px, -50%, 0);
  transform: translate3d(0px, -50%, 0);
}
.swiper-container-vertical
  > .swiper-pagination-bullets
  .swiper-pagination-bullet {
  margin: 6px 0;
  display: block;
}
.swiper-container-vertical
  > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
  top: 50%;
  -webkit-transform: translateY(-50%);
  -ms-transform: translateY(-50%);
  transform: translateY(-50%);
  width: 8px;
}
.swiper-container-vertical
  > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic
  .swiper-pagination-bullet {
  display: inline-block;
  -webkit-transition: 200ms top, 200ms -webkit-transform;
  transition: 200ms top, 200ms -webkit-transform;
  -o-transition: 200ms transform, 200ms top;
  transition: 200ms transform, 200ms top;
  transition: 200ms transform, 200ms top, 200ms -webkit-transform;
}
.swiper-container-horizontal
  > .swiper-pagination-bullets
  .swiper-pagination-bullet {
  margin: 0 4px;
}
.swiper-container-horizontal
  > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
  left: 50%;
  -webkit-transform: translateX(-50%);
  -ms-transform: translateX(-50%);
  transform: translateX(-50%);
  white-space: nowrap;
}
.swiper-container-horizontal
  > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic
  .swiper-pagination-bullet {
  -webkit-transition: 200ms left, 200ms -webkit-transform;
  transition: 200ms left, 200ms -webkit-transform;
  -o-transition: 200ms transform, 200ms left;
  transition: 200ms transform, 200ms left;
  transition: 200ms transform, 200ms left, 200ms -webkit-transform;
}
.swiper-container-horizontal.swiper-container-rtl
  > .swiper-pagination-bullets-dynamic
  .swiper-pagination-bullet {
  -webkit-transition: 200ms right, 200ms -webkit-transform;
  transition: 200ms right, 200ms -webkit-transform;
  -o-transition: 200ms transform, 200ms right;
  transition: 200ms transform, 200ms right;
  transition: 200ms transform, 200ms right, 200ms -webkit-transform;
}
/* Progress */
.swiper-pagination-progressbar {
  background: rgba(0, 0, 0, 0.25);
  position: absolute;
}
.swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
  background: currentColor;
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  -webkit-transform: scale(0);
  -ms-transform: scale(0);
  transform: scale(0);
  -webkit-transform-origin: left top;
  -ms-transform-origin: left top;
  transform-origin: left top;
}
.swiper-container-rtl
  .swiper-pagination-progressbar
  .swiper-pagination-progressbar-fill {
  -webkit-transform-origin: right top;
  -ms-transform-origin: right top;
  transform-origin: right top;
}
.swiper-container-horizontal > .swiper-pagination-progressbar,
.swiper-container-vertical
  > .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite {
  width: 100%;
  height: 4px;
  left: 0;
  top: 0;
}
.swiper-container-vertical > .swiper-pagination-progressbar,
.swiper-container-horizontal
  > .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite {
  width: 4px;
  height: 100%;
  left: 0;
  top: 0;
}
.swiper-pagination-white .swiper-pagination-bullet-active {
  background: #ffffff;
}
.swiper-pagination-progressbar.swiper-pagination-white {
  background: rgba(255, 255, 255, 0.25);
}
.swiper-pagination-progressbar.swiper-pagination-white
  .swiper-pagination-progressbar-fill {
  background: #ffffff;
}
.swiper-pagination-black .swiper-pagination-bullet-active {
  background: #000000;
}
.swiper-pagination-progressbar.swiper-pagination-black {
  background: rgba(0, 0, 0, 0.25);
}
.swiper-pagination-progressbar.swiper-pagination-black
  .swiper-pagination-progressbar-fill {
  background: #000000;
}
.swiper-pagination-lock {
  display: none;
}
/* Scrollbar */
.swiper-scrollbar {
  border-radius: 10px;
  position: relative;
  -ms-touch-action: none;
  background: rgba(0, 0, 0, 0.1);
}
.swiper-container-horizontal > .swiper-scrollbar {
  position: absolute;
  left: 1%;
  bottom: 3px;
  z-index: 50;
  height: 5px;
  width: 98%;
}
.swiper-container-vertical > .swiper-scrollbar {
  position: absolute;
  right: 3px;
  top: 1%;
  z-index: 50;
  width: 5px;
  height: 98%;
}
.swiper-scrollbar-drag {
  height: 100%;
  width: 100%;
  position: relative;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 10px;
  left: 0;
  top: 0;
}
.swiper-scrollbar-cursor-drag {
  cursor: move;
}
.swiper-scrollbar-lock {
  display: none;
}
.swiper-zoom-container {
  width: 100%;
  height: 100%;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
  -webkit-justify-content: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -webkit-align-items: center;
  -ms-flex-align: center;
  align-items: center;
  text-align: center;
}
.swiper-zoom-container > img,
.swiper-zoom-container > svg,
.swiper-zoom-container > canvas {
  max-width: 100%;
  max-height: 100%;
  -o-object-fit: contain;
  object-fit: contain;
}
.swiper-slide-zoomed {
  cursor: move;
}
/* Preloader */
.swiper-lazy-preloader {
  width: 42px;
  height: 42px;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -21px;
  margin-top: -21px;
  z-index: 10;
  -webkit-transform-origin: 50%;
  -ms-transform-origin: 50%;
  transform-origin: 50%;
  -webkit-animation: swiper-preloader-spin 1s steps(12, end) infinite;
  animation: swiper-preloader-spin 1s steps(12, end) infinite;
}
.swiper-lazy-preloader:after {
  display: block;
  content: '';
  width: 100%;
  height: 100%;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%236c6c6c'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  background-position: 50%;
  background-size: 100%;
  background-repeat: no-repeat;
}
.swiper-lazy-preloader-white:after {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%23fff'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
@-webkit-keyframes swiper-preloader-spin {
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes swiper-preloader-spin {
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
/* a11y */
.swiper-container .swiper-notification {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
  opacity: 0;
  z-index: -1000;
}
.swiper-container-fade.swiper-container-free-mode .swiper-slide {
  -webkit-transition-timing-function: ease-out;
  -o-transition-timing-function: ease-out;
  transition-timing-function: ease-out;
}
.swiper-container-fade .swiper-slide {
  pointer-events: none;
  -webkit-transition-property: opacity;
  -o-transition-property: opacity;
  transition-property: opacity;
}
.swiper-container-fade .swiper-slide .swiper-slide {
  pointer-events: none;
}
.swiper-container-fade .swiper-slide-active,
.swiper-container-fade .swiper-slide-active .swiper-slide-active {
  pointer-events: auto;
}
.swiper-container-cube {
  overflow: visible;
}
.swiper-container-cube .swiper-slide {
  pointer-events: none;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  z-index: 1;
  visibility: hidden;
  -webkit-transform-origin: 0 0;
  -ms-transform-origin: 0 0;
  transform-origin: 0 0;
  width: 100%;
  height: 100%;
}
.swiper-container-cube .swiper-slide .swiper-slide {
  pointer-events: none;
}
.swiper-container-cube.swiper-container-rtl .swiper-slide {
  -webkit-transform-origin: 100% 0;
  -ms-transform-origin: 100% 0;
  transform-origin: 100% 0;
}
.swiper-container-cube .swiper-slide-active,
.swiper-container-cube .swiper-slide-active .swiper-slide-active {
  pointer-events: auto;
}
.swiper-container-cube .swiper-slide-active,
.swiper-container-cube .swiper-slide-next,
.swiper-container-cube .swiper-slide-prev,
.swiper-container-cube .swiper-slide-next + .swiper-slide {
  pointer-events: auto;
  visibility: visible;
}
.swiper-container-cube .swiper-slide-shadow-top,
.swiper-container-cube .swiper-slide-shadow-bottom,
.swiper-container-cube .swiper-slide-shadow-left,
.swiper-container-cube .swiper-slide-shadow-right {
  z-index: 0;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.swiper-container-cube .swiper-cube-shadow {
  position: absolute;
  left: 0;
  bottom: 0px;
  width: 100%;
  height: 100%;
  background: #000;
  opacity: 0.6;
  -webkit-filter: blur(50px);
  filter: blur(50px);
  z-index: 0;
}
.swiper-container-flip {
  overflow: visible;
}
.swiper-container-flip .swiper-slide {
  pointer-events: none;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  z-index: 1;
}
.swiper-container-flip .swiper-slide .swiper-slide {
  pointer-events: none;
}
.swiper-container-flip .swiper-slide-active,
.swiper-container-flip .swiper-slide-active .swiper-slide-active {
  pointer-events: auto;
}
.swiper-container-flip .swiper-slide-shadow-top,
.swiper-container-flip .swiper-slide-shadow-bottom,
.swiper-container-flip .swiper-slide-shadow-left,
.swiper-container-flip .swiper-slide-shadow-right {
  z-index: 0;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.swiper-container-coverflow .swiper-wrapper {
  /* Windows 8 IE 10 fix */
  -ms-perspective: 1200px;
}

/*------------------ vtex -----------------------*/
@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-on-localhost/src/monobloco/components/new/StoreComponents/components/ProductImages/components/Carousel/index.js from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import debounce from 'debounce'
import classNames from 'classnames'
import { path, equals } from 'ramda'
import ReactResizeDetector from 'react-resize-detector'

import IconCaret from '../../../../../StoreIcons/IconCaret'
import withCssHandles from '../../../../../CssHandles/withCssHandles'

import Video, { getThumbUrl } from '../Video'
import ProductImage from '../ProductImage'

import styles from '../../styles.css'
import './global.css'

/**
 * ReactIdSwiper cannot be SSRendered, so this is a fake swiper that copies some of its classes and HTML layout and render only the first image of the children array.
 */
const FakeSwiper = ({ children, containerClass, direction = THUMBS_ORIENTATION.HORIZONTAL }) => {
  const swiperContainerDirection = direction === THUMBS_ORIENTATION.HORIZONTAL ? 'swiper-container-horizontal' : direction === THUMBS_ORIENTATION.VERTICAL ? 'swiper-container-vertical' : ''
  const childrenArray = React.Children.toArray(children)
  if (childrenArray.length === 0) {
    return null
  }
  const child = childrenArray[0]
  const childClass = path(['props', 'className'], child)
  const newChildClass = childClass ? `${childClass} swiper-slide-active` : childClass
  return (
    <div className={`${containerClass} swiper-container-initialized ${swiperContainerDirection}`}>
      <div className="swiper-wrapper">
        {React.cloneElement(child, {
          className: newChildClass,
        })}
      </div>
    </div>
  )
}

/** Swiper and its modules are imported using require to avoid breaking SSR */
const Swiper = window.navigator
  ? require('react-id-swiper/lib/ReactIdSwiper').default
  : FakeSwiper

const SwiperModules = window.navigator ? require('swiper/dist/js/swiper') : {}

import ThumbnailSwiper from './ThumbnailSwiper'
import {
  THUMBS_ORIENTATION,
  THUMBS_POSITION_HORIZONTAL,
} from '../../utils/enums'

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

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

class Carousel extends Component {
  thumbSwiper = null
  gallerySwiper = null
  state = initialState

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

    this.isVideo = []
    this.thumbLoadCount = 0

    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)
        this.thumbLoadFinish()
      } else {
        this.getThumb(slide.thumbUrl)
      }
    })
  }

  updateSwiperSize = debounce(() => {
    if (this.thumbSwiper) {
      this.thumbSwiper.update()
    }

    if (this.gallerySwiper) {
      this.gallerySwiper.update()
    }
  }, 500)

  thumbLoadFinish = () => {
    this.thumbLoadCount++
    if (!this.props.slides || this.thumbLoadCount === this.props.slides.length) {
      this.setState({ thumbsLoaded: true })
    }
  }

  getThumb = thumbUrl => {
    if (!window.navigator) return // Image object doesn't exist when it's being rendered in the server side
    const image = new Image()
    image.onload = () => {
      this.thumbLoadFinish()
    }
    image.onerror = () => {
      this.thumbLoadFinish()
    }
    image.src = thumbUrl
  }

  handleResize = () => {
    this.updateSwiperSize()
  }

  componentDidMount() {
    window.addEventListener('resize', this.handleResize)
    this.setInitialVariablesState()
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize)

    this.updateSwiperSize.clear()
  }

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

    if (!equals(prevProps.slides, this.props.slides)) {
      this.setInitialVariablesState()
      this.setState(initialState)
      if (this.props.slides && this.props.slides.length > 1) {
        this.gallerySwiper && this.gallerySwiper.slideTo(0)
        this.thumbSwiper && this.thumbSwiper.slideTo(0)
      }
      return
    }

    const paginationElement = path(['pagination', 'el'], this.gallerySwiper)
    if (paginationElement) paginationElement.hidden = isVideo[activeIndex]

    const gallerySwiperZoom = path(['zoom'], this.gallerySwiper)

    if (gallerySwiperZoom) {
      loaded[activeIndex]
        ? gallerySwiperZoom.enable()
        : gallerySwiperZoom.disable()
    }
  }

  onSlideChange = () => {
    const activeIndex = path(['activeIndex'], this.gallerySwiper)
    this.setState({ activeIndex, sliderChanged: true })
  }

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

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

    this.setState({ thumbUrl, alt })
  }

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

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

    switch (slide.type) {
      case 'image':
        return (
          <ProductImage
            src={slide.url}
            alt={slide.alt}
            aspectRatio={aspectRatio}
            maxHeight={maxHeight}
            zoomFactor={zoomFactor}
            zoomMode={isZoomDisabled ? 'disabled' : zoomMode}
          />
        )
      case 'video':
        return (
          <Video
            url={slide.src}
            setThumb={this.setVideoThumb(i)}
            playing={i === this.state.activeIndex}
            id={i}
          />
        )
      default:
        return null
    }
  }

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

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

    return {
      modules: [SwiperModules.Pagination, SwiperModules.Navigation],
      containerClass: `swiper-container ${cssHandles.productImagesGallerySwiperContainer}`,
      ...(slides.length > 1 && showPaginationDots && {
        pagination: {
          el: '.swiper-pagination',
          clickable: true,
          bulletActiveClass: 'c-action-primary swiper-pagination-bullet-active',
        },
      }),
      ...(slides.length > 1 && {
        navigation: {
          prevEl: '.swiper-caret-prev',
          nextEl: '.swiper-caret-next',
          disabledClass: `c-disabled ${styles.carouselCursorDefault}`,
        },
      }),
      thumbs: {
        swiper: this.thumbSwiper,
      },
      threshold: 10,
      resistanceRatio: slides.length > 1 ? 0.85 : 0,
      ...(showNavigationArrows && {
        renderNextButton: () => (
          <span className={`swiper-caret-next pl7 pr2 right-0 ${caretClassName} ${cssHandles.swiperCaret} ${cssHandles.swiperCaretNext}`}>
            <IconCaret
              orientation="right"
              size={iconSize}
              className={styles.carouselIconCaretRight}
            />
          </span>
        ),
        renderPrevButton: () => (
          <span className={`swiper-caret-prev pr7 pl2 left-0 ${caretClassName} ${cssHandles.swiperCaret} ${cssHandles.swiperCaretPrev}`}>
            <IconCaret
              orientation="left"
              size={iconSize}
              className={styles.carouselIconCaretLeft}
            />
          </span>
        ),
      }),
      on: {
        slideChange: this.onSlideChange,
      },
      getSwiper: swiper => {
        if (this.gallerySwiper !== swiper) {
          this.gallerySwiper = swiper
        }
      }
    }
  }

  get thumbnailsParams() {
    const { displayThumbnailsArrows, thumbnailsOrientation, cssHandles } = this.props

    const isThumbsVertical =
      thumbnailsOrientation === THUMBS_ORIENTATION.VERTICAL
    const caretSize = 24
    const caretClassName = `${cssHandles.productImagesThumbCaret} absolute z-2 pointer c-action-primary flex pv2`
    const caretStyle = { transition: 'opacity 200ms' }

    return {
      modules: [SwiperModules.Navigation],
      ...(displayThumbnailsArrows && {
        navigation: {
          prevEl: '.swiper-thumbnails-caret-prev',
          nextEl: '.swiper-thumbnails-caret-next',
          disabledClass: `c-disabled o-0 pointer-events-none ${styles.carouselCursorDefault}`,
          hiddenClass: 'dn',
        },
        renderNextButton: () => {
          const classes = classNames(
            'swiper-thumbnails-caret-next',
            caretClassName,
            {
              [`bottom-0 pt7 left-0 justify-center w-100 ${styles.gradientBaseBottom}`]: isThumbsVertical,
              [`right-0 top-0 items-center h-100 pl6 ${styles.gradientBaseRight}`]: !isThumbsVertical,
            }
          )
          return (
            <span className={classes} style={caretStyle}>
              <IconCaret
                orientation={isThumbsVertical ? 'down' : 'right'}
                size={caretSize}
              />
            </span>
          )
        },
        renderPrevButton: () => {
          const classes = classNames(
            'swiper-thumbnails-caret-prev top-0 left-0',
            caretClassName,
            {
              [`pb7 justify-center w-100 ${styles.gradientBaseTop}`]: isThumbsVertical,
              [`items-center h-100 pr6 ${styles.gradientBaseLeft}`]: !isThumbsVertical,
            }
          )
          return (
            <span className={classes} style={caretStyle}>
              <IconCaret
                orientation={isThumbsVertical ? 'up' : 'left'}
                size={caretSize}
              />
            </span>
          )
        },
      }),
      observer: true,
      containerClass: `swiper-container h-100 ${cssHandles.productImagesThumbsSwiperContainer}`,
      watchSlidesVisibility: true,
      watchSlidesProgress: true,
      freeMode: false,
      // It seems crazy but this is just
      // an workaround to make it work the thumbnails with the carousel
      slideActiveClass: 'undefined',
      slideNextClass: 'undefined',
      slidePrevClass: 'undefined',
      direction: thumbnailsOrientation,
      slidesPerView: 'auto',
      touchRatio: 1,
      mousewheel: false,
      preloadImages: true,
      shouldSwiperUpdate: true,
      zoom: false,
      threshold: 8,
      /* Slides are grouped when thumbnails arrows are enabled
       * so that clicking on next/prev will scroll more than
       * one thumbnail */
      slidesPerGroup: displayThumbnailsArrows ? 4 : 1,
      getSwiper: swiper => {
        if (this.thumbSwiper !== swiper) {
          this.thumbSwiper = swiper
        }
      }
    }
  }

  render() {
    const { thumbsLoaded, activeIndex } = this.state

    const {
      position,
      cssHandles,
      slides = [],
      thumbnailMaxHeight,
      thumbnailAspectRatio,
      thumbnailsOrientation,
      zoomProps: { zoomType },
    } = this.props

    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[zoomType],
      {
        'ml-20-ns w-80-ns pl5':
          isThumbsVertical &&
          position === THUMBS_POSITION_HORIZONTAL.LEFT &&
          hasThumbs,
        'mr-20-ns w-80-ns pr5':
          isThumbsVertical &&
          position === THUMBS_POSITION_HORIZONTAL.RIGHT &&
          hasThumbs,
      }
    )

    const thumbnailSwiper = thumbsLoaded && hasThumbs && (
      <ThumbnailSwiper
        isThumbsVertical={isThumbsVertical}
        slides={slides}
        activeIndex={activeIndex}
        swiperParams={this.thumbnailsParams}
        thumbUrls={this.state.thumbUrl}
        position={position}
        onThumbClick={index => this.gallerySwiper && this.gallerySwiper.slideTo(index)}
        thumbnailAspectRatio={thumbnailAspectRatio}
        thumbnailMaxHeight={thumbnailMaxHeight}
      />
    )

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

    const SliderComponent = slides.length === 1 ? FakeSwiper : Swiper

    return (
      <div className={containerClasses} aria-hidden="true">
        {isThumbsVertical && thumbnailSwiper}
        <div className={imageClasses}>
          <ReactResizeDetector handleHeight onResize={this.updateSwiperSize}>
            <SliderComponent {...this.galleryParams} shouldSwiperUpdate>
              {slides.map((slide, i) => (
                <div key={i} className={`${cssHandles.productImagesGallerySlide} swiper-slide center-all`}>
                  {this.renderSlide(slide, i)}
                </div>
              ))}
            </SliderComponent>
          </ReactResizeDetector>
          {!isThumbsVertical && 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,
    })
  ),
  displayThumbnailsArrows: PropTypes.bool,
}

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-on-localhost/src/monobloco/components/new/StoreComponents/components/CollectionBadges/components/CollectionBadgeItem.js from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

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

import styles from '../styles.css'

/**
 * Collection Badge Item.
 * Displays a collection badge item.
 */
export class CollectionBadgeItem extends PureComponent {
  render() {
    return (
      <div className={`${styles.item} mh1 pa2 bg-blue white tc`}>
        {this.props.children}
      </div>
    )
  }
}

CollectionBadgeItem.propTypes = {
  /** Children component that should be render inside the collection badge item */
  children: PropTypes.node.isRequired,
}

CollectionBadgeItem.defaultProps = {
  children: {},
}

```


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

[User] Show me the implementation of store-on-localhost/src/monobloco/components/new/StoreComponents/components/AvailabilitySubscriber/mutations/addToAvailabilitySubscriberMutation.gql from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation availabilitySubscribe($acronym: String!, $document: DocumentInput) {
  createDocument(acronym: $acronym, document: $document) {
    cacheId
    id
    href
    documentId
  }
}

```


[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-on-localhost/src/monobloco/components/new/StoreComponents/components/SKUSelector/types/index.ts from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```typescript
export enum InitialSelectionType {
  complete = 'complete',
  image = 'image',
  empty = 'empty',
}

export enum DisplayMode {
  select = 'select',
  default = 'default',
  slider = '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
  onSelectItem: () => void
  image: Image | undefined
  available: boolean
  impossible: boolean
}

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

export type Variations = Record<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-on-localhost/src/monobloco/components/new/StoreComponents/components/SKUSelector/utils/proptypes.ts from the store-on-localhost 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,
    })
  ),
})

```