🔀 merge: Merge branch 'm1-sonnguyen' of github.com:KieIO/grocery-vercel-commerce into m2-datnguyen

:%s
This commit is contained in:
unknown
2021-08-26 18:29:14 +07:00
67 changed files with 1536 additions and 137 deletions

View File

@@ -0,0 +1,51 @@
@import "../../../styles/utilities";
.banner {
@apply bg-primary-light custom-border-radius-lg overflow-hidden;
@screen md {
border: 1px solid var(--primary);
}
&.large {
.inner {
@screen xl {
@apply bg-right-bottom;
background-size: unset;
}
}
}
.inner {
@apply bg-no-repeat;
background-size: 90%;
background-position: right -500% bottom 0%;
.content {
background-image: linear-gradient(
to right,
rgb(227, 242, 233, 0.9),
rgb(227, 242, 233, 0.5) 80%,
rgb(227, 242, 233, 0)
);
padding: 1.6rem;
max-width: 37rem;
@screen md {
max-width: 49.6rem;
padding: 4.8rem;
}
.top {
.heading {
@apply heading-1 font-heading;
margin-bottom: 1.6rem;
}
.subHeading {
@apply sub-headline;
@screen md {
@apply caption;
}
}
}
.bottom {
margin-top: 4rem;
}
}
}
}

View File

@@ -0,0 +1,48 @@
import classNames from 'classnames'
import Link from 'next/link'
import React, { memo } from 'react'
import { IconArrowRight } from 'src/components/icons'
import { ROUTE } from 'src/utils/constanst.utils'
import { LANGUAGE } from 'src/utils/language.utils'
import ButtonCommon from '../ButtonCommon/ButtonCommon'
import s from './Banner.module.scss'
interface Props {
imgLink: string,
title: string,
subtitle: string,
buttonLabel?: string,
linkButton?: string,
size?: 'small' | 'large',
}
const Banner = memo(({ imgLink, title, subtitle, buttonLabel = LANGUAGE.BUTTON_LABEL.SHOP_NOW, linkButton = ROUTE.HOME, size = 'large' }: Props) => {
return (
<div className={classNames({
[s.banner]: true,
[s[size]]: true,
})}>
<div className={s.inner} style={{ backgroundImage: `url(${imgLink})` }}>
<div className={s.content}>
<div className={s.top}>
<h1 className={s.heading}>
{title}
</h1>
<div className={s.subHeading}>
{subtitle}
</div>
</div>
<div className={s.bottom}>
<Link href={linkButton}>
<a>
<ButtonCommon icon={<IconArrowRight />} isIconSuffix={true}>{buttonLabel}</ButtonCommon>
</a>
</Link>
</div>
</div>
</div>
</div>
)
})
export default Banner

View File

@@ -1,6 +1,5 @@
@import '../../../styles/utilities';
.subtitle {
font-size: var(--font-size);
line-height: var(--line-height);
margin-top: .4rem;
}

View File

