mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Monorepo with Turborepo (#651)
* Moved everything * Figuring out how to make imports work * Updated exports * Added missing exports * Added @vercel/commerce-local to `site` * Updated commerce config * Updated exports and commerce config * Updated commerce hoc * Fixed exports in local * Added publish config * Updated imports in site * It's actually working * Don't use debugger in dev for better speeds * Improved DX when editing packages * Set up eslint with husky * Updated prettier config * Added prettier setup to every package * Moved bigcommerce * Moved Bigcommerce to src and package updates * Updated setup of bigcommerce * Moved definitions script * Moved commercejs * Move to src * Fixed types in commercejs * Moved kibocommerce * Moved kibocommerce to src * Added package/tsconfig to kibocommerce * Fixed imports and other things * Moved ordercloud * Moved ordercloud to src * Fixed imports * Added missing prettier files * Moved Saleor * Moved Saleor to src * Fixed imports * Replaced all imports to @commerce * Added prettierignore/rc to all providers * Moved shopify to src * Build shopify in packages * Moved Spree * Moved spree to src * Updated spree * Moved swell * Moved swell to src * Fixed type imports in swell * Moved Vendure to packages * Moved vendure to src * Fixed imports in vendure * Added codegen to saleor * Updated codegen setup for shopify * Added codegen to vendure * Added codegen to kibocommerce * Added all packages to site's deps * Updated codegen setup in bigcommerce * Minor fixes * Updated providers' names in site * Updated packages based on Bel's changes * Updated turbo to latest * Fixed ts complains * Set npm engine in root * New lockfile install * remove engines * Regen lockfile * Switched from npm to yarn * Updated typesVersions in all packages * Moved dep * Updated SWR to the just released 1.2.0 * Removed "isolatedModules" from packages * Updated list of providers and default * Updated swell declaration * Removed next import from kibocommerce * Added COMMERCE_PROVIDER log * Added another log * Updated turbo config * Updated docs * Removed test logs Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
This commit is contained in:
57
site/components/ui/Button/Button.module.css
Normal file
57
site/components/ui/Button/Button.module.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.root {
|
||||
@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 border-accent-9 bg-accent-6;
|
||||
}
|
||||
|
||||
.root:focus {
|
||||
@apply shadow-outline-normal outline-none;
|
||||
}
|
||||
|
||||
.root[data-active] {
|
||||
@apply bg-accent-6;
|
||||
}
|
||||
|
||||
.loading {
|
||||
@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;
|
||||
}
|
||||
|
||||
.naked {
|
||||
@apply bg-transparent font-semibold border-none shadow-none outline-none py-0 px-0;
|
||||
}
|
||||
|
||||
.naked:hover,
|
||||
.naked:focus {
|
||||
@apply bg-transparent border-none;
|
||||
}
|
||||
|
||||
.disabled,
|
||||
.disabled:hover {
|
||||
@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 {
|
||||
}
|
75
site/components/ui/Button/Button.tsx
Normal file
75
site/components/ui/Button/Button.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import cn from 'classnames'
|
||||
import React, {
|
||||
forwardRef,
|
||||
ButtonHTMLAttributes,
|
||||
JSXElementConstructor,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import mergeRefs from 'react-merge-refs'
|
||||
import s from './Button.module.css'
|
||||
import { LoadingDots } from '@components/ui'
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
href?: string
|
||||
className?: string
|
||||
variant?: 'flat' | 'slim' | 'ghost' | 'naked'
|
||||
active?: boolean
|
||||
type?: 'submit' | 'reset' | 'button'
|
||||
Component?: string | JSXElementConstructor<any>
|
||||
width?: string | number
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
|
||||
const {
|
||||
className,
|
||||
variant = 'flat',
|
||||
children,
|
||||
active,
|
||||
width,
|
||||
loading = false,
|
||||
disabled = false,
|
||||
style = {},
|
||||
Component = 'button',
|
||||
...rest
|
||||
} = props
|
||||
const ref = useRef<typeof Component>(null)
|
||||
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{
|
||||
[s.ghost]: variant === 'ghost',
|
||||
[s.slim]: variant === 'slim',
|
||||
[s.naked]: variant === 'naked',
|
||||
[s.loading]: loading,
|
||||
[s.disabled]: disabled,
|
||||
},
|
||||
className
|
||||
)
|
||||
|
||||
return (
|
||||
<Component
|
||||
aria-pressed={active}
|
||||
data-variant={variant}
|
||||
ref={mergeRefs([ref, buttonRef])}
|
||||
className={rootClassName}
|
||||
disabled={disabled}
|
||||
style={{
|
||||
width,
|
||||
...style,
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{loading && (
|
||||
<i className="pl-2 m-0 flex">
|
||||
<LoadingDots />
|
||||
</i>
|
||||
)}
|
||||
</Component>
|
||||
)
|
||||
})
|
||||
|
||||
export default Button
|
2
site/components/ui/Button/index.ts
Normal file
2
site/components/ui/Button/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './Button'
|
||||
export * from './Button'
|
25
site/components/ui/Collapse/Collapse.module.css
Normal file
25
site/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
site/components/ui/Collapse/Collapse.tsx
Normal file
46
site/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> = ({ 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 React.memo(Collapse)
|
2
site/components/ui/Collapse/index.ts
Normal file
2
site/components/ui/Collapse/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './Collapse'
|
||||
export * from './Collapse'
|
27
site/components/ui/Container/Container.tsx
Normal file
27
site/components/ui/Container/Container.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import cn from 'classnames'
|
||||
import React, { FC } from 'react'
|
||||
|
||||
interface ContainerProps {
|
||||
className?: string
|
||||
children?: any
|
||||
el?: HTMLElement
|
||||
clean?: boolean
|
||||
}
|
||||
|
||||
const Container: FC<ContainerProps> = ({
|
||||
children,
|
||||
className,
|
||||
el = 'div',
|
||||
clean,
|
||||
}) => {
|
||||
const rootClassName = cn(className, {
|
||||
'mx-auto max-w-8xl px-6': !clean,
|
||||
})
|
||||
|
||||
let Component: React.ComponentType<React.HTMLAttributes<HTMLDivElement>> =
|
||||
el as any
|
||||
|
||||
return <Component className={rootClassName}>{children}</Component>
|
||||
}
|
||||
|
||||
export default Container
|
1
site/components/ui/Container/index.ts
Normal file
1
site/components/ui/Container/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Container'
|
135
site/components/ui/Grid/Grid.module.css
Normal file
135
site/components/ui/Grid/Grid.module.css
Normal file
@@ -0,0 +1,135 @@
|
||||
.root {
|
||||
@apply grid grid-cols-1 gap-0;
|
||||
}
|
||||
.root > * {
|
||||
@apply row-span-1 bg-transparent box-border overflow-hidden;
|
||||
height: 500px;
|
||||
max-height: 800px;
|
||||
}
|
||||
|
||||
.default > * {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
.layoutNormal {
|
||||
@apply gap-3;
|
||||
}
|
||||
|
||||
.layoutA {
|
||||
& > *:nth-child(6n + 1),
|
||||
& > *:nth-child(6n + 5) {
|
||||
@apply row-span-2 lg:col-span-2;
|
||||
height: var(--row-height);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
& > *:nth-child(6n + 1) {
|
||||
@apply bg-violet;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 2) {
|
||||
@apply bg-accent-8;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 3) {
|
||||
@apply bg-pink;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 6) {
|
||||
@apply bg-cyan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layoutB {
|
||||
& > *:nth-child(6n + 2),
|
||||
& > *:nth-child(6n + 4) {
|
||||
@apply row-span-2 lg:col-span-2;
|
||||
height: var(--row-height);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
& > *:nth-child(6n + 1) {
|
||||
@apply bg-violet;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 2) {
|
||||
@apply bg-accent-8;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 3) {
|
||||
@apply bg-pink;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 6) {
|
||||
@apply bg-cyan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layoutC {
|
||||
& > *:nth-child(12n + 1),
|
||||
& > *:nth-child(12n + 8) {
|
||||
@apply row-span-2 lg:col-span-2;
|
||||
height: var(--row-height);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
& > *:nth-child(12n + 1) {
|
||||
@apply bg-violet;
|
||||
height: var(--row-height);
|
||||
}
|
||||
|
||||
& > *:nth-child(12n + 8) {
|
||||
@apply bg-cyan;
|
||||
height: var(--row-height);
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 3) {
|
||||
@apply bg-pink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layoutD {
|
||||
& > *:nth-child(12n + 2),
|
||||
& > *:nth-child(12n + 7) {
|
||||
@apply row-span-2 lg:col-span-2;
|
||||
height: var(--row-height);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
& > *:nth-child(12n + 2) {
|
||||
@apply bg-violet;
|
||||
}
|
||||
|
||||
& > *:nth-child(12n + 7) {
|
||||
@apply bg-cyan;
|
||||
}
|
||||
|
||||
& > *:nth-child(6n + 3) {
|
||||
@apply bg-pink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.layoutNormal > * {
|
||||
max-height: min-content !important;
|
||||
}
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.root {
|
||||
@apply grid-cols-3 grid-rows-2;
|
||||
}
|
||||
|
||||
.root > * {
|
||||
@apply col-span-1;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.layoutNormal > * {
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
34
site/components/ui/Grid/Grid.tsx
Normal file
34
site/components/ui/Grid/Grid.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import cn from 'classnames'
|
||||
import { FC, ReactNode, Component } from 'react'
|
||||
import s from './Grid.module.css'
|
||||
|
||||
interface GridProps {
|
||||
className?: string
|
||||
children?: ReactNode[] | Component[] | any[]
|
||||
layout?: 'A' | 'B' | 'C' | 'D' | 'normal'
|
||||
variant?: 'default' | 'filled'
|
||||
}
|
||||
|
||||
const Grid: FC<GridProps> = ({
|
||||
className,
|
||||
layout = 'A',
|
||||
children,
|
||||
variant = 'default',
|
||||
}) => {
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{
|
||||
[s.layoutA]: layout === 'A',
|
||||
[s.layoutB]: layout === 'B',
|
||||
[s.layoutC]: layout === 'C',
|
||||
[s.layoutD]: layout === 'D',
|
||||
[s.layoutNormal]: layout === 'normal',
|
||||
[s.default]: variant === 'default',
|
||||
[s.filled]: variant === 'filled',
|
||||
},
|
||||
className
|
||||
)
|
||||
return <div className={rootClassName}>{children}</div>
|
||||
}
|
||||
|
||||
export default Grid
|
1
site/components/ui/Grid/index.ts
Normal file
1
site/components/ui/Grid/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Grid'
|
30
site/components/ui/Hero/Hero.module.css
Normal file
30
site/components/ui/Hero/Hero.module.css
Normal file
@@ -0,0 +1,30 @@
|
||||
.root {
|
||||
@apply flex flex-col py-16 mx-auto;
|
||||
}
|
||||
|
||||
.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 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;
|
||||
}
|
||||
}
|
33
site/components/ui/Hero/Hero.tsx
Normal file
33
site/components/ui/Hero/Hero.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Container } from '@components/ui'
|
||||
import { ArrowRight } from '@components/icons'
|
||||
import s from './Hero.module.css'
|
||||
import Link from 'next/link'
|
||||
interface HeroProps {
|
||||
className?: string
|
||||
headline: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const Hero: FC<HeroProps> = ({ headline, description }) => {
|
||||
return (
|
||||
<div className="bg-accent-9 border-b border-t border-accent-2">
|
||||
<Container>
|
||||
<div className={s.root}>
|
||||
<h2 className={s.title}>{headline}</h2>
|
||||
<div className={s.description}>
|
||||
<p>{description}</p>
|
||||
<Link href="/">
|
||||
<a className="flex items-center text-accent-0 pt-3 font-bold hover:underline cursor-pointer w-max-content">
|
||||
Read it here
|
||||
<ArrowRight width="20" heigh="20" className="ml-1" />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Hero
|
1
site/components/ui/Hero/index.ts
Normal file
1
site/components/ui/Hero/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Hero'
|
7
site/components/ui/Input/Input.module.css
Normal file
7
site/components/ui/Input/Input.module.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.root {
|
||||
@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 {
|
||||
@apply outline-none shadow-outline-normal;
|
||||
}
|
37
site/components/ui/Input/Input.tsx
Normal file
37
site/components/ui/Input/Input.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import cn from 'classnames'
|
||||
import s from './Input.module.css'
|
||||
import React, { InputHTMLAttributes } from 'react'
|
||||
|
||||
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
className?: string
|
||||
onChange?: (...args: any[]) => any
|
||||
}
|
||||
|
||||
const Input: React.FC<InputProps> = (props) => {
|
||||
const { className, children, onChange, ...rest } = props
|
||||
|
||||
const rootClassName = cn(s.root, {}, className)
|
||||
|
||||
const handleOnChange = (e: any) => {
|
||||
if (onChange) {
|
||||
onChange(e.target.value)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<label>
|
||||
<input
|
||||
className={rootClassName}
|
||||
onChange={handleOnChange}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
{...rest}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
export default Input
|
1
site/components/ui/Input/index.ts
Normal file
1
site/components/ui/Input/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Input'
|
11
site/components/ui/Link/Link.tsx
Normal file
11
site/components/ui/Link/Link.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import NextLink, { LinkProps as NextLinkProps } from 'next/link'
|
||||
|
||||
const Link: React.FC<NextLinkProps> = ({ href, children, ...props }) => {
|
||||
return (
|
||||
<NextLink href={href}>
|
||||
<a {...props}>{children}</a>
|
||||
</NextLink>
|
||||
)
|
||||
}
|
||||
|
||||
export default Link
|
1
site/components/ui/Link/index.ts
Normal file
1
site/components/ui/Link/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Link'
|
33
site/components/ui/LoadingDots/LoadingDots.module.css
Normal file
33
site/components/ui/LoadingDots/LoadingDots.module.css
Normal file
@@ -0,0 +1,33 @@
|
||||
.root {
|
||||
@apply inline-flex text-center items-center leading-7;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.root .dot:nth-of-type(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.root .dot::nth-of-type(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
13
site/components/ui/LoadingDots/LoadingDots.tsx
Normal file
13
site/components/ui/LoadingDots/LoadingDots.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import s from './LoadingDots.module.css'
|
||||
|
||||
const LoadingDots: React.FC = () => {
|
||||
return (
|
||||
<span className={s.root}>
|
||||
<span className={s.dot} key={`dot_1`} />
|
||||
<span className={s.dot} key={`dot_2`} />
|
||||
<span className={s.dot} key={`dot_3`} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoadingDots
|
1
site/components/ui/LoadingDots/index.ts
Normal file
1
site/components/ui/LoadingDots/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './LoadingDots'
|
21
site/components/ui/Logo/Logo.tsx
Normal file
21
site/components/ui/Logo/Logo.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
const Logo = ({ className = '', ...props }) => (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
<rect width="100%" height="100%" rx="16" fill="var(--secondary)" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
|
||||
fill="var(--primary)"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Logo
|
1
site/components/ui/Logo/index.ts
Normal file
1
site/components/ui/Logo/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Logo'
|
22
site/components/ui/Marquee/Marquee.module.css
Normal file
22
site/components/ui/Marquee/Marquee.module.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.root {
|
||||
@apply w-full min-w-full relative flex flex-row items-center overflow-hidden py-0;
|
||||
max-height: 320px;
|
||||
}
|
||||
|
||||
.root > div {
|
||||
max-height: 320px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.root > div > * > *:nth-child(2) * {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.primary {
|
||||
@apply bg-accent-0;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
@apply bg-accent-9;
|
||||
}
|
39
site/components/ui/Marquee/Marquee.tsx
Normal file
39
site/components/ui/Marquee/Marquee.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import cn from 'classnames'
|
||||
import s from './Marquee.module.css'
|
||||
import { FC, ReactNode, Component, Children } from 'react'
|
||||
import { default as FastMarquee } from 'react-fast-marquee'
|
||||
|
||||
interface MarqueeProps {
|
||||
className?: string
|
||||
children?: ReactNode[] | Component[] | any[]
|
||||
variant?: 'primary' | 'secondary'
|
||||
}
|
||||
|
||||
const Marquee: FC<MarqueeProps> = ({
|
||||
className = '',
|
||||
children,
|
||||
variant = 'primary',
|
||||
}) => {
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{
|
||||
[s.primary]: variant === 'primary',
|
||||
[s.secondary]: variant === 'secondary',
|
||||
},
|
||||
className
|
||||
)
|
||||
|
||||
return (
|
||||
<FastMarquee gradient={false} className={rootClassName}>
|
||||
{Children.map(children, (child) => ({
|
||||
...child,
|
||||
props: {
|
||||
...child.props,
|
||||
className: cn(child.props.className, `${variant}`),
|
||||
},
|
||||
}))}
|
||||
</FastMarquee>
|
||||
)
|
||||
}
|
||||
|
||||
export default Marquee
|
1
site/components/ui/Marquee/index.ts
Normal file
1
site/components/ui/Marquee/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Marquee'
|
17
site/components/ui/Modal/Modal.module.css
Normal file
17
site/components/ui/Modal/Modal.module.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.root {
|
||||
@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-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;
|
||||
}
|
55
site/components/ui/Modal/Modal.tsx
Normal file
55
site/components/ui/Modal/Modal.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { FC, useRef, useEffect, useCallback } from 'react'
|
||||
import s from './Modal.module.css'
|
||||
import FocusTrap from '@lib/focus-trap'
|
||||
import { Cross } from '@components/icons'
|
||||
import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock'
|
||||
|
||||
interface ModalProps {
|
||||
className?: string
|
||||
children?: any
|
||||
onClose: () => void
|
||||
onEnter?: () => void | null
|
||||
}
|
||||
|
||||
const Modal: FC<ModalProps> = ({ children, onClose }) => {
|
||||
const ref = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
|
||||
const handleKey = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
return onClose()
|
||||
}
|
||||
},
|
||||
[onClose]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const modal = ref.current
|
||||
|
||||
if (modal) {
|
||||
disableBodyScroll(modal, { reserveScrollBarGap: true })
|
||||
window.addEventListener('keydown', handleKey)
|
||||
}
|
||||
return () => {
|
||||
clearAllBodyScrollLocks()
|
||||
window.removeEventListener('keydown', handleKey)
|
||||
}
|
||||
}, [handleKey])
|
||||
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
1
site/components/ui/Modal/index.ts
Normal file
1
site/components/ui/Modal/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Modal'
|
27
site/components/ui/Quantity/Quantity.module.css
Normal file
27
site/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
site/components/ui/Quantity/Quantity.tsx
Normal file
62
site/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
site/components/ui/Quantity/index.ts
Normal file
2
site/components/ui/Quantity/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './Quantity'
|
||||
export * from './Quantity'
|
3
site/components/ui/README.md
Normal file
3
site/components/ui/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# UI
|
||||
|
||||
Building blocks to build a rich graphical interfaces. Components should be atomic and pure. Serve one purpose.
|
0
site/components/ui/Rating/Rating.module.css
Normal file
0
site/components/ui/Rating/Rating.module.css
Normal file
25
site/components/ui/Rating/Rating.tsx
Normal file
25
site/components/ui/Rating/Rating.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { FC, memo } from 'react'
|
||||
import rangeMap from '@lib/range-map'
|
||||
import { Star } from '@components/icons'
|
||||
import cn from 'classnames'
|
||||
|
||||
export interface RatingProps {
|
||||
value: number
|
||||
}
|
||||
|
||||
const Quantity: FC<RatingProps> = ({ value = 5 }) => (
|
||||
<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 memo(Quantity)
|
2
site/components/ui/Rating/index.ts
Normal file
2
site/components/ui/Rating/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './Rating'
|
||||
export * from './Rating'
|
14
site/components/ui/Sidebar/Sidebar.module.css
Normal file
14
site/components/ui/Sidebar/Sidebar.module.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.root {
|
||||
@apply fixed inset-0 h-full z-50 box-border outline-none;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
58
site/components/ui/Sidebar/Sidebar.tsx
Normal file
58
site/components/ui/Sidebar/Sidebar.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { FC, useEffect, useRef } from 'react'
|
||||
import s from './Sidebar.module.css'
|
||||
import cn from 'classnames'
|
||||
import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock'
|
||||
|
||||
interface SidebarProps {
|
||||
children: any
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const Sidebar: FC<SidebarProps> = ({ children, onClose }) => {
|
||||
const sidebarRef = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
const contentRef = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
|
||||
const onKeyDownSidebar = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.code === 'Escape') {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (sidebarRef.current) {
|
||||
sidebarRef.current.focus()
|
||||
}
|
||||
|
||||
const contentElement = contentRef.current
|
||||
|
||||
if (contentElement) {
|
||||
disableBodyScroll(contentElement, { reserveScrollBarGap: true })
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearAllBodyScrollLocks()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(s.root)}
|
||||
ref={sidebarRef}
|
||||
onKeyDown={onKeyDownSidebar}
|
||||
tabIndex={1}
|
||||
>
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<div className={s.backdrop} onClick={onClose} />
|
||||
<section className="absolute inset-y-0 right-0 w-full md:w-auto max-w-full flex outline-none md:pl-10">
|
||||
<div className="h-full w-full md:w-screen md:max-w-md">
|
||||
<div className={s.sidebar} ref={contentRef}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
1
site/components/ui/Sidebar/index.ts
Normal file
1
site/components/ui/Sidebar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Sidebar'
|
48
site/components/ui/Skeleton/Skeleton.module.css
Normal file
48
site/components/ui/Skeleton/Skeleton.module.css
Normal file
@@ -0,0 +1,48 @@
|
||||
.skeleton {
|
||||
@apply block;
|
||||
background-image: linear-gradient(
|
||||
270deg,
|
||||
var(--accent-0),
|
||||
var(--accent-2),
|
||||
var(--accent-0),
|
||||
var(--accent-1)
|
||||
);
|
||||
background-size: 400% 100%;
|
||||
animation: loading 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@apply block relative;
|
||||
|
||||
&:not(.show)::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background-image: linear-gradient(
|
||||
270deg,
|
||||
var(--accent-0),
|
||||
var(--accent-2),
|
||||
var(--accent-0),
|
||||
var(--accent-1)
|
||||
);
|
||||
background-size: 400% 100%;
|
||||
animation: loading 8s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
57
site/components/ui/Skeleton/Skeleton.tsx
Normal file
57
site/components/ui/Skeleton/Skeleton.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React, { CSSProperties } from 'react'
|
||||
import cn from 'classnames'
|
||||
import px from '@lib/to-pixels'
|
||||
import s from './Skeleton.module.css'
|
||||
|
||||
interface SkeletonProps {
|
||||
show?: boolean
|
||||
block?: boolean
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
boxHeight?: string | number
|
||||
}
|
||||
|
||||
const Skeleton: React.FC<SkeletonProps> = ({
|
||||
style,
|
||||
width,
|
||||
height,
|
||||
children,
|
||||
className,
|
||||
show = true,
|
||||
boxHeight = height,
|
||||
}) => {
|
||||
// Automatically calculate the size if there are children
|
||||
// and no fixed sizes are specified
|
||||
const shouldAutoSize = !!children && !(width || height)
|
||||
|
||||
// Defaults
|
||||
width = width || 24
|
||||
height = height || 24
|
||||
boxHeight = boxHeight || height
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cn(s.skeleton, className, {
|
||||
[s.show]: show,
|
||||
[s.wrapper]: shouldAutoSize,
|
||||
[s.loaded]: !shouldAutoSize && !!children,
|
||||
})}
|
||||
style={
|
||||
shouldAutoSize
|
||||
? {}
|
||||
: {
|
||||
minWidth: px(width),
|
||||
minHeight: px(height),
|
||||
marginBottom: `calc(${px(boxHeight)} - ${px(height)})`,
|
||||
...style,
|
||||
}
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default Skeleton
|
1
site/components/ui/Skeleton/index.ts
Normal file
1
site/components/ui/Skeleton/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Skeleton'
|
75
site/components/ui/Text/Text.module.css
Normal file
75
site/components/ui/Text/Text.module.css
Normal file
@@ -0,0 +1,75 @@
|
||||
.body {
|
||||
@apply text-base leading-7 max-w-6xl mx-auto;
|
||||
}
|
||||
|
||||
.heading {
|
||||
@apply text-5xl pt-1 pb-2 font-semibold tracking-wide cursor-pointer mb-2;
|
||||
}
|
||||
|
||||
.pageHeading {
|
||||
@apply pt-1 pb-4 text-2xl leading-7 font-bold tracking-wide;
|
||||
}
|
||||
|
||||
.sectionHeading {
|
||||
@apply pt-1 pb-2 text-2xl font-bold tracking-wide cursor-pointer mb-2;
|
||||
}
|
||||
|
||||
/* Apply base font sizes and styles for typography markup (h2, h2, ul, p, etc.).
|
||||
A helpful addition for whenn page content is consumed from a source managed through a wysiwyg editor. */
|
||||
|
||||
.body :is(h1, h2, h3, h4, h5, h6, p, ul, ol) {
|
||||
@apply mb-4;
|
||||
}
|
||||
|
||||
.body :is(h1, h2, h3, h4, h5, h6):not(:first-child) {
|
||||
@apply mt-8;
|
||||
}
|
||||
|
||||
.body :is(h1, h2, h3, h4, h5, h6) {
|
||||
@apply font-semibold tracking-wide;
|
||||
}
|
||||
|
||||
.body h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
|
||||
.body h2 {
|
||||
@apply text-4xl;
|
||||
}
|
||||
|
||||
.body h3 {
|
||||
@apply text-3xl;
|
||||
}
|
||||
|
||||
.body h4 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
.body h5 {
|
||||
@apply text-xl;
|
||||
}
|
||||
|
||||
.body h6 {
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
.body ul,
|
||||
.body ol {
|
||||
@apply pl-6;
|
||||
}
|
||||
|
||||
.body ul {
|
||||
@apply list-disc;
|
||||
}
|
||||
|
||||
.body ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
|
||||
.body a {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
.body a:hover {
|
||||
@apply no-underline;
|
||||
}
|
70
site/components/ui/Text/Text.tsx
Normal file
70
site/components/ui/Text/Text.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
JSXElementConstructor,
|
||||
CSSProperties,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import s from './Text.module.css'
|
||||
|
||||
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<TextProps> = ({
|
||||
style,
|
||||
className = '',
|
||||
variant = 'body',
|
||||
children,
|
||||
html,
|
||||
onClick,
|
||||
}) => {
|
||||
const componentsMap: {
|
||||
[P in Variant]: React.ComponentType<any> | string
|
||||
} = {
|
||||
body: 'div',
|
||||
heading: 'h1',
|
||||
pageHeading: 'h1',
|
||||
sectionHeading: 'h2',
|
||||
}
|
||||
|
||||
const Component:
|
||||
| JSXElementConstructor<any>
|
||||
| React.ReactElement<any>
|
||||
| React.ComponentType<any>
|
||||
| string = componentsMap![variant!]
|
||||
|
||||
const htmlContentProps = html
|
||||
? {
|
||||
dangerouslySetInnerHTML: { __html: html },
|
||||
}
|
||||
: {}
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={cn(
|
||||
s.root,
|
||||
{
|
||||
[s.body]: variant === 'body',
|
||||
[s.heading]: variant === 'heading',
|
||||
[s.pageHeading]: variant === 'pageHeading',
|
||||
[s.sectionHeading]: variant === 'sectionHeading',
|
||||
},
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
{...htmlContentProps}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
|
||||
export default Text
|
1
site/components/ui/Text/index.ts
Normal file
1
site/components/ui/Text/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Text'
|
216
site/components/ui/context.tsx
Normal file
216
site/components/ui/context.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
import React, { FC, useCallback, useMemo } from 'react'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
|
||||
export interface State {
|
||||
displaySidebar: boolean
|
||||
displayDropdown: boolean
|
||||
displayModal: boolean
|
||||
sidebarView: string
|
||||
modalView: string
|
||||
userAvatar: string
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
displaySidebar: false,
|
||||
displayDropdown: false,
|
||||
displayModal: false,
|
||||
modalView: 'LOGIN_VIEW',
|
||||
sidebarView: 'CART_VIEW',
|
||||
userAvatar: '',
|
||||
}
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: 'OPEN_SIDEBAR'
|
||||
}
|
||||
| {
|
||||
type: 'CLOSE_SIDEBAR'
|
||||
}
|
||||
| {
|
||||
type: 'OPEN_DROPDOWN'
|
||||
}
|
||||
| {
|
||||
type: 'CLOSE_DROPDOWN'
|
||||
}
|
||||
| {
|
||||
type: 'OPEN_MODAL'
|
||||
}
|
||||
| {
|
||||
type: 'CLOSE_MODAL'
|
||||
}
|
||||
| {
|
||||
type: 'SET_MODAL_VIEW'
|
||||
view: MODAL_VIEWS
|
||||
}
|
||||
| {
|
||||
type: 'SET_SIDEBAR_VIEW'
|
||||
view: SIDEBAR_VIEWS
|
||||
}
|
||||
| {
|
||||
type: 'SET_USER_AVATAR'
|
||||
value: string
|
||||
}
|
||||
|
||||
type MODAL_VIEWS =
|
||||
| 'SIGNUP_VIEW'
|
||||
| 'LOGIN_VIEW'
|
||||
| 'FORGOT_VIEW'
|
||||
| 'NEW_SHIPPING_ADDRESS'
|
||||
| 'NEW_PAYMENT_METHOD'
|
||||
|
||||
type SIDEBAR_VIEWS = 'CART_VIEW' | 'CHECKOUT_VIEW' | 'PAYMENT_METHOD_VIEW'
|
||||
|
||||
export const UIContext = React.createContext<State | any>(initialState)
|
||||
|
||||
UIContext.displayName = 'UIContext'
|
||||
|
||||
function uiReducer(state: State, action: Action) {
|
||||
switch (action.type) {
|
||||
case 'OPEN_SIDEBAR': {
|
||||
return {
|
||||
...state,
|
||||
displaySidebar: true,
|
||||
}
|
||||
}
|
||||
case 'CLOSE_SIDEBAR': {
|
||||
return {
|
||||
...state,
|
||||
displaySidebar: false,
|
||||
}
|
||||
}
|
||||
case 'OPEN_DROPDOWN': {
|
||||
return {
|
||||
...state,
|
||||
displayDropdown: true,
|
||||
}
|
||||
}
|
||||
case 'CLOSE_DROPDOWN': {
|
||||
return {
|
||||
...state,
|
||||
displayDropdown: false,
|
||||
}
|
||||
}
|
||||
case 'OPEN_MODAL': {
|
||||
return {
|
||||
...state,
|
||||
displayModal: true,
|
||||
displaySidebar: false,
|
||||
}
|
||||
}
|
||||
case 'CLOSE_MODAL': {
|
||||
return {
|
||||
...state,
|
||||
displayModal: false,
|
||||
}
|
||||
}
|
||||
case 'SET_MODAL_VIEW': {
|
||||
return {
|
||||
...state,
|
||||
modalView: action.view,
|
||||
}
|
||||
}
|
||||
case 'SET_SIDEBAR_VIEW': {
|
||||
return {
|
||||
...state,
|
||||
sidebarView: action.view,
|
||||
}
|
||||
}
|
||||
case 'SET_USER_AVATAR': {
|
||||
return {
|
||||
...state,
|
||||
userAvatar: action.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const UIProvider: FC = (props) => {
|
||||
const [state, dispatch] = React.useReducer(uiReducer, initialState)
|
||||
|
||||
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 = useCallback(
|
||||
() => dispatch({ type: 'OPEN_DROPDOWN' }),
|
||||
[dispatch]
|
||||
)
|
||||
const closeDropdown = useCallback(
|
||||
() => dispatch({ type: 'CLOSE_DROPDOWN' }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const openModal = useCallback(
|
||||
() => dispatch({ type: 'OPEN_MODAL' }),
|
||||
[dispatch]
|
||||
)
|
||||
const closeModal = useCallback(
|
||||
() => dispatch({ type: 'CLOSE_MODAL' }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setUserAvatar = useCallback(
|
||||
(value: string) => dispatch({ type: 'SET_USER_AVATAR', value }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setModalView = useCallback(
|
||||
(view: MODAL_VIEWS) => dispatch({ type: 'SET_MODAL_VIEW', view }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setSidebarView = useCallback(
|
||||
(view: SIDEBAR_VIEWS) => dispatch({ type: 'SET_SIDEBAR_VIEW', view }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
...state,
|
||||
openSidebar,
|
||||
closeSidebar,
|
||||
toggleSidebar,
|
||||
closeSidebarIfPresent,
|
||||
openDropdown,
|
||||
closeDropdown,
|
||||
openModal,
|
||||
closeModal,
|
||||
setModalView,
|
||||
setSidebarView,
|
||||
setUserAvatar,
|
||||
}),
|
||||
[state]
|
||||
)
|
||||
|
||||
return <UIContext.Provider value={value} {...props} />
|
||||
}
|
||||
|
||||
export const useUI = () => {
|
||||
const context = React.useContext(UIContext)
|
||||
if (context === undefined) {
|
||||
throw new Error(`useUI must be used within a UIProvider`)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export const ManagedUIContext: FC = ({ children }) => (
|
||||
<UIProvider>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</UIProvider>
|
||||
)
|
16
site/components/ui/index.ts
Normal file
16
site/components/ui/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export { default as Hero } from './Hero'
|
||||
export { default as Logo } from './Logo'
|
||||
export { default as Grid } from './Grid'
|
||||
export { default as Button } from './Button'
|
||||
export { default as Sidebar } from './Sidebar'
|
||||
export { default as Marquee } from './Marquee'
|
||||
export { default as Container } from './Container'
|
||||
export { default as LoadingDots } from './LoadingDots'
|
||||
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