mirror of
https://github.com/vercel/commerce.git
synced 2025-06-08 01:06:59 +00:00
Perf changes
This commit is contained in:
parent
1a729f9e72
commit
7ef0e2273e
@ -1,51 +1,50 @@
|
|||||||
import { Swatch } from '@components/product'
|
import { Swatch } from '@components/product'
|
||||||
import type { ProductOption } from '@commerce/types/product'
|
import type { ProductOption } from '@commerce/types/product'
|
||||||
import { SelectedOptions } from '../helpers'
|
import { SelectedOptions } from '../helpers'
|
||||||
|
import React from 'react'
|
||||||
interface ProductOptionsProps {
|
interface ProductOptionsProps {
|
||||||
options: ProductOption[]
|
options: ProductOption[]
|
||||||
selectedOptions: SelectedOptions
|
selectedOptions: SelectedOptions
|
||||||
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
|
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductOptions: React.FC<ProductOptionsProps> = ({
|
const ProductOptions: React.FC<ProductOptionsProps> = React.memo(
|
||||||
options,
|
({ options, selectedOptions, setSelectedOptions }) => {
|
||||||
selectedOptions,
|
return (
|
||||||
setSelectedOptions,
|
<div>
|
||||||
}) => {
|
{options.map((opt) => (
|
||||||
return (
|
<div className="pb-4" key={opt.displayName}>
|
||||||
<div>
|
<h2 className="uppercase font-medium text-sm tracking-wide">
|
||||||
{options.map((opt) => (
|
{opt.displayName}
|
||||||
<div className="pb-4" key={opt.displayName}>
|
</h2>
|
||||||
<h2 className="uppercase font-medium text-sm tracking-wide">
|
<div className="flex flex-row py-4">
|
||||||
{opt.displayName}
|
{opt.values.map((v, i: number) => {
|
||||||
</h2>
|
const active = selectedOptions[opt.displayName.toLowerCase()]
|
||||||
<div className="flex flex-row py-4">
|
return (
|
||||||
{opt.values.map((v, i: number) => {
|
<Swatch
|
||||||
const active = selectedOptions[opt.displayName.toLowerCase()]
|
key={`${opt.id}-${i}`}
|
||||||
return (
|
active={v.label.toLowerCase() === active}
|
||||||
<Swatch
|
variant={opt.displayName}
|
||||||
key={`${opt.id}-${i}`}
|
color={v.hexColors ? v.hexColors[0] : ''}
|
||||||
active={v.label.toLowerCase() === active}
|
label={v.label}
|
||||||
variant={opt.displayName}
|
onClick={() => {
|
||||||
color={v.hexColors ? v.hexColors[0] : ''}
|
setSelectedOptions((selectedOptions) => {
|
||||||
label={v.label}
|
return {
|
||||||
onClick={() => {
|
...selectedOptions,
|
||||||
setSelectedOptions((selectedOptions) => {
|
[opt.displayName.toLowerCase()]:
|
||||||
return {
|
v.label.toLowerCase(),
|
||||||
...selectedOptions,
|
}
|
||||||
[opt.displayName.toLowerCase()]: v.label.toLowerCase(),
|
})
|
||||||
}
|
}}
|
||||||
})
|
/>
|
||||||
}}
|
)
|
||||||
/>
|
})}
|
||||||
)
|
</div>
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
export default ProductOptions
|
export default ProductOptions
|
||||||
|
84
components/product/ProductSidebar/ProductSidebar.module.css
Normal file
84
components/product/ProductSidebar/ProductSidebar.module.css
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
.root {
|
||||||
|
@apply relative grid items-start gap-1 grid-cols-1 overflow-x-hidden;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
@apply relative px-0 pb-0 box-border flex flex-col col-span-1;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
@apply transition-colors ease-in-out duration-500
|
||||||
|
absolute top-0 left-0 z-20 pr-16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .name {
|
||||||
|
@apply pt-0 max-w-full w-full leading-extra-loose;
|
||||||
|
font-size: 2rem;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .name span {
|
||||||
|
@apply py-4 px-6 bg-primary text-primary font-bold;
|
||||||
|
font-size: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .price {
|
||||||
|
@apply pt-2 px-6 pb-4 text-sm bg-primary text-accent-9
|
||||||
|
font-semibold inline-block tracking-wide;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
@apply flex flex-col col-span-1 mx-auto max-w-8xl px-6 py-6 w-full h-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer {
|
||||||
|
@apply flex items-center justify-center overflow-x-hidden bg-violet;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageContainer {
|
||||||
|
@apply text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageContainer > div,
|
||||||
|
.imageContainer > div > div {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer .img {
|
||||||
|
@apply w-full h-auto max-h-full object-cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wishlistButton {
|
||||||
|
@apply absolute z-30 top-0 right-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relatedProductsGrid {
|
||||||
|
@apply grid grid-cols-2 py-2 gap-2 md:grid-cols-4 md:gap-7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen lg {
|
||||||
|
.root {
|
||||||
|
@apply grid-cols-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
@apply mx-0 col-span-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
@apply col-span-4 py-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageContainer {
|
||||||
|
max-height: 600px;
|
||||||
|
}
|
||||||
|
}
|
86
components/product/ProductSidebar/ProductSidebar.tsx
Normal file
86
components/product/ProductSidebar/ProductSidebar.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import s from './ProductSidebar.module.css'
|
||||||
|
import { useAddItem } from '@framework/cart'
|
||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import { ProductOptions } from '@components/product'
|
||||||
|
import type { Product } from '@commerce/types/product'
|
||||||
|
import { Button, Text, Rating, Collapse, useUI } from '@components/ui'
|
||||||
|
import {
|
||||||
|
getProductVariant,
|
||||||
|
selectDefaultOptionFromProduct,
|
||||||
|
SelectedOptions,
|
||||||
|
} from '../helpers'
|
||||||
|
|
||||||
|
interface ProductSidebarProps {
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductSidebar: FC<ProductSidebarProps> = ({ product }) => {
|
||||||
|
const addItem = useAddItem()
|
||||||
|
const { openSidebar } = useUI()
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
selectDefaultOptionFromProduct(product, setSelectedOptions)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const variant = getProductVariant(product, selectedOptions)
|
||||||
|
const addToCart = async () => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
await addItem({
|
||||||
|
productId: String(product.id),
|
||||||
|
variantId: String(variant ? variant.id : product.variants[0].id),
|
||||||
|
})
|
||||||
|
openSidebar()
|
||||||
|
setLoading(false)
|
||||||
|
} catch (err) {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProductOptions
|
||||||
|
options={product.options}
|
||||||
|
selectedOptions={selectedOptions}
|
||||||
|
setSelectedOptions={setSelectedOptions}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className="pb-4 break-words w-full max-w-xl"
|
||||||
|
html={product.descriptionHtml || product.description}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
<Rating value={2} />
|
||||||
|
<div className="text-accent-6 pr-1 font-medium text-sm">36 reviews</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
aria-label="Add to Cart"
|
||||||
|
type="button"
|
||||||
|
className={s.button}
|
||||||
|
onClick={addToCart}
|
||||||
|
loading={loading}
|
||||||
|
disabled={variant?.availableForSale === false}
|
||||||
|
>
|
||||||
|
{variant?.availableForSale === false
|
||||||
|
? 'Not Available'
|
||||||
|
: 'Add To Cart'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<Collapse title="Care">
|
||||||
|
This is a limited edition production run. Printing starts when the
|
||||||
|
drop ends.
|
||||||
|
</Collapse>
|
||||||
|
<Collapse title="Details">
|
||||||
|
This is a limited edition production run. Printing starts when the
|
||||||
|
drop ends. Reminder: Bad Boys For Life. Shipping may take 10+ days due
|
||||||
|
to COVID-19.
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductSidebar
|
1
components/product/ProductSidebar/index.ts
Normal file
1
components/product/ProductSidebar/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './ProductSidebar'
|
@ -2,65 +2,27 @@ import cn from 'classnames'
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { NextSeo } from 'next-seo'
|
import { NextSeo } from 'next-seo'
|
||||||
import s from './ProductView.module.css'
|
import s from './ProductView.module.css'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC } from 'react'
|
||||||
import type { Product } from '@commerce/types/product'
|
import type { Product } from '@commerce/types/product'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import {
|
|
||||||
getProductVariant,
|
|
||||||
selectDefaultOptionFromProduct,
|
|
||||||
SelectedOptions,
|
|
||||||
} from '../helpers'
|
|
||||||
import { useAddItem } from '@framework/cart'
|
|
||||||
import { WishlistButton } from '@components/wishlist'
|
import { WishlistButton } from '@components/wishlist'
|
||||||
import { ProductSlider, ProductCard, ProductOptions } from '@components/product'
|
import { ProductSlider, ProductCard } from '@components/product'
|
||||||
import {
|
import { Container, Text } from '@components/ui'
|
||||||
Button,
|
import ProductSidebar from '../ProductSidebar'
|
||||||
Container,
|
|
||||||
Text,
|
|
||||||
useUI,
|
|
||||||
Rating,
|
|
||||||
Collapse,
|
|
||||||
} from '@components/ui'
|
|
||||||
|
|
||||||
interface ProductViewProps {
|
interface ProductViewProps {
|
||||||
product: Product
|
product: Product
|
||||||
className?: string
|
|
||||||
relatedProducts: Product[]
|
relatedProducts: Product[]
|
||||||
children?: React.ReactNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
|
const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
|
||||||
const { openSidebar } = useUI()
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})
|
|
||||||
const addItem = useAddItem()
|
|
||||||
const { price } = usePrice({
|
const { price } = usePrice({
|
||||||
amount: product.price.value,
|
amount: product.price.value,
|
||||||
baseAmount: product.price.retailPrice,
|
baseAmount: product.price.retailPrice,
|
||||||
currencyCode: product.price.currencyCode!,
|
currencyCode: product.price.currencyCode!,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
selectDefaultOptionFromProduct(product, setSelectedOptions)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const variant = getProductVariant(product, selectedOptions)
|
|
||||||
const addToCart = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
await addItem({
|
|
||||||
productId: String(product.id),
|
|
||||||
variantId: String(variant ? variant.id : product.variants[0].id),
|
|
||||||
})
|
|
||||||
openSidebar()
|
|
||||||
setLoading(false)
|
|
||||||
} catch (err) {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="max-w-none w-full" clean>
|
<>
|
||||||
<NextSeo
|
<NextSeo
|
||||||
title={product.name}
|
title={product.name}
|
||||||
description={product.description}
|
description={product.description}
|
||||||
@ -78,110 +40,73 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
|
|||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className={cn(s.root, 'fit')}>
|
<Container className="max-w-none w-full" clean>
|
||||||
<div className={cn(s.main, 'fit')}>
|
<div className={cn(s.root, 'fit')}>
|
||||||
<div className={s.header}>
|
<div className={cn(s.main, 'fit')}>
|
||||||
<h3 className={s.name}>
|
<div className={s.header}>
|
||||||
<span>{product.name}</span>
|
<h3 className={s.name}>
|
||||||
</h3>
|
<span>{product.name}</span>
|
||||||
<div className={s.price}>
|
</h3>
|
||||||
{`${price} ${product.price?.currencyCode}`}
|
<div className={s.price}>
|
||||||
|
{`${price} ${product.price?.currencyCode}`}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={s.sliderContainer}>
|
<div className={s.sliderContainer}>
|
||||||
<ProductSlider key={product.id}>
|
<ProductSlider key={product.id}>
|
||||||
{product.images.map((image, i) => (
|
{product.images.map((image, i) => (
|
||||||
<div key={image.url} className={s.imageContainer}>
|
<div key={image.url} className={s.imageContainer}>
|
||||||
<Image
|
<Image
|
||||||
className={s.img}
|
className={s.img}
|
||||||
src={image.url!}
|
src={image.url!}
|
||||||
alt={image.alt || 'Product Image'}
|
alt={image.alt || 'Product Image'}
|
||||||
width={600}
|
width={600}
|
||||||
height={600}
|
height={600}
|
||||||
priority={i === 0}
|
priority={i === 0}
|
||||||
quality="85"
|
quality="85"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</ProductSlider>
|
</ProductSlider>
|
||||||
</div>
|
|
||||||
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
|
||||||
<WishlistButton
|
|
||||||
className={s.wishlistButton}
|
|
||||||
productId={product.id}
|
|
||||||
variant={product.variants[0]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={s.sidebar}>
|
|
||||||
<ProductOptions
|
|
||||||
options={product.options}
|
|
||||||
selectedOptions={selectedOptions}
|
|
||||||
setSelectedOptions={setSelectedOptions}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
className="pb-4 break-words w-full max-w-xl"
|
|
||||||
html={product.descriptionHtml || product.description}
|
|
||||||
/>
|
|
||||||
<div className="flex flex-row justify-between items-center">
|
|
||||||
<Rating value={2} />
|
|
||||||
<div className="text-accent-6 pr-1 font-medium text-sm">
|
|
||||||
36 reviews
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
||||||
<div>
|
<WishlistButton
|
||||||
<Button
|
className={s.wishlistButton}
|
||||||
aria-label="Add to Cart"
|
productId={product.id}
|
||||||
type="button"
|
variant={product.variants[0]}
|
||||||
className={s.button}
|
|
||||||
onClick={addToCart}
|
|
||||||
loading={loading}
|
|
||||||
disabled={variant?.availableForSale === false}
|
|
||||||
>
|
|
||||||
{variant?.availableForSale === false
|
|
||||||
? 'Not Available'
|
|
||||||
: 'Add To Cart'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6">
|
|
||||||
<Collapse title="Care">
|
|
||||||
This is a limited edition production run. Printing starts when the
|
|
||||||
drop ends.
|
|
||||||
</Collapse>
|
|
||||||
<Collapse title="Details">
|
|
||||||
This is a limited edition production run. Printing starts when the
|
|
||||||
drop ends. Reminder: Bad Boys For Life. Shipping may take 10+ days
|
|
||||||
due to COVID-19.
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr className="mt-7 border-accent-2" />
|
|
||||||
<section className="py-12 px-6 mb-10">
|
|
||||||
<Text variant="sectionHeading">Related Products</Text>
|
|
||||||
<div className={s.relatedProductsGrid}>
|
|
||||||
{relatedProducts.map((p) => (
|
|
||||||
<div
|
|
||||||
key={p.path}
|
|
||||||
className="animated fadeIn bg-accent-0 border border-accent-2"
|
|
||||||
>
|
|
||||||
<ProductCard
|
|
||||||
noNameTag
|
|
||||||
product={p}
|
|
||||||
key={p.path}
|
|
||||||
variant="simple"
|
|
||||||
className="animated fadeIn"
|
|
||||||
imgProps={{
|
|
||||||
width: 300,
|
|
||||||
height: 300,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
))}
|
</div>
|
||||||
|
<div className={s.sidebar}>
|
||||||
|
<ProductSidebar product={product} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<hr className="mt-7 border-accent-2" />
|
||||||
</Container>
|
<section className="py-12 px-6 mb-10">
|
||||||
|
<Text variant="sectionHeading">Related Products</Text>
|
||||||
|
<div className={s.relatedProductsGrid}>
|
||||||
|
{relatedProducts.map((p) => (
|
||||||
|
<div
|
||||||
|
key={p.path}
|
||||||
|
className="animated fadeIn bg-accent-0 border border-accent-2"
|
||||||
|
>
|
||||||
|
<ProductCard
|
||||||
|
noNameTag
|
||||||
|
product={p}
|
||||||
|
key={p.path}
|
||||||
|
variant="simple"
|
||||||
|
className="animated fadeIn"
|
||||||
|
imgProps={{
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { FC } from 'react'
|
import React from 'react'
|
||||||
import s from './Swatch.module.css'
|
import s from './Swatch.module.css'
|
||||||
import { Check } from '@components/icons'
|
import { Check } from '@components/icons'
|
||||||
import Button, { ButtonProps } from '@components/ui/Button'
|
import Button, { ButtonProps } from '@components/ui/Button'
|
||||||
@ -13,48 +13,50 @@ interface SwatchProps {
|
|||||||
label?: string | null
|
label?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const Swatch: FC<Omit<ButtonProps, 'variant'> & SwatchProps> = ({
|
const Swatch: React.FC<Omit<ButtonProps, 'variant'> & SwatchProps> = React.memo(
|
||||||
className,
|
({
|
||||||
color = '',
|
active,
|
||||||
label = null,
|
className,
|
||||||
variant = 'size',
|
color = '',
|
||||||
active,
|
label = null,
|
||||||
...props
|
variant = 'size',
|
||||||
}) => {
|
...props
|
||||||
variant = variant?.toLowerCase()
|
}) => {
|
||||||
|
variant = variant?.toLowerCase()
|
||||||
|
|
||||||
if (label) {
|
if (label) {
|
||||||
label = label?.toLowerCase()
|
label = label?.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
const swatchClassName = cn(
|
||||||
|
s.swatch,
|
||||||
|
{
|
||||||
|
[s.color]: color,
|
||||||
|
[s.active]: active,
|
||||||
|
[s.size]: variant === 'size',
|
||||||
|
[s.dark]: color ? isDark(color) : false,
|
||||||
|
[s.textLabel]: !color && label && label.length > 3,
|
||||||
|
},
|
||||||
|
className
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
aria-label="Variant Swatch"
|
||||||
|
className={swatchClassName}
|
||||||
|
{...(label && color && { title: label })}
|
||||||
|
style={color ? { backgroundColor: color } : {}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{color && active && (
|
||||||
|
<span>
|
||||||
|
<Check />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!color ? label : null}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
const swatchClassName = cn(
|
|
||||||
s.swatch,
|
|
||||||
{
|
|
||||||
[s.active]: active,
|
|
||||||
[s.size]: variant === 'size',
|
|
||||||
[s.color]: color,
|
|
||||||
[s.dark]: color ? isDark(color) : false,
|
|
||||||
[s.textLabel]: !color && label && label.length > 3,
|
|
||||||
},
|
|
||||||
className
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className={swatchClassName}
|
|
||||||
style={color ? { backgroundColor: color } : {}}
|
|
||||||
aria-label="Variant Swatch"
|
|
||||||
{...(label && color && { title: label })}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{color && active && (
|
|
||||||
<span>
|
|
||||||
<Check />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{!color ? label : null}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Swatch
|
export default Swatch
|
||||||
|
@ -10,7 +10,7 @@ export interface CollapseProps {
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const Collapse: FC<CollapseProps> = ({ title, children }) => {
|
const Collapse: FC<CollapseProps> = React.memo(({ title, children }) => {
|
||||||
const [isActive, setActive] = useState(false)
|
const [isActive, setActive] = useState(false)
|
||||||
const [ref, { height: viewHeight }] = useMeasure()
|
const [ref, { height: viewHeight }] = useMeasure()
|
||||||
|
|
||||||
@ -41,6 +41,6 @@ const Collapse: FC<CollapseProps> = ({ title, children }) => {
|
|||||||
</a.div>
|
</a.div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default Collapse
|
export default Collapse
|
||||||
|
@ -7,7 +7,7 @@ export interface RatingProps {
|
|||||||
value: number
|
value: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const Quantity: FC<RatingProps> = ({ value = 5 }) => {
|
const Quantity: React.FC<RatingProps> = React.memo(({ value = 5 }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row py-6 text-accent-9">
|
<div className="flex flex-row py-6 text-accent-9">
|
||||||
{rangeMap(5, (i) => (
|
{rangeMap(5, (i) => (
|
||||||
@ -22,6 +22,6 @@ const Quantity: FC<RatingProps> = ({ value = 5 }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default Quantity
|
export default Quantity
|
||||||
|
@ -12,7 +12,7 @@ export async function getStaticProps({
|
|||||||
}: GetStaticPropsContext) {
|
}: GetStaticPropsContext) {
|
||||||
const config = { locale, locales }
|
const config = { locale, locales }
|
||||||
const { products } = await commerce.getAllProducts({
|
const { products } = await commerce.getAllProducts({
|
||||||
variables: { first: 12 },
|
variables: { first: 6 },
|
||||||
config,
|
config,
|
||||||
preview,
|
preview,
|
||||||
})
|
})
|
||||||
@ -69,7 +69,7 @@ export default function Home({
|
|||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Marquee>
|
<Marquee>
|
||||||
{products.slice(0, 3).map((product, i) => (
|
{products.slice(3).map((product, i) => (
|
||||||
<ProductCard key={product.id} product={product} variant="slim" />
|
<ProductCard key={product.id} product={product} variant="slim" />
|
||||||
))}
|
))}
|
||||||
</Marquee>
|
</Marquee>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user