mirror of
https://github.com/vercel/commerce.git
synced 2025-07-27 04:01:23 +00:00
🔀 merge: Merge branch 'm1-lytran' of https://github.com/KieIO/grocery-vercel-commerce into common
:%s
This commit is contained in:
51
src/components/common/Banner/Banner.module.scss
Normal file
51
src/components/common/Banner/Banner.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
src/components/common/Banner/Banner.tsx
Normal file
48
src/components/common/Banner/Banner.tsx
Normal 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
|
@@ -11,6 +11,20 @@
|
||||
cursor: not-allowed;
|
||||
color: var(--disabled);
|
||||
}
|
||||
&:hover {
|
||||
@apply shadow-md;
|
||||
&:not(:disabled) {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--text-active);
|
||||
}
|
||||
|
||||
&.loading {
|
||||
&::before {
|
||||
content: "";
|
||||
@@ -24,20 +38,6 @@
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@apply shadow-md;
|
||||
&:not(:disabled) {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--text-active);
|
||||
}
|
||||
|
||||
&.light {
|
||||
@apply text-base bg-white;
|
||||
@@ -60,8 +60,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.onlyIcon {
|
||||
padding: 0.8rem;
|
||||
.icon {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
padding: 3.2rem 4.8rem;
|
||||
&.onlyIcon {
|
||||
padding: 1.6rem;
|
||||
}
|
||||
&.loading {
|
||||
&::before {
|
||||
width: 2.4rem;
|
||||
@@ -70,6 +80,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
&.preserve {
|
||||
flex-direction: row-reverse;
|
||||
.icon {
|
||||
@@ -79,6 +91,10 @@
|
||||
|
||||
.icon {
|
||||
margin: 0 1.6rem 0 0;
|
||||
}
|
||||
|
||||
.label,
|
||||
.icon {
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
@@ -6,15 +6,15 @@ interface Props {
|
||||
children?: React.ReactNode,
|
||||
type?: 'primary' | 'light' | 'ghost',
|
||||
size?: 'default' | 'large',
|
||||
icon?: any,
|
||||
icon?: React.ReactNode,
|
||||
isIconSuffix?: boolean,
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
onClick?: () => void,
|
||||
}
|
||||
|
||||
const ButtonCommon = memo(({ type = 'primary', size = 'default',
|
||||
icon, loading, disabled, isIconSuffix, children, onClick }: Props) => {
|
||||
const ButtonCommon = memo(({ type = 'primary', size = 'default', loading = false, isIconSuffix = false,
|
||||
icon, disabled, children, onClick }: Props) => {
|
||||
return (
|
||||
<button className={classNames({
|
||||
[s.buttonCommon]: true,
|
||||
@@ -22,6 +22,7 @@ const ButtonCommon = memo(({ type = 'primary', size = 'default',
|
||||
[s[size]]: !!size,
|
||||
[s.loading]: loading,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.onlyIcon]: icon && !children,
|
||||
})}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
|
26
src/components/common/ButtonIconBuy/ButtonIconBuy.tsx
Normal file
26
src/components/common/ButtonIconBuy/ButtonIconBuy.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { memo } from 'react'
|
||||
import { IconBuy } from 'src/components/icons'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
|
||||
interface Props {
|
||||
type?: 'primary' | 'light' | 'ghost',
|
||||
size?: 'default' | 'large',
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
onClick?: () => void,
|
||||
}
|
||||
|
||||
const ButtonIconBuy = memo(({ type = 'light', size = 'default', loading = false, disabled, onClick }: Props) => {
|
||||
return (
|
||||
<ButtonCommon
|
||||
type={type}
|
||||
size={size}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
icon={<IconBuy />}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export default ButtonIconBuy
|
31
src/components/common/Footer/Footer.module.scss
Normal file
31
src/components/common/Footer/Footer.module.scss
Normal 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;
|
||||
}
|
||||
}
|
85
src/components/common/Footer/Footer.tsx
Normal file
85
src/components/common/Footer/Footer.tsx
Normal 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
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
@@ -1,5 +1,44 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.inputCommon {
|
||||
@apply custom-border-radius transition-all duration-200;
|
||||
.inputWrap {
|
||||
@apply flex items-center relative;
|
||||
.icon {
|
||||
@apply absolute;
|
||||
content: "";
|
||||
left: 1.6rem;
|
||||
margin-right: 1.6rem;
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
.icon + .inputCommon {
|
||||
padding-left: 4.8rem;
|
||||
}
|
||||
.inputCommon {
|
||||
@apply block w-full transition-all duration-200 rounded;
|
||||
padding: 1.2rem 1.6rem;
|
||||
border: 1px solid var(--border-line);
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
border: 1px solid var(--primary);
|
||||
@apply shadow-md;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@apply text-label;
|
||||
}
|
||||
|
||||
&.custom {
|
||||
@apply custom-border-radius;
|
||||
border: 1px solid transparent;
|
||||
background: var(--gray);
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,17 +5,19 @@ import s from './InputCommon.module.scss';
|
||||
type Ref = {
|
||||
focus: () => void
|
||||
} | null;
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
value?: string | number,
|
||||
placeholder?: string,
|
||||
type?: 'text' | 'number',
|
||||
styleType?: 'default' | 'custom',
|
||||
icon?: React.ReactNode,
|
||||
onChange?: (value: string | number) => void,
|
||||
onEnter?: (value: string | number) => void,
|
||||
}
|
||||
|
||||
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, onChange, onEnter }: Props, ref) => {
|
||||
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleType = 'default', icon,
|
||||
onChange, onEnter }: Props, ref) => {
|
||||
const inputElementRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@@ -36,15 +38,20 @@ const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, onChange
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={inputElementRef}
|
||||
value={value}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={s.inputCommon}
|
||||
/>
|
||||
<div className={s.inputWrap}>
|
||||
{
|
||||
icon && <span className={s.icon}>{icon}</span>
|
||||
}
|
||||
<input
|
||||
ref={inputElementRef}
|
||||
value={value}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={`${s.inputCommon} ${s[styleType]}`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
})
|
||||
|
22
src/components/common/InputSearch/InputSearch.tsx
Normal file
22
src/components/common/InputSearch/InputSearch.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { IconSearch } from 'src/components/icons';
|
||||
import { LANGUAGE } from 'src/utils/language.utils';
|
||||
import { Inputcommon } from '..';
|
||||
|
||||
interface Props {
|
||||
onChange?: (value: string | number) => void,
|
||||
onEnter?: (value: string | number) => void,
|
||||
}
|
||||
|
||||
const InputSearch = ({ onChange, onEnter }: Props) => {
|
||||
return (
|
||||
<Inputcommon placeholder={LANGUAGE.PLACE_HOLDER.SEARCH}
|
||||
styleType='custom'
|
||||
icon={<IconSearch />}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default InputSearch
|
8
src/components/common/Layout/Layout.module.scss
Normal file
8
src/components/common/Layout/Layout.module.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.mainLayout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
> main {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
||||
)
|
||||
|
90
src/components/common/MenuDropdown/MenuDropdown.module.scss
Normal file
90
src/components/common/MenuDropdown/MenuDropdown.module.scss
Normal 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;
|
||||
}
|
||||
}
|
42
src/components/common/MenuDropdown/MenuDropdown.tsx
Normal file
42
src/components/common/MenuDropdown/MenuDropdown.tsx
Normal 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;
|
@@ -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;
|
||||
}
|
16
src/components/common/NotiMessage/NotiMessage.tsx
Normal file
16
src/components/common/NotiMessage/NotiMessage.tsx
Normal 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
|
@@ -8,3 +8,9 @@ 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 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'
|
||||
|
Reference in New Issue
Block a user