@@ -1,19 +1,18 @@
import React from 'react'
// import classNames from 'classnames'
import s from './CollectionHeading.module.scss'
import HeadingCommon from '../HeadingCommon/HeadingCommon'
interface CollectionHeadingProps {
headingType?: 'default' | 'highlight' | 'light'
headingText: string;
subtitle: string
type?: 'default' | 'highlight' | 'light';
title: string;
subtitle: string;
}
const CollectionHeading = ({ headingType='default', headingText, subtitle }: CollectionHeadingProps) => {
const CollectionHeading = ({ type = 'default', title, subtitle }: CollectionHeadingProps) => {
return (
<section className="collectionHeading">
<HeadingCommon headingType={headingType} headingText={headingText}/>
<section>
<HeadingCommon type={type}>{title}</HeadingCommon>
<div className={s.subtitle}>{subtitle}</div>
</section>
)

View File

@@ -0,0 +1,31 @@
@import "../../../styles/utilities";
.footer {
@apply spacing-horizontal;
padding-top: 4rem;
padding-bottom: 2rem;
margin-bottom: 10rem;
.footerMenu {
padding-bottom: 4rem;
}
.menu {
@apply flex flex-wrap;
}
@screen md {
margin-bottom: 0;
padding-bottom: 4rem;
padding-left: 3.2rem;
padding-right: 3.2rem;
.footerMenu {
@apply flex;
padding-bottom: 8rem;
.menu {
@apply flex-nowrap justify-between;
}
}
}
@screen lg {
@apply spacing-horizontal;
}
}

View File

@@ -0,0 +1,85 @@
import React from 'react'
import { ROUTE } from 'src/utils/constanst.utils'
import FooterColumn from './components/FooterColumn/FooterColumn'
import FooterSocial from './components/FooterSocial/FooterSocial'
import s from './Footer.module.scss'
const FOOTER_COLUMNS = [
{
title: 'Company',
items: [
{
name: 'All Product',
link: ROUTE.PRODUCTS,
},
{
name: 'About Us',
link: ROUTE.ABOUT,
},
{
name: 'Bussiness',
link: ROUTE.BUSSINESS,
}
]
},
{
title: 'Resources',
items: [
{
name: 'Contact Us',
link: ROUTE.CONTACT,
},
{
name: 'FAQ',
link: ROUTE.FAQ,
},
{
name: 'Customer Service',
link: ROUTE.CUSTOMER_SERVICE,
},
]
},
{
title: 'Quick Links',
items: [
{
name: 'Terms & Conditions',
link: ROUTE.TERM_CONDITION,
},
{
name: 'Privacy Policy',
link: ROUTE.TERM_CONDITION,
},
{
name: 'Blog',
link: ROUTE.TERM_CONDITION,
},
]
}
]
interface Props {
className?: string
children?: any
}
const Footer = ({ }: Props) => {
return (
<footer className={s.footer}>
<div className={s.footerMenu}>
<section className={s.menu}>
{FOOTER_COLUMNS.map(item => <FooterColumn
key={item.title}
title={item.title}
items={item.items} />)}
</section>
<FooterSocial />
</div>
<div>
© 2021 Online Grocery
</div>
</footer>
)
}
export default Footer

View File

@@ -0,0 +1,33 @@
@import "../../../../../styles/utilities";
.footerColumn {
width: 50%;
margin-bottom: 4rem;
@screen md {
padding-right: 6.4rem;
width: unset;
margin-bottom: 0;
}
@screen lg {
padding-right: 12.8rem;
}
.title {
@apply sm-headline text-active;
margin-bottom: 2.4rem;
}
ul {
list-style: none;
li {
&:not(:last-child) {
margin-bottom: 1.6rem;
}
a {
@apply transition-all duration-200 no-underline;
&:hover {
color: var(--primary);
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
import Link from 'next/link';
import React from 'react';
import s from './FooterColumn.module.scss'
interface Props {
title: string,
items: { link: string, name: string, isOpenNewTab?: boolean }[],
}
const FooterColumn = ({ title, items }: Props) => {
return (
<section className={s.footerColumn}>
<h4 className={s.title}>
{title}
</h4>
<ul>
{
items.map(item => <li key={item.name}>
{
item.isOpenNewTab ?
<a href={item.link} target="_blank" rel="noopener noreferrer">
{item.name}
</a>
:
<Link href={item.link}>
<a >
{item.name}
</a>
</Link>
}
</li>)
}
</ul>
</section>
);
};
export default FooterColumn;

View File

@@ -0,0 +1,43 @@
@import "../../../../../styles/utilities";
.footerSocial {
.title {
@apply sm-headline text-active;
margin-bottom: 2.4rem;
}
.socialMedia,
.payment {
@apply list-none flex items-center;
}
.socialMedia {
li {
@apply transition-all duration-200;
margin-right: 1.6rem;
&:last-child {
margin-right: 0;
}
a {
@apply no-underline;
}
&:hover {
svg path {
fill: var(--primary);
}
}
}
}
.payment {
margin-top: 3.2rem;
li {
margin-right: 1.6rem;
width: 4rem;
img {
width: 100%;
object-fit: contain;
}
&:last-child {
margin-right: 0;
}
}
}
}

View File

@@ -0,0 +1,75 @@
import React from 'react';
import IconFacebook from 'src/components/icons/IconFacebook';
import IconInstagram from 'src/components/icons/IconInstagram';
import IconTwitter from 'src/components/icons/IconTwitter';
import IconYoutube from 'src/components/icons/IconYoutube';
import { SOCIAL_LINKS } from 'src/utils/constanst.utils';
import IconVisa from '../../../../../assets/imgs/visa.png';
import IconMasterCard from '../../../../../assets/imgs/mastercard.png';
import IconGooglePlay from '../../../../../assets/imgs/gpay.png';
import IconApplePay from '../../../../../assets/imgs/apple_pay.png';
import s from './FooterSocial.module.scss';
const SOCIAL_MENU = [
{
icon: <IconFacebook />,
link: SOCIAL_LINKS.FB,
},
{
icon: <IconTwitter />,
link: SOCIAL_LINKS.TWITTER,
},
{
icon: <IconYoutube />,
link: SOCIAL_LINKS.YOUTUBE,
},
{
icon: <IconInstagram />,
link: SOCIAL_LINKS.IG,
},
]
const PAYMENT_METHODS = [
{
icon: IconVisa.src,
name: 'Visa'
},
{
icon: IconMasterCard.src,
name: 'Master Card'
},
{
icon: IconGooglePlay.src,
name: 'GooglePay'
},
{
icon: IconApplePay.src,
name: 'Apple Pay'
},
]
const FooterSocial = () => {
return (
<section className={s.footerSocial}>
<div className={s.title}>Social</div>
<ul className={s.socialMedia}>
{
SOCIAL_MENU.map(item => <li key={item.link}>
<a href={item.link} target="_blank" rel="noopener noreferrer">
{item.icon}
</a>
</li>)
}
</ul>
<ul className={s.payment}>
{
PAYMENT_METHODS.map(item => <li key={item.name}>
<img src={item.icon} alt={item.name} />
</li>)
}
</ul>
</section>
);
};
export default FooterSocial;

View File

@@ -1,22 +1,16 @@
@import "../../../styles/utilities";
.header {
.btn {
// @apply font-bold py-2 px-4 rounded;
@apply sticky bg-white shadow-md;
top: 0;
z-index: 9999;
margin-bottom: 3.2rem;
&.full {
@apply shadow-none;
border: 1px solid var(--border-line);
}
.btnBlue {
@apply bg-primary hover:bg-warning text-label font-bold py-2 px-4 custom-border-radius;
}
.link {
color: theme("colors.warning");
}
.heading {
@apply text-base font-heading;
}
.paragraph {
@apply topline;
}
.logo {
@apply font-logo;
.menu {
padding-left: 3.2rem;
padding-right: 3.2rem;
}
}

View File

@@ -1,4 +1,10 @@
import { FC } from 'react'
import classNames from 'classnames'
import React, { memo, useEffect, useState } from 'react'
import { isMobile } from 'src/utils/funtion.utils'
import HeaderHighLight from './components/HeaderHighLight/HeaderHighLight'
import HeaderMenu from './components/HeaderMenu/HeaderMenu'
import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu'
import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile'
import s from './Header.module.scss'
interface Props {
@@ -6,14 +12,37 @@ interface Props {
children?: any
}
const Header: FC<Props> = ({ }: Props) => {
const Header = memo(({ }: Props) => {
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
useEffect(() => {
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
const handleScroll = () => {
if (!isMobile()) {
if (window.scrollY === 0) {
setIsFullHeader(true)
} else {
setIsFullHeader(false)
}
}
}
return (
<div className={s.header}>
This is Header
<h1 className={s.heading}>This is heading</h1>
<div className={s.logo}>This is logo text</div>
</div>
<>
<header className={classNames({ [s.header]: true, [s.full]: isFullHeader })}>
<HeaderHighLight isShow={isFullHeader} />
<div className={s.menu}>
<HeaderMenu isFull={isFullHeader} />
<HeaderSubMenu isShow={isFullHeader} />
</div>
</header>
<HeaderSubMenuMobile />
</>
)
}
})
export default Header

View File

@@ -0,0 +1,39 @@
@import "../../../../../styles/utilities";
.headerHighLight {
@apply hidden;
@screen md {
transform: translateY(-10rem);
height: 0;
&.show {
@apply flex justify-between items-center spacing-horizontal bg-primary caption;
animation: showHeaderHightlight 0.2s;
height: unset;
transform: none;
padding-top: 0.8rem;
padding-bottom: 0.8rem;
color: var(--white);
.menu {
@apply flex items-center list-none;
padding: 0.8rem 0;
li {
&:not(:last-child) {
margin-right: 3.2rem;
}
a {
@appy no-underline;
}
}
}
}
}
}
@keyframes showHeaderHightlight {
0% {
transform: translateY(-4rem);
}
100% {
transform: none;
}
}

View File

@@ -0,0 +1,49 @@
import classNames from 'classnames'
import Link from 'next/link'
import { memo, useEffect, useRef } from 'react'
import { ROUTE } from 'src/utils/constanst.utils'
import s from './HeaderHighLight.module.scss'
const MENU = [
{
name: 'Delivery & Policy',
link: ROUTE.PRIVACY_POLICY,
},
{
name: 'Blog',
link: ROUTE.BLOGS,
},
{
name: 'About Us',
link: ROUTE.ABOUT,
},
]
interface Props {
children?: any,
isShow: boolean,
}
const HeaderHighLight = memo(({ isShow }: Props) => {
return (
<section className={classNames({ [s.headerHighLight]: true, [s.show]: isShow })}>
<div>
Free Shipping on order $49+ / Express $99+
</div>
<ul className={s.menu}>
{
MENU.map(item => <li key={item.name}>
<Link href={item.link}>
<a >
{item.name}
</a>
</Link>
</li>)
}
</ul>
</section>
)
})
export default HeaderHighLight

View File

@@ -0,0 +1,65 @@
@import "../../../../../styles/utilities";
.headerMenu {
padding-top: 1.6rem;
padding-bottom: 0.8rem;
@screen md {
@apply flex justify-between items-center;
padding-top: 0.8rem;
padding-bottom: 0.8rem;
&.full {
padding-top: 2.4rem;
padding-bottom: 2.4rem;
}
}
.left {
.top {
@apply flex justify-between items-center;
.iconCart {
}
}
.inputSearch {
margin-top: 2.4rem;
@screen lg {
min-width: 51.2rem;
max-width: 50%;
}
}
@screen md {
@apply flex items-center;
.top {
.iconCart {
@apply hidden;
}
}
.inputSearch {
margin-left: 4.8rem;
margin-top: 0;
}
}
}
.menu {
@apply hidden;
@screen md {
@apply flex items-center list-none;
li {
@apply flex justify-center items-center w-full;
&:not(:last-child) {
margin-right: 4.8rem;
@screen lg {
margin-right: 6.4rem;
}
}
a {
@appy no-underline;
&.iconFovourite {
svg path {
fill: var(--negative);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,69 @@
import classNames from 'classnames'
import Link from 'next/link'
import { memo } from 'react'
import InputSearch from 'src/components/common/InputSearch/InputSearch'
import MenuDropdown from 'src/components/common/MenuDropdown/MenuDropdown'
import { IconBuy, IconHeart, IconHistory, IconUser } from 'src/components/icons'
import { ACCOUNT_TAB, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
import s from './HeaderMenu.module.scss'
const OPTION_MENU = [
{
link: ROUTE.ACCOUNT,
name: 'Account',
},
{
link: '/',
name: 'Logout',
},
]
interface Props {
children?: any,
isFull: boolean,
}
const HeaderMenu = memo(({ isFull }: Props) => {
return (
<section className={classNames({ [s.headerMenu]: true, [s.full]: isFull })}>
<div className={s.left}>
<div className={s.top}>
<div>Online Grocery</div>
<button className={s.iconCart}>
<IconBuy />
</button>
</div>
<div className={s.inputSearch}>
<InputSearch />
</div>
</div>
<ul className={s.menu}>
<li>
<Link href={`${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.ORDER}`}>
<a >
<IconHistory />
</a>
</Link>
</li>
<li>
<Link href={`${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.FAVOURITE}`}>
<a className={s.iconFovourite}>
<IconHeart />
</a>
</Link>
</li>
<li>
<MenuDropdown options={OPTION_MENU} isHasArrow={false}><IconUser /></MenuDropdown>
</li>
<li>
<button>
<IconBuy />
</button>
</li>
</ul>
</section>
)
})
export default HeaderMenu

View File

@@ -0,0 +1,3 @@
.headerNoti {
@apply flex items-center;
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import NotiMessage from 'src/components/common/NotiMessage/NotiMessage';
import { IconInfo } from 'src/components/icons';
import s from './HeaderNoti.module.scss';
const HeaderNoti = () => {
return (
<NotiMessage>
<div className={s.headerNoti}>
<IconInfo />&nbsp;<span>You can buy fresh products after <b>11pm</b> or <b>8am</b></span>
</div>
</NotiMessage>
);
};
export default HeaderNoti;

View File

@@ -0,0 +1,42 @@
@import "../../../../../styles/utilities";
.headerSubMenu {
@apply hidden;
@screen md {
transform: translateY(-10rem);
height: 0;
&.show {
@apply block;
padding-bottom: 2.4rem;
transform: none;
height: unset;
@screen lg {
@apply flex justify-between items-center;
}
.menu {
@apply flex items-center list-none;
margin-bottom: 2.4rem;
@screen lg {
margin-bottom: 0;
}
li {
&:not(:last-child) {
margin-right: 2.4rem;
@screen lg {
margin-right: 4rem;
}
}
a {
@appy no-underline;
}
&:hover {
@apply text-primary;
}
&.active {
@apply text-primary;
}
}
}
}
}
}

View File

@@ -0,0 +1,88 @@
import classNames from 'classnames'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { memo } from 'react'
import MenuDropdown from 'src/components/common/MenuDropdown/MenuDropdown'
import { ProductFeature, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
import HeaderNoti from './HeaderNoti/HeaderNoti'
import s from './HeaderSubMenu.module.scss'
const MENU = [
{
name: 'New Items',
link: `${ROUTE.PRODUCTS}?${QUERY_KEY.FEATURED}=${ProductFeature.NewItem}`,
},
{
name: 'Sales',
link: `${ROUTE.PRODUCTS}?${QUERY_KEY.FEATURED}=${ProductFeature.Sales}`,
},
{
name: 'Best Sellers',
link: `${ROUTE.PRODUCTS}?${QUERY_KEY.FEATURED}=${ProductFeature.BestSellers}`,
},
{
name: 'About Us',
link: ROUTE.ABOUT,
},
{
name: 'Blog',
link: ROUTE.BLOGS,
},
]
// note: hard code, remove later
const CATEGORY = [
{
name: 'Veggie',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=veggie`,
},
{
name: 'Seafood',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=seafood`,
},
{
name: 'Frozen',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=frozen`,
},
{
name: 'Coffee Bean',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=coffee-bean`,
},
{
name: 'Sauce',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=sauce`,
},
]
interface Props {
children?: any,
isShow: boolean,
}
const HeaderSubMenu = memo(({ isShow }: Props) => {
const router = useRouter()
return (
<section className={classNames({ [s.headerSubMenu]: true, [s.show]: isShow })}>
<ul className={s.menu}>
{/* todo: handle active item */}
<li>
<MenuDropdown options={CATEGORY} align="left">Categories</MenuDropdown>
</li>
{
MENU.map(item => <li key={item.name}
className={classNames({ [s.active]: router.asPath === item.link })}>
<Link href={item.link}>
<a >
{item.name}
</a>
</Link>
</li>)
}
</ul>
<HeaderNoti />
</section>
)
})
export default HeaderSubMenu

View File

@@ -0,0 +1,50 @@
@import "../../../../../styles/utilities";
.headerSubMenuMobile {
@apply fixed w-full bg-white;
bottom: 0;
left: 0;
padding: 2rem 1rem;
border-top: 1px solid var(--border-line);
box-shadow: -5px 6px 10px rgba(0, 0, 0, 0.2);
.menu {
@apply grid grid-cols-4;
li {
a {
@apply transition-all duration-200 no-underline;
&:hover {
color: var(--primary);
}
}
.menuItem {
@apply flex flex-col justify-center items-center sm-label;
.icon {
position: relative;
margin-bottom: 0.5rem;
svg path {
fill: currentColor;
}
}
&.active {
@apply text-primary;
}
&.dot {
.icon {
&::after {
@apply absolute bg-negative rounded-full;
content: "";
top: 0;
right: 0;
$size: 1rem;
width: $size;
height: $size;
}
}
}
}
}
}
@screen md {
@apply hidden;
}
}

View File

@@ -0,0 +1,66 @@
import classNames from 'classnames'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { memo } from 'react'
import { IconHeart, IconHome, IconShopping, IconUser } from 'src/components/icons'
import { ACCOUNT_TAB, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
import s from './HeaderSubMenuMobile.module.scss'
const OPTION_MENU = [
{
link: ROUTE.HOME,
name: 'Home',
icon: <IconHome />,
isMarked: true,
},
{
link: ROUTE.PRODUCTS,
name: 'Shopping',
icon: <IconShopping />,
isMarked: false,
},
{
link: `${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.FAVOURITE}`,
name: 'Favourites',
icon: <IconHeart />,
isMarked: false,
},
{
link: ROUTE.ACCOUNT,
name: 'Account',
icon: <IconUser />,
isMarked: false,
},
]
interface Props {
children?: any
}
const HeaderSubMenuMobile = memo(({ }: Props) => {
const router = useRouter()
return (
<header className={s.headerSubMenuMobile}>
<ul className={s.menu}>
{
OPTION_MENU.map(item => <li key={item.name}>
<Link href={item.link}>
<a >
<div className={classNames({
[s.menuItem]: true,
[s.dot]: item.isMarked,
[s.active]: router.pathname === item.link, // todo: handle active item
})}>
<span className={s.icon}>{item.icon}</span>
<span className={s.label}>{item.name}</span>
</div>
</a>
</Link>
</li>)
}
</ul>
</header>
)
})
export default HeaderSubMenuMobile

View File

@@ -1,13 +1,13 @@
@import '../../../styles/utilities';
.headingCommon {
@apply heading-1 font-heading uppercase text-left;
@apply heading-1 font-heading text-left;
&.highlight {
color: var(--negative);
}
&.light {
color: var(--disabled);
color: var(--white);
}
&.center {
@apply text-center;

View File

@@ -3,19 +3,19 @@ import classNames from 'classnames'
import s from './HeadingCommon.module.scss'
interface HeadingCommonProps {
headingType?: 'highlight' | 'light' | 'default';
textAlign?: 'center' | 'left';
headingText?: string;
type?: 'highlight' | 'light' | 'default';
align?: 'center' | 'left';
children: string;
}
const HeadingCommon = ({ headingType='default', textAlign='left', headingText='categories' }: HeadingCommonProps) => {
const HeadingCommon = ({ type='default', align='left', children }: HeadingCommonProps) => {
return (
<div className={classNames(s.headingCommon, {
[s[`${headingType}`]]: headingType,
[s[`${textAlign}`]]: textAlign
<h1 className={classNames(s.headingCommon, {
[s[type]]: type,
[s[align]]: align
})}
>{headingText}</div>
>{children}</h1>
)
}

View File

@@ -16,7 +16,7 @@
}
.inputCommon {
@apply block w-full transition-all duration-200 rounded;
padding: 1.6rem;
padding: 1.2rem 1.6rem;
border: 1px solid var(--border-line);
&:hover,
&:focus,
@@ -31,7 +31,8 @@
}
&.custom {
@apply custom-border-radius border-none;
@apply custom-border-radius;
border: 1px solid transparent;
background: var(--gray);
&:hover,
&:focus,

View File

@@ -0,0 +1,8 @@
.mainLayout {
display: flex;
flex-direction: column;
min-height: 100vh;
> main {
flex: 1;
}
}

View File

@@ -1,7 +1,9 @@
import { FC, useRef, useEffect } from 'react'
import Header from '../Header/Header'
import { CommerceProvider } from '@framework'
import { useRouter } from 'next/router'
import { FC } from 'react'
import Footer from '../Footer/Footer'
import Header from '../Header/Header'
import s from './Layout.module.scss'
interface Props {
className?: string
@@ -14,8 +16,11 @@ const Layout: FC<Props> = ({ children }) => {
return (
<CommerceProvider locale={locale}>
<Header />
<main>{children}</main>
<div className={s.mainLayout}>
<Header />
<main >{children}</main>
<Footer />
</div>
</CommerceProvider>
)

View File

@@ -0,0 +1,90 @@
@import "../../../styles/utilities";
.menuDropdown {
@apply relative cursor-pointer;
width: fit-content;
&:hover {
.label {
color: var(--primary);
svg path {
fill: currentColor;
}
}
.menu {
@apply block;
animation: menuDropdownAnimation 0.2s ease-out;
}
}
.label {
@apply flex justify-end items-center transition-all duration-200;
svg path {
width: fit-content;
}
}
&.arrow {
.label {
margin-right: 1.6rem;
}
&::after {
@apply inline-block absolute transition-all duration-100;
content: "";
top: 35%;
right: 0;
border: solid currentColor;
border-width: 0 2px 2px 0;
padding: 2px;
transform: rotate(45deg);
}
&:hover {
&::after {
@apply border-primary;
transform: rotate(-135deg);
}
}
}
.menu {
@apply hidden absolute;
content: "";
right: 0;
top: 2rem;
height: max-content;
min-width: 19.2rem;
z-index: 100;
&.left {
left: 0;
}
&:hover {
@apply block shadow-md;
}
.menuIner {
@apply rounded list-none bg-white;
border: 1px solid var(--text-active);
margin-top: .4rem;
li {
@apply block w-full transition-all duration-200 cursor-pointer text-active;
padding: 0.8rem 1.6rem;
&:hover {
@apply bg-primary-lightest;
color: var(--primary);
}
a {
@apply block;
}
}
}
}
}
@keyframes menuDropdownAnimation {
0% {
opacity: 0;
transform: translateY(1.6rem);
}
100% {
opacity: 1;
transform: none;
}
}

View File

@@ -0,0 +1,42 @@
import classNames from 'classnames';
import Link from 'next/link';
import React from 'react';
import s from './MenuDropdown.module.scss';
interface Props {
children?: React.ReactNode,
options: { link: string, name: string }[],
isHasArrow?: boolean,
align?: 'left'
}
const MenuDropdown = ({ options, children, isHasArrow = true, align }: Props) => {
return (
<div className={classNames({
[s.menuDropdown]: true,
[s.arrow]: isHasArrow,
})}>
<span className={s.label}>
{children}
</span>
<section className={classNames({
[s.menu]: true,
[s.left]: align === 'left',
})} >
<ul className={s.menuIner}>
{
options.map(item => <li key={item.name}>
<Link href={item.link}>
<a >
{item.name}
</a>
</Link>
</li>)
}
</ul>
</section>
</div>
);
};
export default MenuDropdown;

View File

@@ -0,0 +1,9 @@
@import "../../../styles/utilities";
.notiMessage {
@apply caption bg-info-light;
width: fit-content;
color: var(--info-dark);
padding: 0.4rem 1.6rem;
border-radius: 3rem;
}

View File

@@ -0,0 +1,16 @@
import React from 'react'
import s from './NotiMessage.module.scss'
interface Props {
children?: React.ReactNode
}
const NotiMessage = ({ children }: Props) => {
return (
<div className={s.notiMessage}>
{children}
</div>
)
}
export default NotiMessage

View File

@@ -0,0 +1,15 @@
import React, { MutableRefObject } from 'react'
interface ScrollTargetProps {
refScrollUp: MutableRefObject<HTMLDivElement>;
}
const ScrollTarget = ({ refScrollUp } : ScrollTargetProps) => {
return (
<div ref={refScrollUp}></div>
)
}
export default ScrollTarget

View File

@@ -0,0 +1,24 @@
@import '../../../styles/utilities';
.scrollToTop {
@apply hidden;
@screen md {
&.show {
@apply block rounded-lg fixed cursor-pointer;
right: 11.2rem;
bottom: 21.6rem;
width: 6.4rem;
height: 6.4rem;
background-color: var(--border-line);
}
&.hide {
@apply hidden;
}
}
.scrollToTopBtn {
@apply outline-none w-full h-full;
}
}

View File

@@ -0,0 +1,54 @@
import React, { useState, useEffect, MutableRefObject } from 'react'
import classNames from 'classnames'
import s from './ScrollToTop.module.scss'
import ArrowUp from '../../icons/IconArrowUp'
interface ScrollToTopProps {
target: MutableRefObject<HTMLDivElement>;
visibilityHeight?: number;
}
const ScrollToTop = ({ target, visibilityHeight=450 }: ScrollToTopProps) => {
const [scrollPosition, setSrollPosition] = useState(0);
const [showScrollToTop, setShowScrollToTop] = useState("hide");
function handleVisibleButton() {
const position = window.pageYOffset;
setSrollPosition(position);
if (scrollPosition > visibilityHeight) {
return setShowScrollToTop("show")
} else if (scrollPosition < visibilityHeight) {
return setShowScrollToTop("hide");
}
};
function handleScrollUp() {
target.current.scrollIntoView({ behavior: "smooth" });
}
function addEventScroll() {
window.addEventListener("scroll", handleVisibleButton);
}
useEffect(() => {
addEventScroll()
});
return (
<div className={classNames(s.scrollToTop, {
[s[`${showScrollToTop}`]]: showScrollToTop
})}
onClick={handleScrollUp}
>
<button className={s.scrollToTopBtn}>
<ArrowUp />
</button>
</div>
)
}
export default ScrollToTop

View File

@@ -12,7 +12,13 @@ export { default as ViewAllItem} from './ViewAllItem/ViewAllItem'
export { default as ItemWishList} from './ItemWishList/ItemWishList'
export { default as Logo} from './Logo/Logo'
export { default as Inputcommon} from './InputCommon/InputCommon'
export { default as InputSearch} from './InputSearch/InputSearch'
export { default as ButtonIconBuy} from './ButtonIconBuy/ButtonIconBuy'
export { default as HeadingCommon } from './HeadingCommon/HeadingCommon'
export { default as CollectionHeading } from './CollectionHeading/CollectionHeading'
export { default as ScrollToTop } from './ScrollToTop/ScrollToTop'
export { default as ScrollTarget } from './ScrollToTop/ScrollTarget'
export { default as InputSearch} from './InputSearch/InputSearch'
export { default as ButtonIconBuy} from './ButtonIconBuy/ButtonIconBuy'
export { default as Banner} from './Banner/Banner'
export { default as Footer} from './Footer/Footer'
export { default as MenuDropdown} from './MenuDropdown/MenuDropdown'
export { default as NotiMessage} from './NotiMessage/NotiMessage'