Skip to content

Commit 75c3141

Browse files
committed
add ProductSlider + ProductCard
1 parent 9fc92dc commit 75c3141

File tree

13 files changed

+376
-1033
lines changed

13 files changed

+376
-1033
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"vite-tsconfig-paths": "4.2.0"
4343
},
4444
"dependencies": {
45+
"qwik-image": "^0.0.7",
4546
"qwik-storefront-ui": "^0.0.3",
4647
"storefront-qwik-boilerplate": "link:"
4748
}

pnpm-lock.yaml

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/images/product.webp

16.8 KB
Binary file not shown.

src/components/Footer/Footer.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const Footer = component$<FooterProps>(({ class: _class = '' }) => {
4747
{contactOptions.map(({ icon, link, details, key }) => (
4848
<div
4949
key={key}
50-
class='mx-auto my-4 text-center flex flex-col items-center'
50+
class='mx-auto my-4 text-center flex flex-col items-center w-8'
5151
>
5252
{icon}
5353
<Link
+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { component$ } from '@builder.io/qwik';
2+
import { Link } from '@builder.io/qwik-city';
3+
import { Image } from 'qwik-image';
4+
import {
5+
SfButton,
6+
SfCounter,
7+
SfIconShoppingCart,
8+
SfLink,
9+
SfRating,
10+
} from 'qwik-storefront-ui';
11+
import { useTranslation } from '~/shared/utils';
12+
13+
export type ProductCardProps = {
14+
name: string;
15+
description?: string;
16+
imageUrl?: string;
17+
imageAlt?: string;
18+
rating?: number;
19+
ratingCount?: number;
20+
price?: number;
21+
slug?: string;
22+
class?: string;
23+
};
24+
25+
export const ProductCard = component$<ProductCardProps>(
26+
({
27+
name,
28+
description,
29+
imageUrl,
30+
imageAlt,
31+
price,
32+
rating,
33+
ratingCount,
34+
slug,
35+
class: _class,
36+
...attributes
37+
}) => {
38+
const { t } = useTranslation('');
39+
40+
return (
41+
<div
42+
class={[
43+
'border border-neutral-200 rounded-md hover:shadow-lg flex-auto flex-shrink-0',
44+
_class,
45+
]}
46+
data-testid='product-card'
47+
{...attributes}
48+
>
49+
<div class='relative'>
50+
<SfLink
51+
href={`/product/${slug}`}
52+
as={Link}
53+
class='relative block w-full'
54+
>
55+
<Image
56+
loading='lazy'
57+
layout='constrained'
58+
objectFit='fill'
59+
width={240}
60+
height={240}
61+
data-testid='image-slot'
62+
class='object-cover rounded-md aspect-square w-full h-full'
63+
src={imageUrl ?? ''}
64+
alt={imageAlt || 'primary image'}
65+
/>
66+
</SfLink>
67+
</div>
68+
<div class='p-2 border-t border-neutral-200 typography-text-sm'>
69+
<SfLink
70+
href={`/product/${slug}`}
71+
as={Link}
72+
variant='secondary'
73+
class='no-underline'
74+
>
75+
{name}
76+
</SfLink>
77+
<div class='flex items-center pt-1'>
78+
<SfRating size='xs' value={rating} max={5} />
79+
80+
<SfLink
81+
href='#'
82+
variant='secondary'
83+
as={Link}
84+
class='ml-1 no-underline'
85+
>
86+
<SfCounter size='xs'>{ratingCount}</SfCounter>
87+
</SfLink>
88+
</div>
89+
<p class='block py-2 font-normal typography-text-xs text-neutral-700 text-justify'>
90+
{description}
91+
</p>
92+
<span
93+
class='block pb-2 font-bold typography-text-sm'
94+
data-testid='product-card-vertical-price'
95+
>
96+
${price}
97+
</span>
98+
<SfButton
99+
type='button'
100+
size='sm'
101+
class='inline-flex items-center justify-center font-medium text-base focus-visible:outline focus-visible:outline-offset rounded-md disabled:text-disabled-500 disabled:bg-disabled-300 disabled:shadow-none disabled:ring-0 disabled:cursor-not-allowed leading-5 text-sm py-1.5 px-3 gap-1.5 text-white shadow hover:shadow-md active:shadow bg-primary-700 hover:bg-primary-800 active:bg-primary-900 disabled:bg-disabled-300'
102+
>
103+
{t('addToCartShort')}
104+
<div q:slot='prefix'>
105+
<SfIconShoppingCart size='sm' class='w-5 h-5' />
106+
</div>
107+
</SfButton>
108+
</div>
109+
</div>
110+
);
111+
}
112+
);

src/components/ProductCard/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ProductCard';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { component$ } from '@builder.io/qwik';
2+
import { ProductCard } from '../ProductCard';
3+
4+
type ProductSliderProps = {
5+
products: {
6+
id: string;
7+
slug: string;
8+
name: string;
9+
sku: string;
10+
rating: { average: number; count: number };
11+
price: {
12+
isDiscounted: boolean;
13+
regularPrice: {
14+
currency: string;
15+
amount: number;
16+
precisionAmount: string;
17+
};
18+
value: { currency: string; amount: number; precisionAmount: string };
19+
};
20+
primaryImage: {
21+
alt: string;
22+
url: string;
23+
};
24+
description?: string;
25+
}[];
26+
class?: string;
27+
};
28+
29+
export const ProductSlider = component$<ProductSliderProps>(({ products }) => {
30+
// <SfScrollable
31+
// buttonsPlacement="floating"
32+
// className="items-center pb-4"
33+
// {...attributes}
34+
// wrapperClassName={className}
35+
// >
36+
37+
return (
38+
<div class='items-center relative flex max-w-screen-3xl mx-auto px-4 md:px-10 mb-20'>
39+
<button
40+
type='button'
41+
class='inline-flex items-center justify-center font-medium text-base focus-visible:outline focus-visible:outline-offset rounded-md disabled:text-disabled-500 disabled:bg-disabled-300 disabled:shadow-none disabled:ring-0 disabled:cursor-not-allowed p-4 gap-3 text-primary-700 hover:bg-primary-100 hover:text-primary-800 active:bg-primary-200 active:text-primary-900 ring-1 ring-primary-700 hover:shadow-md active:shadow shadow hover:ring-primary-800 active:ring-primary-900 disabled:ring-1 disabled:ring-disabled-300 disabled:bg-white/50 hidden md:block !ring-neutral-500 !text-neutral-500 disabled:!ring-disabled-300 disabled:!text-disabled-500 !rounded-full bg-white absolute left-4 z-10'
42+
data-testid='button'
43+
>
44+
<svg
45+
viewBox='0 0 24 24'
46+
data-testid='chevron-left'
47+
xmlns='http://www.w3.org/2000/svg'
48+
class='inline-block fill-current w-6 h-6 undefined'
49+
>
50+
<path d='M14.706 17.297a.998.998 0 0 0 0-1.41l-3.876-3.885 3.877-3.885a.998.998 0 0 0-1.412-1.41l-4.588 4.588a1 1 0 0 0 0 1.414l4.588 4.588a.997.997 0 0 0 1.41 0Z'></path>
51+
</svg>
52+
</button>
53+
<div class='items-center pb-4 motion-safe:scroll-smooth overflow-x-auto flex gap-4'>
54+
{products.map(
55+
({ id, name, description, rating, price, primaryImage, slug }) => (
56+
<ProductCard
57+
key={id}
58+
class='max-w-[192px]'
59+
name={name}
60+
description={description}
61+
ratingCount={rating.count}
62+
rating={rating.average}
63+
price={price.value.amount}
64+
imageUrl={primaryImage.url}
65+
imageAlt={primaryImage.alt}
66+
slug={slug}
67+
/>
68+
)
69+
)}
70+
</div>
71+
<button
72+
type='button'
73+
class='inline-flex items-center justify-center font-medium text-base focus-visible:outline focus-visible:outline-offset rounded-md disabled:text-disabled-500 disabled:bg-disabled-300 disabled:shadow-none disabled:ring-0 disabled:cursor-not-allowed p-4 gap-3 text-primary-700 hover:bg-primary-100 hover:text-primary-800 active:bg-primary-200 active:text-primary-900 ring-1 ring-primary-700 hover:shadow-md active:shadow shadow hover:ring-primary-800 active:ring-primary-900 disabled:ring-1 disabled:ring-disabled-300 disabled:bg-white/50 hidden md:block !ring-neutral-500 !text-neutral-500 disabled:!ring-disabled-300 disabled:!text-disabled-500 !rounded-full bg-white absolute right-4 z-10'
74+
data-testid='button'
75+
>
76+
<svg
77+
viewBox='0 0 24 24'
78+
data-testid='chevron-right'
79+
xmlns='http://www.w3.org/2000/svg'
80+
class='inline-block fill-current w-6 h-6 undefined'
81+
>
82+
<path d='M8.705 17.297a.998.998 0 0 1-.001-1.41l3.876-3.885-3.876-3.885a.998.998 0 0 1 1.412-1.41l4.587 4.588a1 1 0 0 1 0 1.414l-4.587 4.588a.997.997 0 0 1-1.411 0Z'></path>
83+
</svg>
84+
</button>
85+
</div>
86+
);
87+
});

src/components/ProductSlider/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ProductSlider';

src/components/UI/CategoryCard/CategoryCard.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { component$ } from '@builder.io/qwik';
22
import { Link } from '@builder.io/qwik-city';
3+
import { Image } from 'qwik-image';
34

45
type CategoryCardProps = {
56
items: {
@@ -28,11 +29,13 @@ export const CategoryCard = component$(
2829
aria-label={name}
2930
>
3031
<div class='relative h-[240px] w-[240px] rounded-full bg-neutral-100 group-hover:shadow-xl group-active:shadow-none'>
31-
<img
32+
<Image
33+
loading='lazy'
34+
layout='constrained'
35+
objectFit='fill'
36+
width={240}
37+
height={240}
3238
alt={name}
33-
loading='eager'
34-
width='240'
35-
height='240'
3639
src={image}
3740
/>
3841
</div>

src/components/UI/Display/Display.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { component$ } from '@builder.io/qwik';
22
import { Link } from '@builder.io/qwik-city';
3+
import { Image } from 'qwik-image';
34
import { SfButton } from 'qwik-storefront-ui';
45

56
type DisplayProps = {
@@ -68,13 +69,15 @@ export const Display = component$<DisplayProps>(({ items, ...attributes }) => {
6869
{buttonText}
6970
</SfButton>
7071
</div>
71-
<img
72-
alt={title}
72+
<Image
73+
class='w-full md:w-1/2 self-end object-contain flex-1'
7374
loading='eager'
74-
height={300}
75+
layout='constrained'
76+
objectFit='fill'
7577
width={300}
78+
height={300}
79+
alt={title}
7680
src={image}
77-
class='w-full md:w-1/2 self-end object-contain flex-1'
7881
/>
7982
</div>
8083
</div>

src/components/UI/Hero/Hero.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { component$ } from '@builder.io/qwik';
22
import { Link } from '@builder.io/qwik-city';
3+
import { Image } from 'qwik-image';
34
import { SfButton } from 'qwik-storefront-ui';
45

56
export type HeroProps = {
@@ -28,11 +29,13 @@ export const Hero = component$<HeroProps>(
2829
<div class='relative min-h-[600px] mb-10'>
2930
<div class='md:flex md:flex-row-reverse md:justify-center min-h-[600px] max-w-screen-3xl mx-auto'>
3031
<div class='flex flex-col justify-center md:basis-2/4 md:items-stretch md:overflow-hidden'>
31-
<img
32-
alt='hero'
32+
<Image
3333
loading='eager'
34-
width='500'
35-
height='400'
34+
layout='constrained'
35+
objectFit='fill'
36+
width={500}
37+
height={400}
38+
alt='hero'
3639
src={image}
3740
/>
3841
</div>

0 commit comments

Comments
 (0)