Skip to content

Commit

Permalink
Added UI design changes
Browse files Browse the repository at this point in the history
Added dark theme
  • Loading branch information
mzohaibqc committed Jan 14, 2024
1 parent 716ed19 commit 96fabb5
Show file tree
Hide file tree
Showing 20 changed files with 435 additions and 116 deletions.
53 changes: 53 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
"coverage-badge": "coverage-badger -r coverage/clover.xml -d coverage/"
},
"dependencies": {
"@headlessui/react": "^1.7.18",
"classnames": "^2.5.1",
"next": "14.0.4",
"next-themes": "^0.2.1",
"react": "^18",
"react-dom": "^18",
"react-select": "^5.8.0"
Expand Down
4 changes: 0 additions & 4 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@
button, a {
@apply focus:border-black focus:outline-none focus:ring-2 focus:ring-black;
}

body {
background: var(--background);
}
15 changes: 11 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from 'next';
import { Urbanist } from 'next/font/google';
import Header from '@/components/Header';
import { CartProvider } from '@/store';
import { Provider as ThemeProvider } from '@/store/ThemeProvider';

import './globals.css';

Expand All @@ -23,10 +24,16 @@ export default function RootLayout({
return (
<html lang="en">
<body className={font.className}>
<Header />
<main className="min-h-screen max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 pt-16">
<CartProvider>{children}</CartProvider>
</main>
<ThemeProvider attribute="class" defaultTheme="light">
<CartProvider>
<div className="bg-white dark:bg-gray-800 dark:text-white text-gray-900 flex flex-col min-h-screen">
<Header />
<main className="flex-1 max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
{children}
</main>
</div>
</CartProvider>
</ThemeProvider>
</body>
</html>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function Home() {
return <div className="py-20">{error}</div>;
}
return (
<div className="mt-2 md:mt-4">
<div className="mt-4 lg:mt-6">
<ProductsProvider products={products}>
<Products />
</ProductsProvider>
Expand Down
20 changes: 16 additions & 4 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@

import classNames from 'classnames';

type Props = React.ComponentPropsWithoutRef<'button'>;
type Props = React.ComponentPropsWithoutRef<'button'> & {
size?: 'small' | 'medium' | 'large';
};

export default function Button({ className, children, ...rest }: Props) {
export default function Button({
className,
size = 'medium',
children,
...rest
}: Props) {
return (
<button
className={classNames(
'w-6 h-6 rounded-md flex items-center justify-center cursor-pointer disabled:cursor-not-allowed text-lg hover:bg-gray-100 hover:border hover:border-gray-300',
className
'text-gray-900 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-md text-sm p-1 w-10 h-10 inline-flex items-center justify-center',
className,
{
'w-5 h-5': size === 'small',
'w-6 h-6': size === 'medium',
'w-8 h-8': size === 'large',
}
)}
{...rest}
>
Expand Down
76 changes: 68 additions & 8 deletions src/components/Cart/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,75 @@
'use client';

import classNames from 'classnames';
import { Fragment, useMemo } from 'react';
import { Popover, Transition } from '@headlessui/react';
import { useCart } from '@/store';
import { Cart as CartLogo } from '@/components/Icons';
import CartItem from '@/components/CartItem';
import CountDot from '@/components/CountDot';
import Button from '../Button';

export default function CartPopover() {
const { total, cart } = useCart();
const itemsCount = useMemo(
() => cart.reduce((acc, item) => acc + item.count, 0),
[cart]
);

export default function Cart() {
const { total } = useCart();
return (
<div className="w-full lg:w-[300px] lg:h-[400px] p-4 rounded-md border bg-white flex justify-between">
<p className="text-lg font-medium">Cart Total:</p>
<p className="text-2xl font-medium">
&pound;<span data-testid="cart-total">{total.toFixed(2)}</span>
</p>
</div>
<Popover className="relative">
{({ open }) => (
<>
<Popover.Button
as={Button}
size="large"
// className={`
// ${open ? 'text-black' : 'text-black/90'}
// text-gray-900 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-md text-sm p-1 w-8 h-8 inline-flex items-center justify-center relative`}
>
<CartLogo className="w-6 h-6 dark:text-gray-200 text-gray-900" />

{itemsCount > 0 && (
<CountDot count={itemsCount} className="absolute -top-2 left-6" />
)}
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-1/2 z-10 mt-3 sm:mt-2 w-[400px] max-w-xs sm:max-w-sm -translate-x-full ml-8 transform px-4 sm:px-0">
<div className="w-full p-4 rounded-md border border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-900 flex flex-col justify-between gap-2 shadow-[0_8px_30px_rgb(0,0,0,0.12)]">
<div className="flex flex-col gap-2">
{cart.map((item) => (
<CartItem key={item.id} product={item} />
))}
{cart.length === 0 && <p className="text-center">No Items</p>}
</div>

<div
className={classNames(
'flex justify-between pt-2 px-1 border-t',
{
'mt-3 ': cart.length > 0,
}
)}
>
<p className="text-lg font-medium">Cart Total:</p>
<p className="text-2xl font-medium font-mono">
&pound;
<span data-testid="cart-total">{total.toFixed(2)}</span>
</p>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
}
109 changes: 109 additions & 0 deletions src/components/CartItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use client';
import classNames from 'classnames';
import Image from 'next/image';
import { useCallback, useMemo } from 'react';
import Button from '@/components/Button';
import Color from '@/components/Color';
import { Add, Minus } from '@/components/Icons';
import { useCart } from '@/store';
import { Product } from '../ProductItem';

type Props = {
product: Product;
className?: string;
};

export default function CartItem({ product, className }: Props) {
const { addItem, removeItem, clearItem, cart } = useCart();
const count = useMemo(
() => cart.find((item) => item.id === product.id)?.count || 0,
[cart, product]
);
const handleAddItem = useCallback(() => addItem(product), [addItem, product]);
const handleRemoveItem = useCallback(
() => removeItem(product),
[removeItem, product]
);
const handleClearItem = useCallback(
() => clearItem(product),
[clearItem, product]
);
const disabled = useMemo(() => count === 0, [count]);
return (
<div
className={classNames(className, 'flex flex-col')}
data-testid={`product-${product.id}`}
>
<div className="flex-1 flex rounded-md overflow-hidden bg-white dark:bg-gray-900 shadow-[rgba(17,_17,_26,_0.1)_0px_0px_16px] border border-gray-200 dark:border-gray-800">
<Image
priority
src={product.img}
alt="Product Image"
className="rounded-l-md"
width={50}
height={80}
data-testid={`product-${product.id}-img`}
style={{ objectFit: 'cover', objectPosition: '50% 20%' }}
/>
<div className="info flex-1 px-2 py-1 flex flex-col">
<h2
className="text-sm font-semibold mb-1 line-clamp-1"
title={product.name}
aria-label={product.name}
>
{product.name}
</h2>

<div className="flex justify-between items-center gap-3 mb-1">
<div className="flex items-center gap-3">
<Color
color={product.colour}
data-testid={`product-${product.id}-color`}
size="small"
/>
<p className="text-sm text-gray-500 font-medium font-mono">
&pound;
<span data-testid={`product-${product.id}-price`}>
{product.price.toFixed(2)}
</span>
</p>
</div>
<button
className="px-1 text-sm text-gray-600 disabled:text-gray-400 disabled:cursor-not-allowed disabled:hover:no-underline hover:underline focus:underline underline-offset-4 decoration-2"
onClick={handleClearItem}
disabled={disabled}
data-testid={`product-${product.id}-clear`}
>
<span>Remove</span>
</button>
</div>
<div className="w-full border border-gray-200 dark:border-gray-800 px-2 rounded-md sm:max-w-xs flex justify-around items-center">
<Button
onClick={handleRemoveItem}
disabled={disabled}
aria-label="Remove One Item"
data-testid={`product-${product.id}-remove`}
size="small"
>
<Minus className="w-4 h-4" />
</Button>
<span
data-testid={`product-${product.id}-count`}
className="text-md text-gray-500 px-5 min-w-16 rounded-md text-center"
>
{count}
</span>
<Button
onClick={handleAddItem}
aria-label="Add One Item"
data-testid={`product-${product.id}-add`}
size="small"
>
<Add className="w-4 h-4" />
</Button>
</div>
</div>
</div>
</div>
);
}
17 changes: 14 additions & 3 deletions src/components/Color/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@ import classNames from 'classnames';

type Props = React.ComponentPropsWithoutRef<'div'> & {
color: string;
size?: 'small' | 'medium' | 'large';
};

export default function Color({ color, ...rest }: Props) {
export default function Color({
color,
size = 'medium',
className,
...rest
}: Props) {
return (
<div
{...rest}
className={classNames(
'w-6 h-6 rounded-md bg-white shadow-[0_8px_30px_rgb(0,0,0,0.12)]',
rest.className
'rounded-md bg-white shadow-[0_8px_30px_rgb(0,0,0,0.12)] dark:border dark:border-gray-700',
{
'w-4 h-4': size === 'small',
'w-6 h-6': size === 'medium',
'w-8 h-8': size === 'large',
},
className
)}
style={{ backgroundColor: colorMap[color] || color }}
>
Expand Down
Loading

0 comments on commit 96fabb5

Please sign in to comment.