mirror of
https://github.com/vercel/commerce.git
synced 2025-07-27 12:11:23 +00:00
🔀 merge: Merge branch 'common' of https://github.com/KieIO/grocery-vercel-commerce into m2-quangnhan
:%s
This commit is contained in:
@@ -1,20 +1,17 @@
|
||||
@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;
|
||||
.menu {
|
||||
padding-left: 3.2rem;
|
||||
padding-right: 3.2rem;
|
||||
}
|
||||
.logo {
|
||||
@apply font-logo;
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
@@ -0,0 +1,3 @@
|
||||
.headerNoti {
|
||||
@apply flex items-center;
|
||||
}
|
@@ -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 /> <span>You can buy fresh products after <b>11pm</b> or <b>8am</b></span>
|
||||
</div>
|
||||
</NotiMessage>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderNoti;
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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
|
Reference in New Issue
Block a user