diff --git a/src/stories/ecommerce/Ecommerce.stories.tsx b/src/stories/ecommerce/Ecommerce.stories.tsx
index 0062a972..08784a49 100644
--- a/src/stories/ecommerce/Ecommerce.stories.tsx
+++ b/src/stories/ecommerce/Ecommerce.stories.tsx
@@ -10,18 +10,17 @@ const meta = {
title: "Examples/Ecommerce",
parameters: {
layout: "fullscreen"
- }
-};
-
-export default meta;
-
-export const Collections = {
- render: function Render() {
- return (
+ },
+ decorators: [
+ (Story) => (
-
+
- );
- }
+ )
+ ]
};
-export const Details = {
+export default meta;
+
+export const Collections = {
render: function Render() {
- return (
-
-
-
- );
+ return ;
}
};
export const QuickViews = {
render: function Render() {
- return (
-
-
-
- );
+ return ;
+ }
+};
+
+export const Details = {
+ render: function Render() {
+ return
;
}
};
diff --git a/src/stories/ecommerce/examples/collections/Collections.tsx b/src/stories/ecommerce/examples/collections/Collections.tsx
index 2cb1b41e..3845426b 100644
--- a/src/stories/ecommerce/examples/collections/Collections.tsx
+++ b/src/stories/ecommerce/examples/collections/Collections.tsx
@@ -44,7 +44,7 @@ export function Collections(): React.ReactElement {
// If no filters are applied, return all products
const filteredProducts = PRODUCTS.map((product) => ({
...product,
- image: data?.results[product.id]?.urls.small
+ image: data?.results[product.id - 1]?.urls.small
})).filter(({ category, color, tag }) => {
// Check if the product matches the Tag filter (only if filters.tag is not empty)
const matchesTag = filters.tag.length === 0 || filters.tag.includes(tag);
diff --git a/src/stories/ecommerce/examples/collections/Details.tsx b/src/stories/ecommerce/examples/collections/Details.tsx
index fe9befc2..6e078c51 100644
--- a/src/stories/ecommerce/examples/collections/Details.tsx
+++ b/src/stories/ecommerce/examples/collections/Details.tsx
@@ -1,19 +1,34 @@
-import React, { useMemo, useReducer, useState } from "react";
+import React, { useMemo, useState } from "react";
// UI Components
-import { Text, Container, Stack, Box, Accordion, Grid, Checkbox, Separator } from "@stewed/react";
+import {
+ Text,
+ Container,
+ Stack,
+ Box,
+ Grid,
+ Tag,
+ FormField,
+ Group,
+ Button,
+ TextField,
+ AspectRatio,
+ Avatar,
+ Separator,
+ Progress
+} from "@stewed/react";
// Partials
import { Products } from "./components/Products";
// Hooks
import { useFetchImages } from "../../../../api/useFetchImages";
-// Data
-import { PRODUCTS, SIZES } from "../data";
-import { AspectRatio } from "@stewed/react";
-import { Tag, FormField, Group, Button, TextField } from "@stewed/react";
-import { HiStar, HiMinusSm, HiOutlinePlusSm } from "react-icons/hi";
import { useInput } from "@stewed/hooks";
+// Icons
+import { HiStar, HiMinusSm, HiOutlinePlusSm } from "react-icons/hi";
+// Data
+import { PRODUCTS, SIZES, REVIEWS } from "../data";
export function Details(): React.ReactElement {
const { data } = useFetchImages({ query: "fashion", perPage: 5 });
+ const { data: profiles } = useFetchImages({ query: "profile", perPage: REVIEWS.length });
// This prevents unnecessary recalculations when the component re-renders.
const product = useMemo(() => PRODUCTS.find(({ discount }) => discount), []);
@@ -36,14 +51,32 @@ export function Details(): React.ReactElement {
[data?.results]
);
+ const reviewAnalysis = useMemo(() => {
+ const distribution = REVIEWS.reduce(
+ (acc, { reviewRate }) => {
+ acc[reviewRate] = (acc[reviewRate] || 0) + 1;
+ return acc;
+ },
+ { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }
+ );
+
+ return Object.entries(distribution)
+ .map(([rate, count]) => ({
+ rate: Number(rate),
+ percentage: Math.round((count / REVIEWS.length) * 100)
+ }))
+ .reverse();
+ }, []);
+
return (
-
-
-
+
+
+
+
{product?.name}
@@ -71,7 +104,7 @@ export function Details(): React.ReactElement {
{product?.rate && (
-
+
{Array.from({ length: 5 }).map((_, index) => (
-
- ({product.rate}){" "}
- {product?.reviews > 1000 ? `${product?.reviews / 1000}k` : product?.reviews}{" "}
- reviews
+
+ ({product?.reviews} reviews)
)}
@@ -98,101 +129,114 @@ export function Details(): React.ReactElement {
{product?.description}
-
- Size
-
-
- {SIZES.map((value) => (
-
- ))}
-
-
-
-
-
- Pick Color
-
-
- {product?.color.map((value) => (
-
- ))}
-
-
-
-
-
- Quantity
-
-
-
- }
- onClick={() => setValue(Number(value) - 1)}
- disabled={value <= 1}
- iconOnly
- >
- Decrease
-
-
- (product?.stock || 0) ? "critical" : "neutral"}
- size="sm"
- appearance="ghost"
- name="quantity"
- value={value}
- onChange={onChange}
- maxChars={Number(product?.stock).toString().length}
- alignment="center"
- pattern="\d*"
- autoComplete="off"
- />
-
- }
- onClick={() => setValue(Number(value) + 1)}
- disabled={value === product?.stock}
- iconOnly
+
+
+
+ Quantity
+
+
- Increase
-
-
-
-
-
+
+ }
+ onClick={() => setValue(Number(value) - 1)}
+ disabled={value <= 1}
+ iconOnly
+ >
+ Decrease
+
+
+ (product?.stock || 0) ? "critical" : "neutral"}
+ size="sm"
+ appearance="ghost"
+ name="quantity"
+ value={value}
+ onChange={onChange}
+ maxChars={Number(product?.stock).toString().length}
+ alignment="center"
+ pattern="\d*"
+ autoComplete="off"
+ />
+
+ }
+ onClick={() => setValue(Number(value) + 1)}
+ disabled={value === product?.stock}
+ iconOnly
+ >
+ Increase
+
+
+
+
+
+
+
+ Size
+
+
+ {SIZES.map((value) => (
+
+ ))}
+
+
+
+
+
+ Pick Color
+
+
+ {product?.color.map((value) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
@@ -207,6 +251,109 @@ export function Details(): React.ReactElement {
+
+
+
+
+
+
+ Customer Reviews
+
+
+
+ {product?.rate && (
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+ ))}
+
+
+
+ based on {product?.reviews} reviews
+
+
+ )}
+
+
+
+
+ {reviewAnalysis.map(({ rate, percentage }) => (
+
+
+
+
+ {rate}
+
+
+ {percentage}%
+
+
+
+
+
+ ))}
+
+
+
+
+
+ Drop your thoughts
+
+
+ Got something to say about this? Share your take with everyone.
+
+
+
+
+
+
+
+
+ {REVIEWS.slice(0, 5).map(({ name, reviewRate, review }, idx) => (
+
+
+
+
+
+
+ {name}
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+ ))}
+
+
+
+
+ {review}
+
+
+
+
+ ))}
+
+
+
);
diff --git a/src/stories/ecommerce/examples/collections/components/ProductItem.tsx b/src/stories/ecommerce/examples/collections/components/ProductItem.tsx
index fe49c1d4..2c9c50ea 100644
--- a/src/stories/ecommerce/examples/collections/components/ProductItem.tsx
+++ b/src/stories/ecommerce/examples/collections/components/ProductItem.tsx
@@ -3,17 +3,11 @@ import React from "react";
import { Hoverable, MotionProps, Text, Card, Motion, Box, Stack, Button } from "@stewed/react";
// Icons
import { IoEyeOutline, IoHeartOutline, IoStar } from "react-icons/io5";
+// Types
+import type { Product } from "../../data";
-export interface ProductItemProps {
- id: number;
- name: string;
+export interface ProductItemProps extends Product {
image: string | undefined;
- category: string;
- price: {
- value: number;
- currency: string;
- };
- rate: number;
}
export function ProductItem({
@@ -25,7 +19,7 @@ export function ProductItem({
}: ProductItemProps): React.ReactElement {
return (
- {({ status }) => {
+ {({ status, isTouch }) => {
let animation: MotionProps["animation"] = "zoom-in-soft";
if (status === "enter") {
@@ -38,7 +32,7 @@ export function ProductItem({
return (
-
+
{({ className }) => (
- {data.length ? (
-
- {data.map((product) => (
-
- ))}
-
- ) : (
-
-
-
- No products found
-
- Your search did not match any product
-
-
- )}
-
+
+
+ {data.length ? (
+
+ {data.map((product) => (
+
+ ))}
+
+ ) : (
+
+
+
+ No products found
+
+ Your search did not match any product
+
+
+ )}
+
+
);
}
diff --git a/src/stories/ecommerce/examples/data.ts b/src/stories/ecommerce/examples/data.ts
index c5451828..ae735223 100644
--- a/src/stories/ecommerce/examples/data.ts
+++ b/src/stories/ecommerce/examples/data.ts
@@ -1,9 +1,114 @@
-export const PRODUCTS = [
+export const REVIEWS = [
+ {
+ name: "Alice Johnson",
+ reviewRate: 5,
+ review:
+ "Absolutely loved the product! The craftsmanship is outstanding, and it arrived well-packaged. Highly recommend it to anyone looking for quality and reliability."
+ },
+ {
+ name: "Michael Smith",
+ reviewRate: 4,
+ review:
+ "Very good overall. The product met my expectations, but shipping took a bit longer than expected. Customer service was helpful in tracking my order, which made the delay more bearable."
+ },
+ {
+ name: "Samantha Brown",
+ reviewRate: 3,
+ review:
+ "The product is decent but not exactly as described. Some features were missing, but it still works fine for basic needs. For the price, I expected a little more attention to detail."
+ },
+ {
+ name: "David Wilson",
+ reviewRate: 2,
+ review:
+ "Not very satisfied. The item arrived damaged, and while the company did offer a replacement, the process was slow and frustrating. I expected better quality control."
+ },
+ {
+ name: "Jessica Lee",
+ reviewRate: 1,
+ review:
+ "Terrible experience. The product broke within days of using it, and customer service was unhelpful. I wouldn't recommend this to anyone."
+ },
+ {
+ name: "Olivia Martinez",
+ reviewRate: 5,
+ review:
+ "This product exceeded my expectations! The design is sleek, and it works perfectly. I've already recommended it to my friends and family. Great job!"
+ },
+ {
+ name: "John Evans",
+ reviewRate: 4,
+ review:
+ "Good value for the money. The product does what it says, but the instructions were a bit unclear. Once I figured it out, everything worked as advertised."
+ },
+ {
+ name: "Emily Clark",
+ reviewRate: 3,
+ review:
+ "Average experience. While the product works, it's not as user-friendly as I hoped. I had to contact support multiple times to understand how to use it effectively."
+ },
+ {
+ name: "Daniel Turner",
+ reviewRate: 5,
+ review:
+ "Fantastic product! I've been using it for weeks now, and it still performs like it's brand new. The attention to detail and durability is impressive."
+ },
+ {
+ name: "Sophia Harris",
+ reviewRate: 2,
+ review:
+ "Disappointed with this purchase. The product feels cheap and doesn't perform as advertised. I might try another brand next time."
+ },
+ {
+ name: "Liam Baker",
+ reviewRate: 4,
+ review:
+ "Happy with the purchase overall. The product is well-built and functional, but it could use a few improvements in terms of design and usability."
+ },
+ {
+ name: "Isabella Wright",
+ reviewRate: 1,
+ review:
+ "Worst purchase I've ever made. The product stopped working within a week, and I couldn't get a refund. Save your money and buy something else."
+ },
+ {
+ name: "Matthew Green",
+ reviewRate: 5,
+ review:
+ "I’m so impressed with this product! It’s exactly what I needed, and the performance is top-notch. I couldn’t be happier with my purchase. Definitely worth the investment!"
+ },
+ {
+ name: "Charlotte King",
+ reviewRate: 5,
+ review:
+ "This product has completely transformed the way I do things! It’s easy to use, reliable, and works exactly as described. I highly recommend it to anyone looking for a high-quality product."
+ },
+ {
+ name: "Ethan Taylor",
+ reviewRate: 5,
+ review:
+ "Incredible! The product exceeded my expectations in every way. It’s built to last, and the functionality is unmatched. Definitely a 5-star product. I’m recommending it to everyone I know."
+ },
+ {
+ name: "Ava Morgan",
+ reviewRate: 5,
+ review:
+ "I’ve been using this for a few weeks now, and it’s still performing perfectly. The attention to detail is remarkable, and the design is modern and sleek. I would buy it again in a heartbeat!"
+ },
+ {
+ name: "Lucas Scott",
+ reviewRate: 5,
+ review:
+ "I’m honestly amazed by this product! It’s worth every penny and more. The quality is exceptional, and it has made my life so much easier. If you're considering buying it, don't hesitate!"
+ }
+];
+
+export const PRODUCTS: Product[] = [
{
id: 1,
name: "Chic Midi Dress",
category: "Dresses",
- rate: 4.3,
+ rate: REVIEWS.reduce((total, review) => total + review.reviewRate, 0) / REVIEWS.length,
color: ["Black", "Red"],
description:
"A timeless midi dress with a flattering silhouette, perfect for any occasion, from casual outings to evening events. The soft fabric feels luxurious and drapes beautifully, making it a versatile wardrobe staple.\n\nThe dress can be dressed up or down depending on the occasion, providing an effortless yet elegant look. Ideal for cocktail parties, dinners, or a day out with friends.\n\nIts versatility and comfort make it a must-have piece for your wardrobe.",
@@ -16,7 +121,7 @@ export const PRODUCTS = [
stock: 120,
sizes: ["S", "M", "L"],
discount: 15,
- reviews: 1200
+ reviews: REVIEWS.length
},
{
id: 2,
@@ -292,7 +397,7 @@ export const PRODUCTS = [
}
];
-export const SIZES = ["XXS", "XS", "S", "M", "L", "XL", "XXL"];
+export const SIZES = ["XXS", "XS", "S", "M", "L", "XL", "XXL"] as const;
export const CATEGORIES = [...new Set(PRODUCTS.map((product) => product.category))];
@@ -343,3 +448,22 @@ export const SORT = [
key: "newest"
}
];
+
+export interface Product {
+ id: number;
+ name: string;
+ category: string;
+ tag: string;
+ description: string;
+ sales: number;
+ color: string[];
+ price: {
+ value: number;
+ currency: string;
+ };
+ sizes: string[];
+ discount?: number;
+ stock: number;
+ rate: number;
+ reviews: number;
+}
diff --git a/src/stories/ecommerce/examples/quick-view/QuickView.tsx b/src/stories/ecommerce/examples/quick-view/QuickView.tsx
index 468b9308..26b32333 100644
--- a/src/stories/ecommerce/examples/quick-view/QuickView.tsx
+++ b/src/stories/ecommerce/examples/quick-view/QuickView.tsx
@@ -63,7 +63,7 @@ export function QuickView(): React.ReactElement {
{product?.rate && (
-
+
{Array.from({ length: 5 }).map((_, index) => (
-
- ({product.rate}){" "}
- {product?.reviews > 1000 ? `${product?.reviews / 1000}k` : product?.reviews}{" "}
- reviews
+
+ ({product?.reviews} reviews)
)}
@@ -135,7 +133,7 @@ export function QuickView(): React.ReactElement {
Quantity