mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
New Release (#371)
* Custom Checkout Progress * Updates to Checkout * Custom Checkout Progress * Adding tabs * Adding Collapse * Adding Collapse * Improving Sidebar Scroll * Modif footer * Changes * More design updates * sidebar cart * More design updates * More design updates * More design updates * More design updates * Types * Types * Design Updates * More changes * More changes * More changes * Changes * Changes * Changes * New tailwind required changes * Sidebar Styling issues with Mobile * Latest changes - Normalizing cart * Styling Fixes * New changes * Changes * latest * Refactor and Renaming some UI Props * Adding Quantity Component * Adding Rating Component * Rating Component * More updates * User Select disabled, plus hidding horizontal scroll bars * Changes * Adding ProductOptions Component and more helpers * Styling updates * Styling updates * Fix for slim tags * Missmatch with RightArrow * Footer updates and some styles * Latest Updates * Latest Updates * Latest Updates * Removing Portal, since it's not needed. We might add it later I'd rather not to. * Removing Portal, since it's not needed. We might add it later I'd rather not to. * Sam backdrop filter * General UI Improvements * General UI Improvements * Search now with Geist Colors * Now with Geist Colors * Changes * Scroll for Mobile on IOs devises * LoadingDots Working (: * Changes * More Changes * Perf changes * More perf changes * Fade to the Nametags in the ProductCard * changes * Search issue ui * Search issue ui * Make sure to only refresh navbar and modals when required * Index revalidate * Fixed image issue * hide album scroll on windows * Fix scrollbar * Changing * Adding 404 with Layout * Removing Toast * Adding Assets * Adding Assets * Progress with LocalProvider * New productTag * Only images for the drop * changes * Empty SWRhooks * Adding Local Provider * Working local * Working view of a LocalProvider * More updates * Changes * Removed react-ticker * default to local if no env available * default to local if no env available * add missing `@` to css import * rewrite search rewrites to multiple pages * allow requests in getStaticProps to execute in parallel * make type import explicit * add a tsconfig.js file * use local provider in tsconfig.js * avoid a circular dependency * Saleor was not in the providers list * avoid circular dependency in bigcommerce * Adding more to the Local Provider (#366) * Adding more data * Adding more data * optimize assets (#370) * Optimize assets (#372) * optimize assets * remove assets * remove assets * cart enabled * Adding saleor * Changes with Webpack * Changes Co-authored-by: Luis Alvarez <luis@vercel.com> Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com> Co-authored-by: Shu Ding <g@shud.in>
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
.root {
|
||||
@apply bg-secondary text-accents-1 cursor-pointer inline-flex px-10 rounded-sm leading-6 transition ease-in-out duration-150 shadow-sm font-semibold text-center justify-center uppercase py-4 border border-transparent items-center;
|
||||
@apply bg-accent-9 text-accent-0 cursor-pointer inline-flex
|
||||
px-10 py-5 leading-6 transition ease-in-out duration-150
|
||||
shadow-sm text-center justify-center uppercase
|
||||
border border-transparent items-center text-sm font-semibold
|
||||
tracking-wide;
|
||||
max-height: 64px;
|
||||
}
|
||||
|
||||
.root:hover {
|
||||
@apply bg-accents-0 text-primary border border-secondary;
|
||||
@apply border-accent-9 bg-accent-6;
|
||||
}
|
||||
|
||||
.root:focus {
|
||||
@@ -11,22 +16,33 @@
|
||||
}
|
||||
|
||||
.root[data-active] {
|
||||
@apply bg-gray-600;
|
||||
@apply bg-accent-6;
|
||||
}
|
||||
|
||||
.loading {
|
||||
@apply bg-accents-1 text-accents-3 border-accents-2 cursor-not-allowed;
|
||||
@apply bg-accent-1 text-accent-3 border-accent-2 cursor-not-allowed;
|
||||
}
|
||||
|
||||
.slim {
|
||||
@apply py-2 transform-none normal-case;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
@apply border border-accent-2 bg-accent-0 text-accent-9 text-sm;
|
||||
}
|
||||
|
||||
.ghost:hover {
|
||||
@apply border-accent-9 bg-accent-9 text-accent-0;
|
||||
}
|
||||
|
||||
.disabled,
|
||||
.disabled:hover {
|
||||
@apply text-accents-4 border-accents-2 bg-accents-1 cursor-not-allowed;
|
||||
@apply text-accent-4 border-accent-2 bg-accent-1 cursor-not-allowed;
|
||||
filter: grayscale(1);
|
||||
-webkit-transform: translateZ(0);
|
||||
-webkit-perspective: 1000;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.progress {
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import { LoadingDots } from '@components/ui'
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
href?: string
|
||||
className?: string
|
||||
variant?: 'flat' | 'slim'
|
||||
variant?: 'flat' | 'slim' | 'ghost'
|
||||
active?: boolean
|
||||
type?: 'submit' | 'reset' | 'button'
|
||||
Component?: string | JSXElementConstructor<any>
|
||||
@@ -39,6 +39,7 @@ const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{
|
||||
[s.ghost]: variant === 'ghost',
|
||||
[s.slim]: variant === 'slim',
|
||||
[s.loading]: loading,
|
||||
[s.disabled]: disabled,
|
||||
|
25
components/ui/Collapse/Collapse.module.css
Normal file
25
components/ui/Collapse/Collapse.module.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.root {
|
||||
@apply border-b border-accent-2 py-4 flex flex-col outline-none;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply flex flex-row items-center;
|
||||
}
|
||||
|
||||
.header .label {
|
||||
@apply text-base font-medium;
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply pt-3 overflow-hidden pl-8;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply mr-3 text-accent-6;
|
||||
margin-left: -6px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.icon.open {
|
||||
transform: rotate(90deg);
|
||||
}
|
46
components/ui/Collapse/Collapse.tsx
Normal file
46
components/ui/Collapse/Collapse.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import cn from 'classnames'
|
||||
import React, { FC, ReactNode, useState } from 'react'
|
||||
import s from './Collapse.module.css'
|
||||
import { ChevronRight } from '@components/icons'
|
||||
import { useSpring, a } from '@react-spring/web'
|
||||
import useMeasure from 'react-use-measure'
|
||||
|
||||
export interface CollapseProps {
|
||||
title: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const Collapse: FC<CollapseProps> = React.memo(({ title, children }) => {
|
||||
const [isActive, setActive] = useState(false)
|
||||
const [ref, { height: viewHeight }] = useMeasure()
|
||||
|
||||
const animProps = useSpring({
|
||||
height: isActive ? viewHeight : 0,
|
||||
config: { tension: 250, friction: 32, clamp: true, duration: 150 },
|
||||
opacity: isActive ? 1 : 0,
|
||||
})
|
||||
|
||||
const toggle = () => setActive((x) => !x)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={s.root}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={isActive}
|
||||
onClick={toggle}
|
||||
>
|
||||
<div className={s.header}>
|
||||
<ChevronRight className={cn(s.icon, { [s.open]: isActive })} />
|
||||
<span className={s.label}>{title}</span>
|
||||
</div>
|
||||
<a.div style={{ overflow: 'hidden', ...animProps }}>
|
||||
<div ref={ref} className={s.content}>
|
||||
{children}
|
||||
</div>
|
||||
</a.div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default Collapse
|
2
components/ui/Collapse/index.ts
Normal file
2
components/ui/Collapse/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './Collapse'
|
||||
export * from './Collapse'
|
@@ -1,14 +1,19 @@
|
||||
import cn from 'classnames'
|
||||
import React, { FC } from 'react'
|
||||
|
||||
interface Props {
|
||||
interface ContainerProps {
|
||||
className?: string
|
||||
children?: any
|
||||
el?: HTMLElement
|
||||
clean?: boolean
|
||||
}
|
||||
|
||||
const Container: FC<Props> = ({ children, className, el = 'div', clean }) => {
|
||||
const Container: FC<ContainerProps> = ({
|
||||
children,
|
||||
className,
|
||||
el = 'div',
|
||||
clean,
|
||||
}) => {
|
||||
const rootClassName = cn(className, {
|
||||
'mx-auto max-w-8xl px-6': !clean,
|
||||
})
|
||||
|
@@ -1,7 +1,9 @@
|
||||
.root {
|
||||
--row-height: calc(100vh - 88px);
|
||||
@apply grid grid-cols-1 gap-0;
|
||||
min-height: var(--row-height);
|
||||
|
||||
@screen lg {
|
||||
@apply grid-cols-3 grid-rows-2;
|
||||
}
|
||||
|
||||
& > * {
|
||||
@apply row-span-1 bg-transparent box-border overflow-hidden;
|
||||
@@ -15,17 +17,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.root {
|
||||
@apply grid-cols-3 grid-rows-2;
|
||||
}
|
||||
|
||||
.root & > * {
|
||||
@apply col-span-1;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.default {
|
||||
& > * {
|
||||
@apply bg-transparent;
|
||||
@@ -34,9 +25,17 @@
|
||||
|
||||
.layoutNormal {
|
||||
@apply gap-3;
|
||||
}
|
||||
|
||||
& > * {
|
||||
min-height: 325px;
|
||||
@screen md {
|
||||
.layoutNormal > * {
|
||||
max-height: min-content !important;
|
||||
}
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.layoutNormal > * {
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,13 +51,12 @@
|
||||
}
|
||||
|
||||
&.filled {
|
||||
& > *:nth-child(6n + 1),
|
||||
& > *:nth-child(6n + 5) {
|
||||
& > *:nth-child(6n + 1) {
|
||||
@apply bg-violet;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 5) {
|
||||
@apply bg-blue;
|
||||
& > *:nth-child(6n + 2) {
|
||||
@apply bg-accent-8;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 3) {
|
||||
@@ -83,12 +81,12 @@
|
||||
}
|
||||
|
||||
&.filled {
|
||||
& > *:nth-child(6n + 2) {
|
||||
@apply bg-blue;
|
||||
& > *:nth-child(6n + 1) {
|
||||
@apply bg-violet;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 4) {
|
||||
@apply bg-violet;
|
||||
& > *:nth-child(6n + 2) {
|
||||
@apply bg-accent-8;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 3) {
|
||||
|
@@ -2,14 +2,14 @@ import cn from 'classnames'
|
||||
import { FC, ReactNode, Component } from 'react'
|
||||
import s from './Grid.module.css'
|
||||
|
||||
interface Props {
|
||||
interface GridProps {
|
||||
className?: string
|
||||
children?: ReactNode[] | Component[] | any[]
|
||||
layout?: 'A' | 'B' | 'C' | 'D' | 'normal'
|
||||
variant?: 'default' | 'filled'
|
||||
}
|
||||
|
||||
const Grid: FC<Props> = ({
|
||||
const Grid: FC<GridProps> = ({
|
||||
className,
|
||||
layout = 'A',
|
||||
children,
|
||||
|
@@ -1,9 +1,30 @@
|
||||
.root {
|
||||
@apply mx-auto grid grid-cols-1 py-32 gap-4;
|
||||
@apply flex flex-col py-16 mx-auto;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.title {
|
||||
@apply text-accent-0 font-extrabold text-4xl leading-none tracking-tight;
|
||||
}
|
||||
|
||||
.description {
|
||||
@apply mt-4 text-xl leading-8 text-accent-2 mb-1 lg:max-w-4xl;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.root {
|
||||
@apply grid-cols-2;
|
||||
@apply flex-row items-start justify-center py-32;
|
||||
}
|
||||
.title {
|
||||
@apply text-5xl max-w-xl text-right leading-10 -mt-3;
|
||||
line-height: 3.5rem;
|
||||
}
|
||||
.description {
|
||||
@apply mt-0 ml-6;
|
||||
}
|
||||
}
|
||||
|
||||
@screen xl {
|
||||
.title {
|
||||
@apply text-6xl;
|
||||
}
|
||||
}
|
||||
|
@@ -1,30 +1,26 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Container } from '@components/ui'
|
||||
import { RightArrow } from '@components/icons'
|
||||
import { ArrowRight } from '@components/icons'
|
||||
import s from './Hero.module.css'
|
||||
import Link from 'next/link'
|
||||
interface Props {
|
||||
interface HeroProps {
|
||||
className?: string
|
||||
headline: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const Hero: FC<Props> = ({ headline, description }) => {
|
||||
const Hero: FC<HeroProps> = ({ headline, description }) => {
|
||||
return (
|
||||
<div className="bg-black">
|
||||
<div className="bg-accent-9 border-b border-t border-accent-2">
|
||||
<Container>
|
||||
<div className={s.root}>
|
||||
<h2 className="text-4xl leading-10 font-extrabold text-white sm:text-5xl sm:leading-none sm:tracking-tight lg:text-6xl">
|
||||
{headline}
|
||||
</h2>
|
||||
<div className="flex flex-col justify-between">
|
||||
<p className="mt-5 text-xl leading-7 text-accent-2 text-white">
|
||||
{description}
|
||||
</p>
|
||||
<h2 className={s.title}>{headline}</h2>
|
||||
<div className={s.description}>
|
||||
<p>{description}</p>
|
||||
<Link href="/">
|
||||
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
|
||||
<a className="flex items-center text-accent-0 pt-3 font-bold hover:underline cursor-pointer w-max-content">
|
||||
Read it here
|
||||
<RightArrow width="20" heigh="20" className="ml-1" />
|
||||
<ArrowRight width="20" heigh="20" className="ml-1" />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.root {
|
||||
@apply bg-primary py-2 px-6 w-full appearance-none transition duration-150 ease-in-out pr-10 border border-accents-3 text-accents-6;
|
||||
@apply bg-primary py-2 px-6 w-full appearance-none transition duration-150 ease-in-out pr-10 border border-accent-3 text-accent-6;
|
||||
}
|
||||
|
||||
.root:focus {
|
||||
|
@@ -2,12 +2,12 @@ import cn from 'classnames'
|
||||
import s from './Input.module.css'
|
||||
import React, { InputHTMLAttributes } from 'react'
|
||||
|
||||
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
|
||||
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
className?: string
|
||||
onChange?: (...args: any[]) => any
|
||||
}
|
||||
|
||||
const Input: React.FC<Props> = (props) => {
|
||||
const Input: React.FC<InputProps> = (props) => {
|
||||
const { className, children, onChange, ...rest } = props
|
||||
|
||||
const rootClassName = cn(s.root, {}, className)
|
||||
|
@@ -1,22 +1,23 @@
|
||||
.root {
|
||||
@apply inline-flex text-center items-center leading-7;
|
||||
}
|
||||
|
||||
& span {
|
||||
@apply bg-accents-6 rounded-full h-2 w-2;
|
||||
animation-name: blink;
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
margin: 0 2px;
|
||||
.root .dot {
|
||||
@apply rounded-full h-2 w-2;
|
||||
background-color: currentColor;
|
||||
animation-name: blink;
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.root .dot:nth-of-type(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
&:nth-of-type(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
.root .dot::nth-of-type(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
|
@@ -3,9 +3,9 @@ import s from './LoadingDots.module.css'
|
||||
const LoadingDots: React.FC = () => {
|
||||
return (
|
||||
<span className={s.root}>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
<span className={s.dot} key={`dot_1`} />
|
||||
<span className={s.dot} key={`dot_2`} />
|
||||
<span className={s.dot} key={`dot_3`} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@@ -1,22 +1,22 @@
|
||||
.root {
|
||||
@apply w-full relative;
|
||||
height: 320px;
|
||||
min-width: 100%;
|
||||
@apply w-full min-w-full relative flex flex-row items-center overflow-hidden py-0;
|
||||
max-height: 320px;
|
||||
}
|
||||
|
||||
.container {
|
||||
@apply flex flex-row items-center;
|
||||
.root > div {
|
||||
max-height: 320px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container > * {
|
||||
@apply relative flex-1 px-16 py-4 h-full;
|
||||
min-height: 320px;
|
||||
.root > div > * > *:nth-child(2) * {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.primary {
|
||||
@apply bg-white;
|
||||
@apply bg-accent-0;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
@apply bg-black;
|
||||
@apply bg-accent-9;
|
||||
}
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import cn from 'classnames'
|
||||
import s from './Marquee.module.css'
|
||||
import { FC, ReactNode, Component } from 'react'
|
||||
import Ticker from 'react-ticker'
|
||||
import { FC, ReactNode, Component, Children } from 'react'
|
||||
import { default as FastMarquee } from 'react-fast-marquee'
|
||||
|
||||
interface Props {
|
||||
interface MarqueeProps {
|
||||
className?: string
|
||||
children?: ReactNode[] | Component[] | any[]
|
||||
variant?: 'primary' | 'secondary'
|
||||
}
|
||||
|
||||
const Marquee: FC<Props> = ({
|
||||
const Marquee: FC<MarqueeProps> = ({
|
||||
className = '',
|
||||
children,
|
||||
variant = 'primary',
|
||||
@@ -24,11 +24,15 @@ const Marquee: FC<Props> = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={rootClassName}>
|
||||
<Ticker offset={80}>
|
||||
{() => <div className={s.container}>{children}</div>}
|
||||
</Ticker>
|
||||
</div>
|
||||
<FastMarquee gradient={false} className={rootClassName}>
|
||||
{Children.map(children, (child) => ({
|
||||
...child,
|
||||
props: {
|
||||
...child.props,
|
||||
className: cn(child.props.className, `${variant}`),
|
||||
},
|
||||
}))}
|
||||
</FastMarquee>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,17 @@
|
||||
.root {
|
||||
@apply fixed bg-primary text-primary flex items-center inset-0 z-50 justify-center;
|
||||
background-color: rgba(0, 0, 0, 0.35);
|
||||
@apply fixed bg-black bg-opacity-40 flex items-center inset-0 z-50 justify-center;
|
||||
backdrop-filter: blur(0.8px);
|
||||
-webkit-backdrop-filter: blur(0.8px);
|
||||
}
|
||||
|
||||
.modal {
|
||||
@apply bg-primary p-12 border border-accents-2 relative;
|
||||
@apply bg-primary p-12 border border-accent-2 relative;
|
||||
}
|
||||
|
||||
.modal:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.close {
|
||||
@apply hover:text-accent-5 transition ease-in-out duration-150 focus:outline-none absolute right-0 top-0 m-6;
|
||||
}
|
||||
|
@@ -1,22 +1,20 @@
|
||||
import { FC, useRef, useEffect, useCallback } from 'react'
|
||||
import Portal from '@reach/portal'
|
||||
import s from './Modal.module.css'
|
||||
import FocusTrap from '@lib/focus-trap'
|
||||
import { Cross } from '@components/icons'
|
||||
import {
|
||||
disableBodyScroll,
|
||||
enableBodyScroll,
|
||||
clearAllBodyScrollLocks,
|
||||
enableBodyScroll,
|
||||
} from 'body-scroll-lock'
|
||||
import FocusTrap from '@lib/focus-trap'
|
||||
interface Props {
|
||||
interface ModalProps {
|
||||
className?: string
|
||||
children?: any
|
||||
open?: boolean
|
||||
onClose: () => void
|
||||
onEnter?: () => void | null
|
||||
}
|
||||
|
||||
const Modal: FC<Props> = ({ children, open, onClose, onEnter = null }) => {
|
||||
const Modal: FC<ModalProps> = ({ children, onClose }) => {
|
||||
const ref = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
|
||||
const handleKey = useCallback(
|
||||
@@ -30,36 +28,31 @@ const Modal: FC<Props> = ({ children, open, onClose, onEnter = null }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
if (open) {
|
||||
disableBodyScroll(ref.current)
|
||||
window.addEventListener('keydown', handleKey)
|
||||
} else {
|
||||
enableBodyScroll(ref.current)
|
||||
}
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true })
|
||||
window.addEventListener('keydown', handleKey)
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKey)
|
||||
if (ref && ref.current) {
|
||||
enableBodyScroll(ref.current)
|
||||
}
|
||||
clearAllBodyScrollLocks()
|
||||
window.removeEventListener('keydown', handleKey)
|
||||
}
|
||||
}, [open, handleKey])
|
||||
}, [handleKey])
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
{open ? (
|
||||
<div className={s.root}>
|
||||
<div className={s.modal} role="dialog" ref={ref}>
|
||||
<button
|
||||
onClick={() => onClose()}
|
||||
aria-label="Close panel"
|
||||
className="hover:text-gray-500 transition ease-in-out duration-150 focus:outline-none absolute right-0 top-0 m-6"
|
||||
>
|
||||
<Cross className="h-6 w-6" />
|
||||
</button>
|
||||
<FocusTrap focusFirst>{children}</FocusTrap>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</Portal>
|
||||
<div className={s.root}>
|
||||
<div className={s.modal} role="dialog" ref={ref}>
|
||||
<button
|
||||
onClick={() => onClose()}
|
||||
aria-label="Close panel"
|
||||
className={s.close}
|
||||
>
|
||||
<Cross className="h-6 w-6" />
|
||||
</button>
|
||||
<FocusTrap focusFirst>{children}</FocusTrap>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
27
components/ui/Quantity/Quantity.module.css
Normal file
27
components/ui/Quantity/Quantity.module.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.actions {
|
||||
@apply flex p-1 border-accent-2 border items-center justify-center
|
||||
w-12 text-accent-7;
|
||||
transition-property: border-color, background, color, transform, box-shadow;
|
||||
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.actions:hover {
|
||||
@apply border bg-accent-1 border-accent-3 text-accent-9;
|
||||
transition: border-color;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.actions:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.actions:disabled {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply bg-transparent px-4 w-full h-full focus:outline-none select-none pointer-events-auto;
|
||||
}
|
62
components/ui/Quantity/Quantity.tsx
Normal file
62
components/ui/Quantity/Quantity.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { FC } from 'react'
|
||||
import s from './Quantity.module.css'
|
||||
import { Cross, Plus, Minus } from '@components/icons'
|
||||
import cn from 'classnames'
|
||||
export interface QuantityProps {
|
||||
value: number
|
||||
increase: () => any
|
||||
decrease: () => any
|
||||
handleRemove: React.MouseEventHandler<HTMLButtonElement>
|
||||
handleChange: React.ChangeEventHandler<HTMLInputElement>
|
||||
max?: number
|
||||
}
|
||||
|
||||
const Quantity: FC<QuantityProps> = ({
|
||||
value,
|
||||
increase,
|
||||
decrease,
|
||||
handleChange,
|
||||
handleRemove,
|
||||
max = 6,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row h-9">
|
||||
<button className={s.actions} onClick={handleRemove}>
|
||||
<Cross width={20} height={20} />
|
||||
</button>
|
||||
<label className="w-full border-accent-2 border ml-2">
|
||||
<input
|
||||
className={s.input}
|
||||
onChange={(e) =>
|
||||
Number(e.target.value) < max + 1 ? handleChange(e) : () => {}
|
||||
}
|
||||
value={value}
|
||||
type="number"
|
||||
max={max}
|
||||
min="0"
|
||||
readOnly
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={decrease}
|
||||
className={s.actions}
|
||||
style={{ marginLeft: '-1px' }}
|
||||
disabled={value <= 1}
|
||||
>
|
||||
<Minus width={18} height={18} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={increase}
|
||||
className={cn(s.actions)}
|
||||
style={{ marginLeft: '-1px' }}
|
||||
disabled={value < 1 || value >= max}
|
||||
>
|
||||
<Plus width={18} height={18} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Quantity
|
2
components/ui/Quantity/index.ts
Normal file
2
components/ui/Quantity/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './Quantity'
|
||||
export * from './Quantity'
|
0
components/ui/Rating/Rating.module.css
Normal file
0
components/ui/Rating/Rating.module.css
Normal file
27
components/ui/Rating/Rating.tsx
Normal file
27
components/ui/Rating/Rating.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { FC } from 'react'
|
||||
import rangeMap from '@lib/range-map'
|
||||
import { Star } from '@components/icons'
|
||||
import cn from 'classnames'
|
||||
|
||||
export interface RatingProps {
|
||||
value: number
|
||||
}
|
||||
|
||||
const Quantity: React.FC<RatingProps> = React.memo(({ value = 5 }) => {
|
||||
return (
|
||||
<div className="flex flex-row py-6 text-accent-9">
|
||||
{rangeMap(5, (i) => (
|
||||
<span
|
||||
key={`star_${i}`}
|
||||
className={cn('inline-block ml-1 ', {
|
||||
'text-accent-5': i >= Math.floor(value),
|
||||
})}
|
||||
>
|
||||
<Star />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default Quantity
|
2
components/ui/Rating/index.ts
Normal file
2
components/ui/Rating/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './Rating'
|
||||
export * from './Rating'
|
@@ -1,3 +1,14 @@
|
||||
.root {
|
||||
@apply fixed inset-0 overflow-hidden h-full z-50;
|
||||
@apply fixed inset-0 h-full z-50 box-border;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@apply h-full flex flex-col text-base bg-accent-0 shadow-xl overflow-y-auto overflow-x-hidden;
|
||||
-webkit-overflow-scrolling: touch !important;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
@apply absolute inset-0 bg-black bg-opacity-40 duration-100 ease-linear;
|
||||
backdrop-filter: blur(0.8px);
|
||||
-webkit-backdrop-filter: blur(0.8px);
|
||||
}
|
||||
|
@@ -1,54 +1,45 @@
|
||||
import s from './Sidebar.module.css'
|
||||
import Portal from '@reach/portal'
|
||||
import { FC, useEffect, useRef } from 'react'
|
||||
import s from './Sidebar.module.css'
|
||||
import cn from 'classnames'
|
||||
import {
|
||||
disableBodyScroll,
|
||||
enableBodyScroll,
|
||||
clearAllBodyScrollLocks,
|
||||
} from 'body-scroll-lock'
|
||||
|
||||
interface Props {
|
||||
interface SidebarProps {
|
||||
children: any
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const Sidebar: FC<Props> = ({ children, open = false, onClose }) => {
|
||||
const Sidebar: FC<SidebarProps> = ({ children, onClose }) => {
|
||||
const ref = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
if (open) {
|
||||
disableBodyScroll(ref.current)
|
||||
} else {
|
||||
enableBodyScroll(ref.current)
|
||||
}
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true })
|
||||
}
|
||||
return () => {
|
||||
if (ref && ref.current) {
|
||||
enableBodyScroll(ref.current)
|
||||
}
|
||||
clearAllBodyScrollLocks()
|
||||
}
|
||||
}, [open])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
{open ? (
|
||||
<div className={s.root} ref={ref}>
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<div
|
||||
className="absolute inset-0 bg-black bg-opacity-50 transition-opacity"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<section className="absolute inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16 outline-none">
|
||||
<div className="h-full md:w-screen md:max-w-md">
|
||||
<div className="h-full flex flex-col text-base bg-accents-1 shadow-xl overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div className={cn(s.root)}>
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<div className={s.backdrop} onClick={onClose} />
|
||||
<section className="absolute inset-y-0 right-0 max-w-full flex outline-none pl-10">
|
||||
<div className="h-full w-full md:w-screen md:max-w-md">
|
||||
<div className={s.sidebar} ref={ref}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</Portal>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -2,10 +2,10 @@
|
||||
@apply block;
|
||||
background-image: linear-gradient(
|
||||
270deg,
|
||||
var(--accents-1),
|
||||
var(--accents-2),
|
||||
var(--accents-2),
|
||||
var(--accents-1)
|
||||
var(--accent-0),
|
||||
var(--accent-2),
|
||||
var(--accent-0),
|
||||
var(--accent-1)
|
||||
);
|
||||
background-size: 400% 100%;
|
||||
animation: loading 8s ease-in-out infinite;
|
||||
@@ -28,10 +28,10 @@
|
||||
z-index: 100;
|
||||
background-image: linear-gradient(
|
||||
270deg,
|
||||
var(--accents-1),
|
||||
var(--accents-2),
|
||||
var(--accents-2),
|
||||
var(--accents-1)
|
||||
var(--accent-0),
|
||||
var(--accent-2),
|
||||
var(--accent-0),
|
||||
var(--accent-1)
|
||||
);
|
||||
background-size: 400% 100%;
|
||||
animation: loading 8s ease-in-out infinite;
|
||||
|
@@ -3,17 +3,17 @@ import cn from 'classnames'
|
||||
import px from '@lib/to-pixels'
|
||||
import s from './Skeleton.module.css'
|
||||
|
||||
interface Props {
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
boxHeight?: string | number
|
||||
style?: CSSProperties
|
||||
interface SkeletonProps {
|
||||
show?: boolean
|
||||
block?: boolean
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
boxHeight?: string | number
|
||||
}
|
||||
|
||||
const Skeleton: React.FC<Props> = ({
|
||||
const Skeleton: React.FC<SkeletonProps> = ({
|
||||
style,
|
||||
width,
|
||||
height,
|
||||
|
@@ -1,9 +1,9 @@
|
||||
.body {
|
||||
@apply text-lg leading-7 font-medium max-w-6xl mx-auto;
|
||||
@apply text-base leading-7 max-w-6xl mx-auto;
|
||||
}
|
||||
|
||||
.heading {
|
||||
@apply text-5xl mb-12;
|
||||
@apply text-5xl pt-1 pb-2 font-semibold tracking-wide cursor-pointer mb-2;
|
||||
}
|
||||
|
||||
.pageHeading {
|
||||
@@ -11,5 +11,5 @@
|
||||
}
|
||||
|
||||
.sectionHeading {
|
||||
@apply pt-1 pb-2 font-semibold leading-7 tracking-wider uppercase border-b border-accents-2 mb-3;
|
||||
@apply pt-1 pb-2 text-2xl font-bold tracking-wide cursor-pointer mb-2;
|
||||
}
|
||||
|
@@ -6,22 +6,24 @@ import React, {
|
||||
import cn from 'classnames'
|
||||
import s from './Text.module.css'
|
||||
|
||||
interface Props {
|
||||
interface TextProps {
|
||||
variant?: Variant
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
children?: React.ReactNode | any
|
||||
html?: string
|
||||
onClick?: () => any
|
||||
}
|
||||
|
||||
type Variant = 'heading' | 'body' | 'pageHeading' | 'sectionHeading'
|
||||
|
||||
const Text: FunctionComponent<Props> = ({
|
||||
const Text: FunctionComponent<TextProps> = ({
|
||||
style,
|
||||
className = '',
|
||||
variant = 'body',
|
||||
children,
|
||||
html,
|
||||
onClick,
|
||||
}) => {
|
||||
const componentsMap: {
|
||||
[P in Variant]: React.ComponentType<any> | string
|
||||
@@ -56,6 +58,7 @@ const Text: FunctionComponent<Props> = ({
|
||||
},
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
{...htmlContentProps}
|
||||
>
|
||||
|
@@ -1,13 +1,12 @@
|
||||
import React, { FC, useMemo } from 'react'
|
||||
import React, { FC, useCallback, useMemo } from 'react'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
|
||||
export interface State {
|
||||
displaySidebar: boolean
|
||||
displayDropdown: boolean
|
||||
displayModal: boolean
|
||||
displayToast: boolean
|
||||
sidebarView: string
|
||||
modalView: string
|
||||
toastText: string
|
||||
userAvatar: string
|
||||
}
|
||||
|
||||
@@ -16,8 +15,7 @@ const initialState = {
|
||||
displayDropdown: false,
|
||||
displayModal: false,
|
||||
modalView: 'LOGIN_VIEW',
|
||||
displayToast: false,
|
||||
toastText: '',
|
||||
sidebarView: 'CART_VIEW',
|
||||
userAvatar: '',
|
||||
}
|
||||
|
||||
@@ -28,16 +26,6 @@ type Action =
|
||||
| {
|
||||
type: 'CLOSE_SIDEBAR'
|
||||
}
|
||||
| {
|
||||
type: 'OPEN_TOAST'
|
||||
}
|
||||
| {
|
||||
type: 'CLOSE_TOAST'
|
||||
}
|
||||
| {
|
||||
type: 'SET_TOAST_TEXT'
|
||||
text: ToastText
|
||||
}
|
||||
| {
|
||||
type: 'OPEN_DROPDOWN'
|
||||
}
|
||||
@@ -54,6 +42,10 @@ type Action =
|
||||
type: 'SET_MODAL_VIEW'
|
||||
view: MODAL_VIEWS
|
||||
}
|
||||
| {
|
||||
type: 'SET_SIDEBAR_VIEW'
|
||||
view: SIDEBAR_VIEWS
|
||||
}
|
||||
| {
|
||||
type: 'SET_USER_AVATAR'
|
||||
value: string
|
||||
@@ -65,7 +57,8 @@ type MODAL_VIEWS =
|
||||
| 'FORGOT_VIEW'
|
||||
| 'NEW_SHIPPING_ADDRESS'
|
||||
| 'NEW_PAYMENT_METHOD'
|
||||
type ToastText = string
|
||||
|
||||
type SIDEBAR_VIEWS = 'CART_VIEW' | 'CHECKOUT_VIEW' | 'PAYMENT_METHOD_VIEW'
|
||||
|
||||
export const UIContext = React.createContext<State | any>(initialState)
|
||||
|
||||
@@ -110,28 +103,16 @@ function uiReducer(state: State, action: Action) {
|
||||
displayModal: false,
|
||||
}
|
||||
}
|
||||
case 'OPEN_TOAST': {
|
||||
return {
|
||||
...state,
|
||||
displayToast: true,
|
||||
}
|
||||
}
|
||||
case 'CLOSE_TOAST': {
|
||||
return {
|
||||
...state,
|
||||
displayToast: false,
|
||||
}
|
||||
}
|
||||
case 'SET_MODAL_VIEW': {
|
||||
return {
|
||||
...state,
|
||||
modalView: action.view,
|
||||
}
|
||||
}
|
||||
case 'SET_TOAST_TEXT': {
|
||||
case 'SET_SIDEBAR_VIEW': {
|
||||
return {
|
||||
...state,
|
||||
toastText: action.text,
|
||||
sidebarView: action.view,
|
||||
}
|
||||
}
|
||||
case 'SET_USER_AVATAR': {
|
||||
@@ -146,29 +127,58 @@ function uiReducer(state: State, action: Action) {
|
||||
export const UIProvider: FC = (props) => {
|
||||
const [state, dispatch] = React.useReducer(uiReducer, initialState)
|
||||
|
||||
const openSidebar = () => dispatch({ type: 'OPEN_SIDEBAR' })
|
||||
const closeSidebar = () => dispatch({ type: 'CLOSE_SIDEBAR' })
|
||||
const toggleSidebar = () =>
|
||||
state.displaySidebar
|
||||
? dispatch({ type: 'CLOSE_SIDEBAR' })
|
||||
: dispatch({ type: 'OPEN_SIDEBAR' })
|
||||
const closeSidebarIfPresent = () =>
|
||||
state.displaySidebar && dispatch({ type: 'CLOSE_SIDEBAR' })
|
||||
const openSidebar = useCallback(
|
||||
() => dispatch({ type: 'OPEN_SIDEBAR' }),
|
||||
[dispatch]
|
||||
)
|
||||
const closeSidebar = useCallback(
|
||||
() => dispatch({ type: 'CLOSE_SIDEBAR' }),
|
||||
[dispatch]
|
||||
)
|
||||
const toggleSidebar = useCallback(
|
||||
() =>
|
||||
state.displaySidebar
|
||||
? dispatch({ type: 'CLOSE_SIDEBAR' })
|
||||
: dispatch({ type: 'OPEN_SIDEBAR' }),
|
||||
[dispatch, state.displaySidebar]
|
||||
)
|
||||
const closeSidebarIfPresent = useCallback(
|
||||
() => state.displaySidebar && dispatch({ type: 'CLOSE_SIDEBAR' }),
|
||||
[dispatch, state.displaySidebar]
|
||||
)
|
||||
|
||||
const openDropdown = () => dispatch({ type: 'OPEN_DROPDOWN' })
|
||||
const closeDropdown = () => dispatch({ type: 'CLOSE_DROPDOWN' })
|
||||
const openDropdown = useCallback(
|
||||
() => dispatch({ type: 'OPEN_DROPDOWN' }),
|
||||
[dispatch]
|
||||
)
|
||||
const closeDropdown = useCallback(
|
||||
() => dispatch({ type: 'CLOSE_DROPDOWN' }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const openModal = () => dispatch({ type: 'OPEN_MODAL' })
|
||||
const closeModal = () => dispatch({ type: 'CLOSE_MODAL' })
|
||||
const openModal = useCallback(
|
||||
() => dispatch({ type: 'OPEN_MODAL' }),
|
||||
[dispatch]
|
||||
)
|
||||
const closeModal = useCallback(
|
||||
() => dispatch({ type: 'CLOSE_MODAL' }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const openToast = () => dispatch({ type: 'OPEN_TOAST' })
|
||||
const closeToast = () => dispatch({ type: 'CLOSE_TOAST' })
|
||||
const setUserAvatar = useCallback(
|
||||
(value: string) => dispatch({ type: 'SET_USER_AVATAR', value }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setUserAvatar = (value: string) =>
|
||||
dispatch({ type: 'SET_USER_AVATAR', value })
|
||||
const setModalView = useCallback(
|
||||
(view: MODAL_VIEWS) => dispatch({ type: 'SET_MODAL_VIEW', view }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setModalView = (view: MODAL_VIEWS) =>
|
||||
dispatch({ type: 'SET_MODAL_VIEW', view })
|
||||
const setSidebarView = useCallback(
|
||||
(view: SIDEBAR_VIEWS) => dispatch({ type: 'SET_SIDEBAR_VIEW', view }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
@@ -182,8 +192,7 @@ export const UIProvider: FC = (props) => {
|
||||
openModal,
|
||||
closeModal,
|
||||
setModalView,
|
||||
openToast,
|
||||
closeToast,
|
||||
setSidebarView,
|
||||
setUserAvatar,
|
||||
}),
|
||||
[state]
|
||||
|
@@ -10,4 +10,7 @@ export { default as Skeleton } from './Skeleton'
|
||||
export { default as Modal } from './Modal'
|
||||
export { default as Text } from './Text'
|
||||
export { default as Input } from './Input'
|
||||
export { default as Collapse } from './Collapse'
|
||||
export { default as Quantity } from './Quantity'
|
||||
export { default as Rating } from './Rating'
|
||||
export { useUI } from './context'
|
||||
|
Reference in New Issue
Block a user