mirror of
https://github.com/vercel/commerce.git
synced 2025-07-29 05:01:22 +00:00
Merge branch 'common' of github.com:KieIO/grocery-vercel-commerce into m4-datnguyen
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.banner {
|
||||
@apply bg-primary-light custom-border-radius-lg overflow-hidden;
|
||||
@screen md {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
&.large {
|
||||
margin-bottom: 2.8rem;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,47 +1,24 @@
|
||||
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'
|
||||
import CarouselCommon from '../CarouselCommon/CarouselCommon'
|
||||
import BannerItem, { BannerItemProps } from './BannerItem/BannerItem'
|
||||
|
||||
interface Props {
|
||||
imgLink: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
buttonLabel?: string,
|
||||
linkButton?: string,
|
||||
size?: 'small' | 'large',
|
||||
data: BannerItemProps[],
|
||||
}
|
||||
|
||||
const Banner = memo(({ imgLink, title, subtitle, buttonLabel = LANGUAGE.BUTTON_LABEL.SHOP_NOW, linkButton = ROUTE.HOME, size = 'large' }: Props) => {
|
||||
const option = {
|
||||
slidesPerView: 1,
|
||||
breakpoints: {}
|
||||
}
|
||||
const Banner = memo(({ data }: 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>
|
||||
<CarouselCommon<BannerItemProps>
|
||||
data={data}
|
||||
itemKey="banner"
|
||||
Component={BannerItem}
|
||||
option={option}
|
||||
isDot = {true}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
|
@@ -0,0 +1,52 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.bannerItem {
|
||||
@apply bg-primary-light custom-border-radius-lg overflow-hidden;
|
||||
@screen md {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
&.large {
|
||||
margin-bottom: 2.8rem;
|
||||
.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/BannerItem/BannerItem.tsx
Normal file
48
src/components/common/Banner/BannerItem/BannerItem.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 './BannerItem.module.scss'
|
||||
|
||||
export interface BannerItemProps {
|
||||
imgLink: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
buttonLabel?: string,
|
||||
linkButton?: string,
|
||||
size?: 'small' | 'large',
|
||||
}
|
||||
|
||||
const BannerItem = memo(({ imgLink, title, subtitle, buttonLabel = LANGUAGE.BUTTON_LABEL.SHOP_NOW, linkButton = ROUTE.HOME, size = 'large' }: BannerItemProps) => {
|
||||
return (
|
||||
<div className={classNames({
|
||||
[s.bannerItem]: 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 BannerItem
|
@@ -3,6 +3,7 @@ import React, { memo, useEffect, useState } from 'react'
|
||||
import { useModalCommon } from 'src/components/hooks/useModalCommon'
|
||||
import { isMobile } from 'src/utils/funtion.utils'
|
||||
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
|
||||
import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo'
|
||||
import HeaderHighLight from './components/HeaderHighLight/HeaderHighLight'
|
||||
import HeaderMenu from './components/HeaderMenu/HeaderMenu'
|
||||
import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu'
|
||||
@@ -13,6 +14,7 @@ import s from './Header.module.scss'
|
||||
const Header = memo(() => {
|
||||
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
|
||||
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
|
||||
const { visible: visibleModalInfo, closeModal: closeModalInfo, openModal: openModalInfo } = useModalCommon({ initialValue: false })
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
@@ -35,12 +37,15 @@ const Header = memo(() => {
|
||||
<header className={classNames({ [s.header]: true, [s.full]: isFullHeader })}>
|
||||
<HeaderHighLight isShow={isFullHeader} />
|
||||
<div className={s.menu}>
|
||||
<HeaderMenu isFull={isFullHeader} openModalAuthen={openModalAuthen} />
|
||||
<HeaderMenu isFull={isFullHeader}
|
||||
openModalAuthen={openModalAuthen}
|
||||
openModalInfo={openModalInfo} />
|
||||
<HeaderSubMenu isShow={isFullHeader} />
|
||||
</div>
|
||||
</header>
|
||||
<HeaderSubMenuMobile />
|
||||
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
|
||||
<ModalCreateUserInfo demoVisible={visibleModalInfo} demoCloseModal={closeModalInfo}/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
@@ -11,14 +11,19 @@ interface Props {
|
||||
children?: any,
|
||||
isFull: boolean,
|
||||
openModalAuthen: () => void,
|
||||
openModalInfo: () => void,
|
||||
}
|
||||
|
||||
const HeaderMenu = memo(({ isFull, openModalAuthen }: Props) => {
|
||||
const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo }: Props) => {
|
||||
const optionMenu = useMemo(() => [
|
||||
{
|
||||
onClick: openModalAuthen,
|
||||
name: 'Login (Demo)',
|
||||
},
|
||||
{
|
||||
onClick: openModalInfo,
|
||||
name: 'Create User Info (Demo)',
|
||||
},
|
||||
{
|
||||
link: ROUTE.ACCOUNT,
|
||||
name: 'Account',
|
||||
|
@@ -1,51 +1,94 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.inputWrap {
|
||||
@apply flex items-center relative;
|
||||
.icon {
|
||||
@apply absolute;
|
||||
content: "";
|
||||
left: 1.6rem;
|
||||
margin-right: 1.6rem;
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
.inputInner {
|
||||
@apply flex items-center relative;
|
||||
.icon {
|
||||
@apply absolute flex justify-center items-center;
|
||||
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;
|
||||
.icon + .inputCommon {
|
||||
padding-left: 4.8rem;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@apply text-label;
|
||||
}
|
||||
|
||||
&.custom {
|
||||
@apply custom-border-radius;
|
||||
border: 1px solid transparent;
|
||||
background: var(--gray);
|
||||
.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);
|
||||
}
|
||||
}
|
||||
&.bgTransparent {
|
||||
background: rgb(227, 242, 233, 0.3);
|
||||
color: var(--white);
|
||||
&::placeholder {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.bgTransparent {
|
||||
background: rgb(227, 242, 233, 0.3);
|
||||
color: var(--white);
|
||||
&::placeholder {
|
||||
color: var(--white);
|
||||
|
||||
&.preserve {
|
||||
@apply flex-row-reverse;
|
||||
.icon {
|
||||
left: unset;
|
||||
right: 1.6rem;
|
||||
margin-left: 1.6rem;
|
||||
margin-right: 0;
|
||||
svg path {
|
||||
fill: var(--text-label);
|
||||
}
|
||||
}
|
||||
.icon + .inputCommon {
|
||||
padding-left: 1.6rem;
|
||||
padding-right: 4.8rem;
|
||||
}
|
||||
}
|
||||
&.success {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--negative);
|
||||
}
|
||||
}
|
||||
input {
|
||||
border-color: var(--negative) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.errorMessage {
|
||||
@apply caption;
|
||||
color: var(--negative);
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import { IconCheck, IconError, IconPassword, IconPasswordCross } from 'src/components/icons';
|
||||
import { KEY } from 'src/utils/constanst.utils';
|
||||
import s from './InputCommon.module.scss';
|
||||
|
||||
@@ -14,14 +15,29 @@ interface Props {
|
||||
styleType?: 'default' | 'custom',
|
||||
backgroundTransparent?: boolean,
|
||||
icon?: React.ReactNode,
|
||||
isIconSuffix?: boolean,
|
||||
isShowIconSuccess?: boolean,
|
||||
error?: string,
|
||||
onChange?: (value: string | number) => void,
|
||||
onEnter?: (value: string | number) => void,
|
||||
}
|
||||
|
||||
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleType = 'default', icon, backgroundTransparent = false,
|
||||
isIconSuffix, isShowIconSuccess, error,
|
||||
onChange, onEnter }: Props, ref) => {
|
||||
const inputElementRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const iconElement = useMemo(() => {
|
||||
if (error) {
|
||||
return <span className={s.icon}><IconError /> </span>
|
||||
} else if (isShowIconSuccess) {
|
||||
return <span className={s.icon}><IconCheck /> </span>
|
||||
} else if (icon) {
|
||||
return <span className={s.icon}>{icon} </span>
|
||||
}
|
||||
return <></>
|
||||
}, [icon, error, isShowIconSuccess])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => {
|
||||
inputElementRef.current?.focus();
|
||||
@@ -44,23 +60,33 @@ const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleTyp
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.inputWrap}>
|
||||
<div className={classNames({
|
||||
[s.inputWrap]: true,
|
||||
})}>
|
||||
<div className={classNames({
|
||||
[s.inputInner]: true,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.success]: isShowIconSuccess,
|
||||
[s.error]: !!error,
|
||||
})}>
|
||||
{iconElement}
|
||||
<input
|
||||
ref={inputElementRef}
|
||||
value={value}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={classNames({
|
||||
[s.inputCommon]: true,
|
||||
[s[styleType]]: true,
|
||||
[s.bgTransparent]: backgroundTransparent
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
icon && <span className={s.icon}>{icon}</span>
|
||||
error && <div className={s.errorMessage}>{error}</div>
|
||||
}
|
||||
<input
|
||||
ref={inputElementRef}
|
||||
value={value}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={classNames({
|
||||
[s.inputCommon]: true,
|
||||
[s[styleType]]: true,
|
||||
[s.bgTransparent]: backgroundTransparent
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
@@ -0,0 +1,10 @@
|
||||
.iconPassword {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--text-active);
|
||||
}
|
||||
}
|
40
src/components/common/InputPassword/InputPassword.tsx
Normal file
40
src/components/common/InputPassword/InputPassword.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IconPassword, IconPasswordCross } from 'src/components/icons';
|
||||
import { Inputcommon } from '..';
|
||||
import s from './InputPassword.module.scss';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
value?: string | number,
|
||||
placeholder?: string,
|
||||
styleType?: 'default' | 'custom',
|
||||
error?: string,
|
||||
onChange?: (value: string | number) => void,
|
||||
onEnter?: (value: string | number) => void,
|
||||
}
|
||||
|
||||
const InputPassword = ({ value, placeholder, styleType = 'default', error,
|
||||
onChange, onEnter }: Props) => {
|
||||
const [isShowPassword, setIsShowPassword] = useState<boolean>(false)
|
||||
const toggleShowPassword = () => {
|
||||
setIsShowPassword(!isShowPassword)
|
||||
}
|
||||
|
||||
return (
|
||||
<Inputcommon
|
||||
value={value}
|
||||
type={isShowPassword ? 'text' : 'password'}
|
||||
styleType={styleType}
|
||||
error={error}
|
||||
placeholder={placeholder}
|
||||
icon={<button className={s.iconPassword} onClick={toggleShowPassword}>
|
||||
{isShowPassword ? <IconPassword /> : <IconPasswordCross />}
|
||||
</button>}
|
||||
isIconSuffix={true}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default InputPassword
|
@@ -1,17 +1,12 @@
|
||||
@import '../../../../styles/utilities';
|
||||
|
||||
.formAuthen {
|
||||
@apply bg-white w-full;
|
||||
@apply bg-white w-full u-form;
|
||||
.inner {
|
||||
@screen md {
|
||||
max-width: 52rem;
|
||||
width: 60rem;
|
||||
margin: auto;
|
||||
}
|
||||
.body {
|
||||
> div {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.others {
|
||||
@apply font-bold text-center;
|
||||
margin-top: 4rem;
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import Link from 'next/link'
|
||||
import React, { useRef, useEffect } from 'react'
|
||||
import { Inputcommon, ButtonCommon } from 'src/components/common'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { ButtonCommon, Inputcommon } from 'src/components/common'
|
||||
import InputPassword from 'src/components/common/InputPassword/InputPassword'
|
||||
import { ROUTE } from 'src/utils/constanst.utils'
|
||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||
import s from '../FormAuthen.module.scss'
|
||||
import styles from './FormLogin.module.scss'
|
||||
import classNames from 'classnames'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
import s from '../FormAuthen.module.scss'
|
||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||
import styles from './FormLogin.module.scss'
|
||||
|
||||
interface Props {
|
||||
isHide: boolean,
|
||||
@@ -23,14 +23,13 @@ const FormLogin = ({ onSwitch, isHide }: Props) => {
|
||||
}, [isHide])
|
||||
|
||||
return (
|
||||
<section className={classNames({
|
||||
[s.formAuthen]: true,
|
||||
// [styles.hide]: isHide
|
||||
})}>
|
||||
<section className={s.formAuthen}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef} />
|
||||
<Inputcommon placeholder='Password' type='password' />
|
||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef} />
|
||||
{/* <Inputcommon placeholder='Email Address' type='email' ref={emailRef}
|
||||
isShowIconSuccess={true} isIconSuffix={true} /> */}
|
||||
<InputPassword placeholder='Password'/>
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<Link href={ROUTE.FORGOT_PASSWORD}>
|
||||
|
@@ -24,12 +24,11 @@ const FormRegister = ({ onSwitch, isHide }: Props) => {
|
||||
<section className={classNames({
|
||||
[s.formAuthen]: true,
|
||||
[styles.formRegister]: true,
|
||||
// [styles.hide]: isHide
|
||||
})}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef}/>
|
||||
<Inputcommon placeholder='Password' type='password' />
|
||||
<InputPassword placeholder='Password'/>
|
||||
<div className={styles.passwordNote}>
|
||||
Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.
|
||||
</div>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
@apply flex justify-center items-center min-h-screen;
|
||||
.modal{
|
||||
@apply inline-block align-bottom bg-white relative;
|
||||
max-width: 60rem;
|
||||
max-width: 66.4rem;
|
||||
padding: 3.2rem;
|
||||
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.24);
|
||||
border-radius: 1.2rem;
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
.title{
|
||||
@apply font-heading heading-3;
|
||||
padding: 0 0.8rem 0 0.8rem;
|
||||
padding: 0 1.6rem 0 0.8rem;
|
||||
}
|
||||
.close{
|
||||
@apply absolute;
|
||||
|
@@ -0,0 +1,19 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.formUserInfo {
|
||||
@apply u-form;
|
||||
.inner {
|
||||
@screen md {
|
||||
width: 60rem;
|
||||
margin: auto;
|
||||
}
|
||||
.bottom {
|
||||
@apply grid grid-cols-2;
|
||||
margin-top: 4rem;
|
||||
grid-gap: 1.6rem;
|
||||
> button {
|
||||
@apply w-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import React, { useRef } from 'react';
|
||||
import { useModalCommon } from 'src/components/hooks/useModalCommon';
|
||||
import { CustomInputCommon } from 'src/utils/type.utils';
|
||||
import { Inputcommon } from '..';
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon';
|
||||
import ModalCommon from '../ModalCommon/ModalCommon';
|
||||
import s from './ModalCreateUserInfo.module.scss';
|
||||
|
||||
// todo: remove
|
||||
interface Props {
|
||||
demoVisible: boolean,
|
||||
demoCloseModal: () => void,
|
||||
}
|
||||
|
||||
|
||||
const ModalCreateUserInfo = ({ demoVisible: visible, demoCloseModal: closeModal }: Props) => {
|
||||
// const { visible, closeModal } = useModalCommon({ initialValue: false})
|
||||
const firstInputRef = useRef<CustomInputCommon>(null)
|
||||
|
||||
return (
|
||||
<ModalCommon visible={visible} onClose={closeModal} title='Enter your Information'>
|
||||
<div className={s.formUserInfo}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Street Address' ref={firstInputRef} />
|
||||
<Inputcommon placeholder='City' />
|
||||
<div className={s.line}>
|
||||
{/* todo: select, not input */}
|
||||
<Inputcommon placeholder='State' />
|
||||
<Inputcommon placeholder='Zip code' />
|
||||
</div>
|
||||
<Inputcommon placeholder='Phone (delivery contact)' />
|
||||
</div>
|
||||
<div className={s.bottom}>
|
||||
<ButtonCommon size='large' onClick={closeModal} type='light'>Skip</ButtonCommon>
|
||||
<ButtonCommon size='large'>Submit</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalCommon>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalCreateUserInfo;
|
@@ -1,32 +1,69 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.select{
|
||||
@apply rounded-lg border-solid;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1.2rem 1.6rem;
|
||||
background-color: var(--white);
|
||||
&.base{
|
||||
width: 18.6rem;
|
||||
height: 4.8rem;
|
||||
width: 20.6rem;
|
||||
.selectTrigger{
|
||||
width: 20.6rem;
|
||||
padding: 1.2rem 1.6rem;
|
||||
}
|
||||
}
|
||||
&.large{
|
||||
width: 34.25rem;
|
||||
height: 5.6rem;
|
||||
.selectTrigger{
|
||||
width: 34.25rem;
|
||||
padding: 1.6rem 1.6rem;
|
||||
}
|
||||
}
|
||||
&.default{
|
||||
@apply border;
|
||||
.selectTrigger{
|
||||
@apply border-solid border border-current;
|
||||
}
|
||||
}
|
||||
&.custom{
|
||||
.selectTrigger{
|
||||
@apply border-2;
|
||||
border-color: var(--border-line);
|
||||
color: var(--text-label);
|
||||
}
|
||||
}
|
||||
&.isActive{
|
||||
.selectOptionWrapper{
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.selectTrigger{
|
||||
@apply outline-none flex justify-between;
|
||||
color: var(--text-active);
|
||||
border-radius: 0.8rem;
|
||||
|
||||
}
|
||||
.selectOptionWrapper{
|
||||
@apply outline-none hidden z-10 absolute;
|
||||
border-radius: 0.8rem;
|
||||
background-color: var(--white);
|
||||
padding: 0.4rem 0rem 0.4rem 0rem;
|
||||
margin-top: 0.6rem;
|
||||
&.base{
|
||||
width: 20.6rem;
|
||||
}
|
||||
&.large{
|
||||
width: 34.25rem;
|
||||
}
|
||||
&.default{
|
||||
@apply border-solid border border-current;
|
||||
}
|
||||
&.custom{
|
||||
@apply border-2;
|
||||
border-color: var(--border-line);
|
||||
color: var(--text-label);
|
||||
}
|
||||
.option{
|
||||
&:hover{
|
||||
background-color: black;
|
||||
}
|
||||
&.active{
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -1,26 +1,75 @@
|
||||
import s from './SelectCommon.module.scss'
|
||||
import classNames from 'classnames'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { IconVectorDown } from 'src/components/icons'
|
||||
import SelectOption from './SelectOption/SelectOption'
|
||||
|
||||
interface Props {
|
||||
placeHolder? : string,
|
||||
children? : React.ReactNode,
|
||||
size?: 'base' | 'large',
|
||||
type?: 'default' | 'custom',
|
||||
option: {name: string}[],
|
||||
}
|
||||
|
||||
const SelectCommon = ({ type = 'default', size = 'base', option, placeHolder }: Props) => {
|
||||
return(
|
||||
<select className={classNames({
|
||||
[s.select] : true,
|
||||
[s[type]]: !!type,
|
||||
[s[size]]: !!size,
|
||||
})}
|
||||
>
|
||||
<option disabled selected hidden>{placeHolder}</option>
|
||||
{
|
||||
option.map(item => <option className={s.option} value={item.name}> {item.name} </option>)
|
||||
const SelectCommon = ({ type = 'default', size = 'base', option, children }: Props) => {
|
||||
const [isActive, setActive] = useState(false)
|
||||
const [selectedName, setSelectedName] = useState(children)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
const { target } = event;
|
||||
if (!ref?.current || ref?.current.contains(target as Node)) {
|
||||
return
|
||||
}
|
||||
</select>
|
||||
else{
|
||||
setActive(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', handleClick)
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick)
|
||||
}
|
||||
}, [ref])
|
||||
|
||||
const changeActiveStatus = () => {
|
||||
setActive(!isActive)
|
||||
}
|
||||
|
||||
const changeSelectedName = (item:string) => {
|
||||
setSelectedName(item)
|
||||
}
|
||||
return(
|
||||
<>
|
||||
<div className={classNames({
|
||||
[s.select] : true,
|
||||
[s[size]] : !!size,
|
||||
[s[type]] : !!type,
|
||||
[s.isActive] : isActive,
|
||||
})}
|
||||
onClick = { changeActiveStatus }
|
||||
ref = {ref}
|
||||
>
|
||||
<div className={classNames({
|
||||
[s.selectTrigger] : true,
|
||||
|
||||
})}
|
||||
>{selectedName}<IconVectorDown /></div>
|
||||
|
||||
<div className={classNames({
|
||||
[s.selectOptionWrapper] : true,
|
||||
[s[type]] : !!type,
|
||||
[s[size]] : !!size,
|
||||
})}
|
||||
>
|
||||
{
|
||||
option.map(item =>
|
||||
<SelectOption itemName={item.name} onClick={changeSelectedName} size={size} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,17 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.selectOption {
|
||||
@apply outline-none;
|
||||
background-color: var(--white);
|
||||
&.base{
|
||||
width: 20.4rem;
|
||||
padding: 0.8rem 1.6rem;
|
||||
}
|
||||
&.large{
|
||||
width: 33.75rem;
|
||||
padding: 0.8rem 1.6rem;
|
||||
}
|
||||
&:hover{
|
||||
background-color: var(--gray);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import s from './SelectOption.module.scss'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props{
|
||||
onClick: (value: string) => void,
|
||||
itemName: string,
|
||||
size: 'base' | 'large',
|
||||
}
|
||||
|
||||
const SelectOption = ({onClick, itemName, size}: Props) => {
|
||||
|
||||
const changeName = () => {
|
||||
onClick(itemName)
|
||||
}
|
||||
return(
|
||||
<div className={classNames({
|
||||
[s.selectOption] : true,
|
||||
[s[size]] : !!size,
|
||||
})}
|
||||
onClick = {changeName}
|
||||
>{itemName}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectOption
|
@@ -12,6 +12,7 @@ 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 InputPassword} from './InputPassword/InputPassword'
|
||||
export { default as CheckboxCommon} from './CheckboxCommon/CheckboxCommon'
|
||||
export { default as Author} from './Author/Author'
|
||||
export { default as DateTime} from './DateTime/DateTime'
|
||||
@@ -31,3 +32,4 @@ export { default as ModalCommon} from './ModalCommon/ModalCommon'
|
||||
export { default as ModalConfirm} from "./ModalConfirm/ModalConfirm"
|
||||
export { default as ModalInfo} from "./ModalInfo/ModalInfo"
|
||||
export { default as ProductList} from "./ProductList/ProductList"
|
||||
export { default as ModalCreateUserInfo} from './ModalCreateUserInfo/ModalCreateUserInfo'
|
||||
|
Reference in New Issue
Block a user