Merge pull request 'refactor/components' (#15) from refactor/components into develop

Reviewed-on: #15
This commit is contained in:
Henrique Santos Santana 2023-01-09 03:45:13 +00:00
commit 9be5b1abf3
57 changed files with 1298 additions and 789 deletions

37
package-lock.json generated
View File

@ -9,11 +9,13 @@
"version": "0.1.0",
"dependencies": {
"@types/react-router-dom": "^5.3.3",
"@types/react-text-mask": "^5.4.11",
"formik": "^2.2.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.6.1",
"react-scripts": "5.0.1",
"react-text-mask": "^5.5.0",
"sass": "^1.57.1",
"web-vitals": "^2.1.4",
"yup": "^0.32.11"
@ -3925,6 +3927,14 @@
"@types/react-router": "*"
}
},
"node_modules/@types/react-text-mask": {
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/@types/react-text-mask/-/react-text-mask-5.4.11.tgz",
"integrity": "sha512-DIJ3/dS4jd7NK3lEgsOwcgpp+ZlVrNJEiUDRayZRE/PNMbV/nLWmOKGdL0BUS29hnx0CDgITgPudKx0BgbF5fA==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@ -14202,6 +14212,17 @@
}
}
},
"node_modules/react-text-mask": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.5.0.tgz",
"integrity": "sha512-SLJlJQxa0uonMXsnXRpv5abIepGmHz77ylQcra0GNd7Jtk4Wj2Mtp85uGQHv1avba2uI8ZvRpIEQPpJKsqRGYw==",
"dependencies": {
"prop-types": "^15.5.6"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -19723,6 +19744,14 @@
"@types/react-router": "*"
}
},
"@types/react-text-mask": {
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/@types/react-text-mask/-/react-text-mask-5.4.11.tgz",
"integrity": "sha512-DIJ3/dS4jd7NK3lEgsOwcgpp+ZlVrNJEiUDRayZRE/PNMbV/nLWmOKGdL0BUS29hnx0CDgITgPudKx0BgbF5fA==",
"requires": {
"@types/react": "*"
}
},
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@ -27022,6 +27051,14 @@
"workbox-webpack-plugin": "^6.4.1"
}
},
"react-text-mask": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.5.0.tgz",
"integrity": "sha512-SLJlJQxa0uonMXsnXRpv5abIepGmHz77ylQcra0GNd7Jtk4Wj2Mtp85uGQHv1avba2uI8ZvRpIEQPpJKsqRGYw==",
"requires": {
"prop-types": "^15.5.6"
}
},
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View File

@ -9,11 +9,13 @@
},
"dependencies": {
"@types/react-router-dom": "^5.3.3",
"@types/react-text-mask": "^5.4.11",
"formik": "^2.2.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.6.1",
"react-scripts": "5.0.1",
"react-text-mask": "^5.5.0",
"sass": "^1.57.1",
"web-vitals": "^2.1.4",
"yup": "^0.32.11"

View File

@ -0,0 +1,11 @@
<svg width="66" height="66" viewBox="0 0 66 66" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3751_658)">
<path d="M33.0083 0H32.9917C14.7964 0 0 14.8005 0 33C0 40.2188 2.3265 46.9095 6.28237 52.3421L2.16975 64.6016L14.8541 60.5468C20.0723 64.0035 26.2969 66 33.0083 66C51.2036 66 66 51.1954 66 33C66 14.8046 51.2036 0 33.0083 0Z" fill="#4CAF50"/>
<path d="M52.21 46.6001C51.4139 48.8483 48.2542 50.7128 45.7338 51.2573C44.0095 51.6244 41.7573 51.9173 34.1755 48.774C24.4777 44.7563 18.2324 34.9016 17.7457 34.2623C17.2795 33.6229 13.8269 29.0441 13.8269 24.3086C13.8269 19.5731 16.2318 17.2673 17.2012 16.2773C17.9973 15.4646 19.3132 15.0934 20.5754 15.0934C20.9838 15.0934 21.3509 15.114 21.6809 15.1305C22.6503 15.1718 23.137 15.2295 23.7764 16.7599C24.5725 18.678 26.5113 23.4135 26.7423 23.9003C26.9774 24.387 27.2125 25.047 26.8825 25.6864C26.5732 26.3464 26.3009 26.6393 25.8142 27.2003C25.3274 27.7613 24.8654 28.1903 24.3787 28.7925C23.9332 29.3164 23.4299 29.8774 23.9909 30.8468C24.5519 31.7955 26.4907 34.9594 29.3452 37.5004C33.0288 40.7798 36.0153 41.8275 37.0837 42.273C37.8798 42.603 38.8285 42.5246 39.4102 41.9059C40.1485 41.1098 41.0602 39.7898 41.9883 38.4904C42.6483 37.5581 43.4815 37.4426 44.356 37.7726C45.247 38.082 49.9619 40.4126 50.9313 40.8953C51.9007 41.382 52.54 41.613 52.7752 42.0214C53.0062 42.4298 53.0062 44.3479 52.21 46.6001Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_3751_658">
<rect width="66" height="66" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,3 @@
<svg width="25" height="13" viewBox="0 0 25 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.7428 11.3959C23.741 11.6959 23.6177 11.9953 23.373 12.226L23.3728 12.2261C22.8668 12.7035 22.037 12.7035 21.5311 12.2261L21.5232 12.2345L21.5311 12.2261L12.3457 3.5599L12.0712 3.30091L11.7967 3.5599L2.61178 12.2258C2.10549 12.7033 1.27571 12.7032 0.769767 12.2259L0.769708 12.2258C0.525704 11.9957 0.402467 11.6972 0.400037 11.398C0.401876 11.0981 0.525111 10.7986 0.769782 10.5678L0.769785 10.5678L11.1506 0.773762C11.4013 0.537307 11.7333 0.415846 12.0712 0.415846C12.4093 0.415846 12.7415 0.537566 12.9922 0.773969L23.3728 10.568L23.3729 10.5681C23.6171 10.7983 23.7403 11.0968 23.7428 11.3959Z" fill="white" stroke="white" stroke-width="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 764 B

View File

@ -0,0 +1,10 @@
import { AnchorHTMLAttributes, ReactNode } from 'react'
import { LinkProps, To } from 'react-router-dom'
export interface CustomLinkProps
extends AnchorHTMLAttributes<HTMLAnchorElement>,
LinkProps {
children: ReactNode | ReactNode[]
href?: string
to?: To | any
}

View File

@ -0,0 +1,18 @@
import { CustomLinkProps } from './@types'
import { Link } from 'react-router-dom'
export function CustomLink({ children, href, to, ...props }: CustomLinkProps) {
return (
<>
{href ? (
<a href={href} {...props}>
{children}
</a>
) : (
<Link to={to} {...props}>
{children}
</Link>
)}
</>
)
}

View File

@ -0,0 +1,7 @@
import { HTMLAttributes } from 'react'
export interface IconProps extends HTMLAttributes<HTMLImageElement> {
src: any
alt: string
$container?: HTMLAttributes<HTMLSpanElement>
}

View File

@ -0,0 +1,10 @@
/* eslint-disable jsx-a11y/alt-text */
import { IconProps } from './@types'
export function Icon({ $container, src, alt, ...props }: IconProps) {
return (
<span {...$container}>
<img src={src} alt={alt} {...props} />
</span>
)
}

View File

@ -0,0 +1,5 @@
import { HTMLAttributes, ReactNode } from 'react'
export interface ItemProps extends HTMLAttributes<HTMLLIElement> {
children: ReactNode | ReactNode[]
}

View File

@ -0,0 +1,5 @@
import { ItemProps } from './@types'
export function Item({ children, ...props }: ItemProps) {
return <li {...props}>{children}</li>
}

View File

@ -0,0 +1,15 @@
import { ButtonHTMLAttributes } from 'react'
import { Icon } from '../../Atoms/Icon'
interface ButtonIconProps extends ButtonHTMLAttributes<HTMLButtonElement> {
src: any
alt: string
}
export function ButtonIcon({ src, alt, ...props }: ButtonIconProps) {
return (
<button {...props}>
<Icon className="btn-icon icon" src={src} alt={alt} />
</button>
)
}

View File

@ -0,0 +1,12 @@
import { AnchorHTMLAttributes, ReactNode } from 'react'
import { To } from 'react-router-dom'
import { CustomLinkProps } from '../../Atoms/CustomLink/@types'
import { ItemProps } from '../../Atoms/Item/@types'
export interface ItemListProps
extends AnchorHTMLAttributes<HTMLAnchorElement>,
CustomLinkProps {
to?: To
children?: ReactNode | ReactNode[]
$container?: ItemProps
}

View File

@ -0,0 +1,11 @@
import { Item } from '../../Atoms/Item'
import { CustomLink } from '../../Atoms/CustomLink'
import { ItemListProps } from './@types'
export function ItemList({ $container, children, ...props }: ItemListProps) {
return (
<Item {...$container}>
<CustomLink {...props}>{children}</CustomLink>
</Item>
)
}

View File

@ -1,14 +0,0 @@
import { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from 'react'
interface ItemProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
children: ReactNode | ReactNode[]
$container?: HTMLAttributes<HTMLLIElement>
}
export function Item({ children, $container, ...props }: ItemProps) {
return (
<li {...$container}>
<a {...props}>{children}</a>
</li>
)
}

View File

@ -0,0 +1,53 @@
import { ErrorMessage } from 'formik'
import { InputHTMLAttributes } from 'react'
import { CustomInput, CustomMaskInput } from './__Input'
interface FormGroupProps extends InputHTMLAttributes<HTMLInputElement> {
error: any
name: string | any
masked: boolean
mask?: any
label: string | any
}
export function FormGroup({
error,
masked,
mask,
label,
name,
...props
}: FormGroupProps) {
return (
<div className="form__group">
<label className="form__label" htmlFor={name}>
{label}
</label>
<div className="form__content">
<ErrorMessage
className="error-message"
component={'span'}
name={name}
/>
{masked ? (
<CustomMaskInput
mask={mask}
id={name}
name={name}
className={`form__input ${error}`}
{...props}
/>
) : (
<CustomInput
name={name}
className={`form__input ${error}`}
{...props}
/>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,26 @@
import { Field } from 'formik'
import { InputHTMLAttributes } from 'react'
import MaskedInput, { MaskedInputProps } from 'react-text-mask'
interface CustomInputProps extends InputHTMLAttributes<HTMLInputElement> {}
export function CustomInput({ name, ...props }: CustomInputProps) {
return <Field id={name} name={name} {...props} />
}
interface CustomMaskInputProps
extends InputHTMLAttributes<HTMLInputElement>,
Omit<MaskedInputProps, 'mask'> {
mask?: any
name: string
}
export function CustomMaskInput({ name, ...props }: CustomMaskInputProps) {
return (
<Field id={name} name={name}>
{({ field }: any) => {
return <MaskedInput {...field} {...props} id={name} name={name} />
}}
</Field>
)
}

View File

@ -1,30 +0,0 @@
import { ErrorMessage, Field } from 'formik'
import { InputHTMLAttributes } from 'react'
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
name: string
label: string
error?: any
}
export function Input({ label, name, error, ...props }: InputProps) {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<div className="form-group-content">
<ErrorMessage
className="error-message"
component={'span'}
name={name}
/>
<Field
id={name}
name={name}
className={`form-input ${error}`}
{...props}
/>
</div>
</div>
)
}

View File

@ -3,21 +3,21 @@
height: 100%;
margin-bottom: 69.56px;
}
.form {
fieldset {
width: 100%;
border: none;
&__form {
.form__container {
width: 100%;
border: none;
}
}
}
.form :global {
.form-group {
.form__group {
margin-bottom: 12px;
}
label {
.form__label {
display: flex;
align-items: center;
justify-content: space-between;
@ -32,7 +32,7 @@
}
}
.form-group-content {
.form__content {
position: relative;
width: 100%;
@ -56,7 +56,7 @@
}
}
.form-input {
.form__input {
display: block;
width: 100%;
@ -133,8 +133,32 @@
text-transform: uppercase;
transition: 200ms ease-in-out;
&:hover {
color: var(--clr-common-black);
background-color: var(--clr-primary-blue-500);
}
@media screen and (min-width: 2500px) {
height: 71px;
}
}
.form__success {
opacity: 0;
transition: 200ms;
color: var(--clr-common-green);
font-size: var(--txt-xs);
line-height: 14.06px;
margin-top: 12px;
@media screen and (min-width: 2500px) {
line-height: 28.13px;
}
}
.form__success.form__success-active {
opacity: 1;
}
}

View File

@ -1,12 +1,16 @@
import { Field, Form, Formik } from 'formik'
import { useMemo } from 'react'
import { Input } from './fragments/Input'
import { useEffect, useMemo, useState } from 'react'
/* import { Input } from './fragments/Input' */
import validadeShema from './schema/FormSchema'
import css from './index.module.scss'
import styles from './index.module.scss'
import { FormGroup } from './containers/_FormGroup'
export function Contact() {
const [isSubmiting, setIsSubmiting] = useState(false)
const initialValues = useMemo(() => {
return {
fullname: '',
@ -19,70 +23,131 @@ export function Contact() {
}
}, [])
useEffect(() => {
if (isSubmiting) {
setTimeout(() => {
setIsSubmiting(false)
}, 2000)
}
}, [isSubmiting])
return (
<section className={css.contact}>
<section className={styles['contact']}>
<div>
<h2 className="title">Preencha o formulário</h2>
<Formik
onSubmit={(values, e) => {
e.validateField('email')
e.resetForm({
isSubmitting: true,
})
setIsSubmiting(true)
}}
initialValues={initialValues}
validationSchema={validadeShema}
>
{({ errors, touched }) => {
return (
<Form className={css.form}>
<fieldset className={css['form-container']}>
<Input
<Form className={`${styles['contact__form']} ${styles['form']}`}>
<fieldset className={styles['form__container']}>
<FormGroup
label="Nome"
name="fullname"
type="text"
placeholder="Seu nome completo"
error={errors && touched && 'touched'}
masked={false}
/>
<Input
<FormGroup
label="Email"
type="email"
name="email"
placeholder="Seu e-mail"
error={errors && touched && 'touched'}
masked={false}
/>
<Input
<FormGroup
mask={[
/[0-9]/,
/\d/,
/\d/,
'.',
/[0-9]/,
/\d/,
/\d/,
'.',
/[0-9]/,
/\d/,
/\d/,
'-',
/[0-9]/,
/\d/,
]}
label="CPF"
type="text"
name="cpf"
placeholder="000.000.000-00"
error={errors && touched && 'touched'}
masked={true}
/>
<Input
<FormGroup
mask={[
/[0-3]/,
/[0-9]/,
'.',
/[0-1]/,
/[0-9]/,
'.',
/[0-9]/,
/[0-9]/,
/[0-9]/,
/[0-9]/,
]}
label="Data de Nascimento:"
type="text"
name="date"
placeholder="00.00.0000"
error={errors && touched && 'touched'}
masked={true}
/>
<Input
<FormGroup
mask={[
'(',
/[0-9]/,
/\d/,
')',
' ',
/[0-9]/,
/\d/,
/\d/,
/\d/,
/\d/,
'-',
/[0-9]/,
/\d/,
/\d/,
/\d/,
]}
label="Telefone:"
type="tel"
name="tel"
placeholder="(00) 00000-0000"
error={errors && touched && 'touched'}
masked={true}
/>
<Input
<FormGroup
label="Instagram"
type="text"
name="socials_instagram"
placeholder="@seuuser"
error={errors && touched && 'touched'}
masked={false}
/>
<div className="form-check">
@ -90,12 +155,20 @@ export function Contact() {
<span>*</span>
Declaro que li e aceito
</label>
<Field id="terms" name="terms" type="checkbox" />
<Field id="terms" name="terms" type="checkbox" required />
</div>
<button className={css.submit} type="submit">
<button className={styles['submit']} type="submit">
Cadastre-se
</button>
<p
className={`form__success ${
isSubmiting ? 'form__success-active' : ''
}`}
>
*Formulário enviado com sucesso!
</p>
</fieldset>
</Form>
)

View File

@ -4,12 +4,32 @@ const messages = {
required: '*Campo Obrigatório',
}
const emailRegexPattern = /^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/
const cpfRegexPattern = /^([0-9]){3}\.([0-9]){3}\.([0-9]){3}-([0-9]){2}$/
const telRegexPattern = /\(([0-9]{1,3})\) ([0-9]{5})-([0-9]{4})/
const dateRegexPattern =
/^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/
export default Yup.object().shape({
fullname: Yup.string().required(messages.required),
email: Yup.string().required(messages.required).email('Email ínvalido'),
cpf: Yup.string().required(messages.required),
tel: Yup.string().required(messages.required),
date: Yup.string().required(messages.required),
socials_instagram: Yup.string(),
fullname: Yup.string().trim().min(7).max(60).required(messages.required),
email: Yup.string()
.matches(emailRegexPattern, { message: 'Email ínvalido' })
.required(messages.required)
.email('Email ínvalido'),
cpf: Yup.string()
.matches(cpfRegexPattern, { message: 'CPF ínvalido' })
.required(messages.required),
tel: Yup.string()
.matches(telRegexPattern, { message: 'Número de Telefone ínvalido' })
.required(messages.required),
date: Yup.string()
.matches(dateRegexPattern, { message: 'Data de Nascimento ínvalido' })
.required(messages.required),
socials_instagram: Yup.string().matches(
/(?:^|[^\w])(?:@)([A-Za-z0-9_](?:(?:[A-Za-z0-9_]|(?:\.(?!\.))){0,28}(?:[A-Za-z0-9_]))?)/,
{
messages: 'Instagram ínvalido',
}
),
terms: Yup.boolean().required('*'),
})

View File

@ -0,0 +1,13 @@
import { useMemo } from 'react'
import { Breadcrumb } from '../../components/Molecules/Breadcrumb'
import styles from '../index.module.scss'
export function RouterBreadcrumb() {
let list = useMemo(() => [{ name: 'Introduction', href: '/' }], [])
return (
<div className={styles['breadcrumb-container']}>
<Breadcrumb className={styles['breadcrumb']} list={list} />
</div>
)
}

View File

@ -0,0 +1,37 @@
import { useEffect, useState } from 'react'
import { ItemList } from '../../components/Molecules/ItemList'
import whatsappImg from '../../assets/brands/svgs/whatsapp.svg'
import scrollTopImg from '../../assets/icons/scroll-top.svg'
import styles from '../index.module.scss'
export function ScrollFixed() {
const [isScrollTop, setIsScrollTop] = useState<boolean>(false)
useEffect(() => {
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
setIsScrollTop(true)
} else {
setIsScrollTop(false)
}
})
}, [])
return (
<ul className={styles['scroll__fixed']}>
<ItemList
className={styles['scroll__fixed-whatsapp']}
href="https://wa.me/254777123456"
>
<img src={whatsappImg} alt="" />
</ItemList>
{isScrollTop && (
<ItemList className={styles['scroll__fixed-top']} href={'#root'}>
<img src={scrollTopImg} alt="" />
</ItemList>
)}
</ul>
)
}

View File

@ -35,3 +35,63 @@ main :global {
width: function.fluid(2299.68px, 2500px);
}
}
.scroll__fixed {
position: fixed;
right: 16px;
bottom: 28px;
display: flex;
align-items: center;
flex-direction: column;
gap: 5px;
@media screen and (min-width: 1025px) {
bottom: 168px;
}
@media screen and (min-width: 2500px) {
bottom: 229.24px;
}
&-whatsapp,
&-top {
display: block;
width: 34px;
height: 34px;
@media screen and (min-width: 2500px) {
width: 66px;
height: 66px;
}
}
&-whatsapp {
img {
width: 100%;
height: 100%;
}
}
&-top {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--clr-gray-400);
border-radius: 100%;
img {
width: 12px;
height: 6.46px;
@media screen and (min-width: 1025px) {
width: 13px;
height: 7px;
}
@media screen and (min-width: 2500px) {
width: 24.24px;
height: 13px;
}
}
}
}

View File

@ -1,18 +1,15 @@
import { useMemo } from 'react'
import { Outlet, Route, Routes } from 'react-router-dom'
import { Breadcrumb } from '../modules/components/Breadcrumb'
import { Header } from '../template/Header'
import { Sidebar } from '../template/Sidebar'
import { About } from '../pages/Institutional/About'
import { Contact } from '../pages/Institutional/Contact'
import { Footer } from '../template/Footer'
import { Header } from '../template/Header'
import { Newsletter } from '../template/Newsletter'
import { Sidebar } from '../template/Sidebar'
import css from './index.module.scss'
import { RouterBreadcrumb } from './containers/_BreadCrumb'
import { ScrollFixed } from './containers/_ScrollFixed'
export function Router() {
let listBreadcrumb = useMemo(() => [{ name: 'Introduction', href: '/' }], [])
return (
<Routes>
<Route
@ -20,14 +17,12 @@ export function Router() {
element={
<>
<Header />
<div className={css['breadcrumb-container']}>
<Breadcrumb className={css.breadcrumb} list={listBreadcrumb} />
</div>
<RouterBreadcrumb />
<div className="window-routes">
<Outlet />
</div>
<Newsletter />
<Footer />
<ScrollFixed />
</>
}
>
@ -51,10 +46,22 @@ export function Router() {
>
<Route index element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/payments" element={<h2>Formas de pagamentos</h2>} />
<Route path="/exchange" element={<h2>Troca e Devolução</h2>} />
<Route path="/privacity" element={<h2>Privacidade</h2>} />
<Route path="/shipping" element={<h2>Entrega</h2>} />
<Route
path="/payments"
element={<h2 className="title">Formas de pagamentos</h2>}
/>
<Route
path="/exchange"
element={<h2 className="title">Troca e Devolução</h2>}
/>
<Route
path="/privacity"
element={<h2 className="title">Privacidade</h2>}
/>
<Route
path="/shipping"
element={<h2 className="title">Entrega</h2>}
/>
</Route>
</Route>
</Routes>

View File

@ -17,6 +17,10 @@
--font-family-100: #{fonts.$font-family-100};
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-family-100);
}
@ -94,6 +98,8 @@ textarea {
}
.window-initial {
min-height: 60vh;
display: grid;
grid-template-columns: 1fr;
gap: 30px;

15
src/template/Footer/@types/index.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import { AccordionProps } from '../../../components/Molecules/Accordion/@types'
export interface InstitutionalListProps
extends Omit<AccordionProps, 'setCurrentAccordion'> {}
export interface ContactListProps
extends Omit<AccordionProps, 'setCurrentAccordion'> {}
export interface QuestionsListProps
extends Omit<AccordionProps, 'setCurrentAccordion'> {}
export interface ListProps {
accordionCurrentIsOpen: string | number | ''
handleSetCurrentAccordion: (customKey: string | number | '') => void
}

View File

@ -0,0 +1,45 @@
import { ListProps } from '../@types'
import { AccordionProviderItems } from '../../../components/Molecules/Accordion/context/Items'
import { ContactList } from './__Contact'
import { InstitutionalList } from './__Institutional'
import { QuestionsList } from './__Question'
import { Socials } from './__Socials'
import css from '../index.module.scss'
import { Item } from '../../../components/Atoms/Item'
import { CustomLink } from '../../../components/Atoms/CustomLink'
export function List({
accordionCurrentIsOpen,
handleSetCurrentAccordion,
}: ListProps) {
return (
<>
<AccordionProviderItems
value={{
current: accordionCurrentIsOpen,
handleSetCurrent: handleSetCurrentAccordion,
}}
>
<ul className={`${css['footer__lists']} ${css['lists']}`}>
<Item>
<InstitutionalList customKey={'institutional'} />
</Item>
<Item>
<QuestionsList customKey={'question'} />
</Item>
<Item>
<ContactList customKey={'contact'} />
</Item>
</ul>
<div className={css['footer__network']}>
<Socials />
<CustomLink href="/">www.loremipsum.com</CustomLink>
</div>
</AccordionProviderItems>
</>
)
}

View File

@ -1,7 +1,7 @@
import { Field, Form, Formik } from 'formik'
import * as Yup from 'yup'
import css from './index.module.scss'
import styles from '../index.module.scss'
const schema = Yup.object({
email: Yup.string().required('Campo Obrigratorio').email('Email ínvalido'),
@ -9,16 +9,16 @@ const schema = Yup.object({
export function Newsletter() {
return (
<section className={css.newsletter}>
<div className={css.container}>
<div className={css.content}>
<section className={styles['newsletter']}>
<div className={styles['newsletter__container']}>
<div className={styles['newsletter__content']}>
<h3>Assine nossa newsletter</h3>
<Formik
initialValues={{ email: '' }}
onSubmit={(e) => console.log(e)}
validationSchema={schema}
>
<Form className={css.form}>
<Form className={styles['newsletter__form']}>
<fieldset className="form-container">
<div className="form-group">
<div className="form-input">

View File

@ -0,0 +1,39 @@
import BoletoImg from '../../../assets/brands/Boleto.png'
import DinersBrandImg from '../../../assets/brands/Diners.png'
import EloBrandImg from '../../../assets/brands/Elo.png'
import HiperBrandImg from '../../../assets/brands/Hiper.png'
import PaypalBrandImg from '../../../assets/brands/Paypal.png'
import MasterCardBrandImg from '../../../assets/brands/Master.png'
import VisaBrandImg from '../../../assets/brands/Visa.png'
import VtexPCIImg from '../../../assets/brands/vtex-pci-200.png'
import styles from '../index.module.scss'
import { Item } from '../../../components/Atoms/Item'
const assetsImg = [
MasterCardBrandImg,
VisaBrandImg,
DinersBrandImg,
EloBrandImg,
HiperBrandImg,
PaypalBrandImg,
BoletoImg,
]
export function PaymentsList() {
return (
<ul className={`${styles['footer__payments']} ${styles['payments']}`}>
{assetsImg.map((image, index) => {
return (
<Item className={`${styles['payments__item']}`} key={index}>
<img className="payments__image" src={image} alt="" />
</Item>
)
})}
<div className={`${styles['payments__divider']}`} role={'presentation'} />
<Item className={`${styles['payments__item']}`}>
<img src={VtexPCIImg} alt="" />
</Item>
</ul>
)
}

View File

@ -0,0 +1,28 @@
import { ContactListProps } from '../@types'
import { Item } from '../../../components/Atoms/Item'
import { Accordion } from '../../../components/Molecules/Accordion'
import styles from '../index.module.scss'
export function ContactList({ customKey }: ContactListProps) {
return (
<Accordion className={styles.accordion} customKey={customKey}>
<Accordion.Header
customKey={customKey}
className={`${styles['lists__header']}`}
>
<h4>Fale Conosco</h4>
</Accordion.Header>
<Accordion.Content className={`${styles['lists__content']}`}>
<ul className={styles['lists__content-list']}>
<Item>Atendimento ao Consumidor</Item>
<Item>(11) 4159 9504</Item>
<Item>Atendimento Online</Item>
<Item>(11) 99433-8825</Item>
</ul>
</Accordion.Content>
</Accordion>
)
}

View File

@ -0,0 +1,35 @@
import { InstitutionalListProps } from '../@types'
import { Accordion } from '../../../components/Molecules/Accordion'
import css from '../index.module.scss'
import { ItemList } from '../../../components/Molecules/ItemList'
export function InstitutionalList({ customKey }: InstitutionalListProps) {
return (
<Accordion className={css.accordion} customKey={customKey}>
<Accordion.Header
customKey={customKey}
className={`${css['lists__header']}`}
>
<h4>Institucional</h4>
</Accordion.Header>
<Accordion.Content className={`${css['lists__content']}`}>
<ul className={css['lists__content-list']}>
<ItemList $container={{ className: '' }} href="/">
Quem somos nós
</ItemList>
<ItemList $container={{ className: '' }} href="/">
Política de Privacidade
</ItemList>
<ItemList $container={{ className: '' }} href="/">
Segurança
</ItemList>
<ItemList $container={{ className: '' }} href="/">
Seja um Revendedor
</ItemList>
</ul>
</Accordion.Content>
</Accordion>
)
}

View File

@ -0,0 +1,35 @@
import { QuestionsListProps } from '../@types'
import { Accordion } from '../../../components/Molecules/Accordion'
import { ItemList } from '../../../components/Molecules/ItemList'
import css from '../index.module.scss'
export function QuestionsList({ customKey }: QuestionsListProps) {
return (
<Accordion className={css.accordion} customKey={customKey}>
<Accordion.Header
customKey={customKey}
className={`${css['lists__header']}`}
>
<h4>Dúvidas</h4>
</Accordion.Header>
<Accordion.Content className={`${css['lists__content']}`}>
<ul className={`${css['lists__content-list']}`}>
<ItemList $container={{ className: '' }} href="/">
Entrega
</ItemList>
<ItemList $container={{ className: '' }} href="/">
Pagamento
</ItemList>
<ItemList $container={{ className: '' }} href="/">
Trocas e Devoluções
</ItemList>
<ItemList $container={{ className: '' }} href="/">
Dúvidas Frequentes
</ItemList>
</ul>
</Accordion.Content>
</Accordion>
)
}

View File

@ -0,0 +1,31 @@
import { ItemList } from '../../../components/Molecules/ItemList'
import facebookIcon from '../../../assets/icons/facebook.svg'
import instagramIcon from '../../../assets/icons/instagram.svg'
import linkedinIcon from '../../../assets/icons/linkedin.svg'
import twitterIcon from '../../../assets/icons/twitter.svg'
import youtubeIcon from '../../../assets/icons/youtube.svg'
import css from '../index.module.scss'
export function Socials() {
return (
<ul className={css.socials}>
<ItemList href="/">
<img src={facebookIcon} alt="" />
</ItemList>
<ItemList href="/">
<img src={instagramIcon} alt="" />
</ItemList>
<ItemList href="/">
<img src={twitterIcon} alt="" />
</ItemList>
<ItemList href="/">
<img src={youtubeIcon} alt="" />
</ItemList>
<ItemList href="/">
<img src={linkedinIcon} alt="" />
</ItemList>
</ul>
)
}

View File

@ -1,45 +0,0 @@
import { AccordionProviderItems } from '../../../modules/components/Accordion/context/Items'
import { ContactList } from './Lists/Contact'
import { InstitutionalList } from './Lists/Institutional'
import { QuestionsList } from './Lists/Question'
import { Socials } from './Lists/Socials'
import css from '../index.module.scss'
interface ListProps {
accordionCurrentIsOpen: string | number | ''
handleSetCurrentAccordion: (customKey: string | number | '') => void
}
export function List({
accordionCurrentIsOpen,
handleSetCurrentAccordion,
}: ListProps) {
return (
<>
<AccordionProviderItems
value={{
current: accordionCurrentIsOpen,
handleSetCurrent: handleSetCurrentAccordion,
}}
>
<ul className={css.lists}>
<li>
<InstitutionalList customKey={'1'} />
</li>
<li>
<QuestionsList customKey={'2'} />
</li>
<li>
<ContactList customKey={'3'} />
</li>
</ul>
<div className={css.social}>
<Socials />
<a href="/">www.loremipsum.com</a>
</div>
</AccordionProviderItems>
</>
)
}

View File

@ -1,38 +0,0 @@
import { Accordion } from '../../../../modules/components/Accordion'
import { AccordionProps } from '../../../../modules/components/Accordion/@types'
import { Item } from '../../../../modules/common/Item'
import css from '../../index.module.scss'
interface ContactListProps
extends Omit<AccordionProps, 'setCurrentAccordion'> {}
export function ContactList({ customKey }: ContactListProps) {
return (
<Accordion className={css.accordion} customKey={customKey}>
<Accordion.Header
customKey={customKey}
className={`${css['list-header']}`}
>
<h4>Fale Conosco</h4>
</Accordion.Header>
<Accordion.Content className={`${css.content}`}>
<ul className={css.list}>
<Item $container={{ className: '' }} href="/">
Atendimento ao Consumidor
</Item>
<Item $container={{ className: '' }} href="/">
(11) 4159 9504
</Item>
<Item $container={{ className: '' }} href="/">
Atendimento Online
</Item>
<Item $container={{ className: '' }} href="/">
(11) 99433-8825
</Item>
</ul>
</Accordion.Content>
</Accordion>
)
}

View File

@ -1,37 +0,0 @@
import { Accordion } from '../../../../modules/components/Accordion'
import { AccordionProps } from '../../../../modules/components/Accordion/@types'
import { Item } from '../../../../modules/common/Item'
import css from '../../index.module.scss'
interface InstitutionalListProps
extends Omit<AccordionProps, 'setCurrentAccordion'> {}
export function InstitutionalList({ customKey }: InstitutionalListProps) {
return (
<Accordion className={css.accordion} customKey={customKey}>
<Accordion.Header
customKey={customKey}
className={`${css['list-header']}`}
>
<h4>Institucional</h4>
</Accordion.Header>
<Accordion.Content className={`${css.content}`}>
<ul className={css.institutional}>
<Item $container={{ className: 'k' }} href="/">
Quem somos nós
</Item>
<Item $container={{ className: '' }} href="/">
Política de Privacidade
</Item>
<Item $container={{ className: '' }} href="/">
Segurança
</Item>
<Item $container={{ className: '' }} href="/">
Seja um Revendedor
</Item>
</ul>
</Accordion.Content>
</Accordion>
)
}

View File

@ -1,40 +0,0 @@
import BoletoImg from '../../../../assets/brands/Boleto.png'
import DinersBrandImg from '../../../../assets/brands/Diners.png'
import EloBrandImg from '../../../../assets/brands/Elo.png'
import HiperBrandImg from '../../../../assets/brands/Hiper.png'
import PaypalBrandImg from '../../../../assets/brands/Paypal.png'
import MasterCardBrandImg from '../../../../assets/brands/Master.png'
import VisaBrandImg from '../../../../assets/brands/Visa.png'
import VtexPCIImg from '../../../../assets/brands/vtex-pci-200.png'
import css from '../../index.module.scss'
export function PaymentsList() {
let images = [
MasterCardBrandImg,
VisaBrandImg,
DinersBrandImg,
EloBrandImg,
HiperBrandImg,
PaypalBrandImg,
BoletoImg,
]
return (
<ul className={css.payments}>
{images.map((image, index) => {
return (
<li key={index}>
<img src={image} alt="" />
</li>
)
})}
<div className={css.divider}></div>
<li>
<img src={VtexPCIImg} alt="" />
</li>
</ul>
)
}

View File

@ -1,37 +0,0 @@
import { Accordion } from '../../../../modules/components/Accordion'
import { AccordionProps } from '../../../../modules/components/Accordion/@types'
import { Item } from '../../../../modules/common/Item'
import css from '../../index.module.scss'
interface QuestionsListProps
extends Omit<AccordionProps, 'setCurrentAccordion'> {}
export function QuestionsList({ customKey }: QuestionsListProps) {
return (
<Accordion className={css.accordion} customKey={customKey}>
<Accordion.Header
customKey={customKey}
className={`${css['list-header']}`}
>
<h4>Dúvidas</h4>
</Accordion.Header>
<Accordion.Content className={`${css.content}`}>
<ul className={`${css['quesitons-list']}`}>
<Item $container={{ className: '' }} href="/">
Entrega
</Item>
<Item $container={{ className: '' }} href="/">
Pagamento
</Item>
<Item $container={{ className: '' }} href="/">
Trocas e Devoluções
</Item>
<Item $container={{ className: '' }} href="/">
Dúvidas Frequentes
</Item>
</ul>
</Accordion.Content>
</Accordion>
)
}

View File

@ -1,39 +0,0 @@
import facebookIcon from '../../../../assets/icons/facebook.svg'
import instagramIcon from '../../../../assets/icons/instagram.svg'
import linkedinIcon from '../../../../assets/icons/linkedin.svg'
import twitterIcon from '../../../../assets/icons/twitter.svg'
import youtubeIcon from '../../../../assets/icons/youtube.svg'
import css from '../../index.module.scss'
export function Socials() {
return (
<ul className={css.socials}>
<li>
<a href="/">
<img src={facebookIcon} alt="" />
</a>
</li>
<li>
<a href="/">
<img src={instagramIcon} alt="" />
</a>
</li>
<li>
<a href="/">
<img src={twitterIcon} alt="" />
</a>
</li>
<li>
<a href="/">
<img src={youtubeIcon} alt="" />
</a>
</li>
<li>
<a href="/">
<img src={linkedinIcon} alt="" />
</a>
</li>
</ul>
)
}

View File

@ -1,20 +1,172 @@
@use '../../styles/utils/helpers/functions' as function;
.footer {
border-top: 1px solid var(--clr-common-black);
}
&__container--top,
&__container--bottom {
width: 100%;
padding: 0 16px;
.container-top,
.container-bottom {
width: 100%;
padding: 0 16px;
@media screen and (min-width: 1025px) {
padding: 0;
@media screen and (min-width: 1025px) {
padding: 0;
}
}
}
.container-top {
.footer__content {
border-top: 1px solid var(--clr-common-black);
}
.newsletter {
width: 100%;
border-top: 1px solid var(--clr-common-black);
&__container {
width: 100%;
padding: 16px;
}
}
.newsletter {
h3 {
margin-bottom: 16px;
font-weight: 500;
font-size: var(--txt-normal);
line-height: 16.41px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--clr-gray-800);
@media screen and (min-width: 1025px) {
font-size: var(--txt-large);
line-height: 21.09px;
}
@media screen and (min-width: 2500px) {
line-height: 42.19px;
}
}
}
.newsletter__container {
@media screen and (min-width: 1025px) {
width: function.fluid(474px, 1280px);
padding: 16px 0;
margin: 0 auto;
}
@media screen and (min-width: 2500px) {
width: function.fluid(922px, 2500px);
}
}
.newsletter__container {
fieldset :global {
border: none;
.form-group {
width: 100%;
display: flex;
align-items: center;
flex-direction: column;
gap: 8px;
}
}
}
.newsletter__container :global {
.form-input {
width: 100%;
margin-bottom: 16px;
@media screen and (min-width: 1025px) {
width: function.fluid(340px, 474px);
}
@media screen and (min-width: 2500px) {
width: function.fluid(668px, 922px);
}
}
}
.newsletter__container :global {
input {
display: block;
width: 100%;
height: 50px;
padding: 0 16px;
border: 1px solid var(--clr-gray-400);
font-size: var(--txt-normal);
line-height: 16.41px;
&::placeholder {
color: var(--clr-gray-400);
}
@media screen and (min-width: 1025px) {
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
line-height: 32.81px;
height: 59px;
}
}
}
.newsletter__form {
button[type='submit'] {
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--clr-common-black);
color: var(--clr-common-white);
text-transform: uppercase;
font-size: var(--txt-normal);
line-height: 16.41px;
font-weight: 700;
letter-spacing: 0.05em;
@media screen and (min-width: 1025px) {
width: function.fluid(126px, 474px);
font-size: var(--txt-xs);
line-height: 14.06px;
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
width: function.fluid(246px, 922px);
line-height: 28.13px;
height: 59px;
}
}
}
.newsletter {
@media screen and (min-width: 1025px) {
&__container {
fieldset :global {
.form-group {
flex-direction: row;
}
.form-group {
.form-input {
margin-bottom: 0;
}
}
}
}
}
}
.footer__container--top {
padding-top: 17px;
padding-bottom: 24px;
@ -42,10 +194,10 @@
}
}
.container-bottom {
.footer__container--bottom {
background-color: var(--clr-common-black);
.phrase {
.footer__phrase {
width: function.fluid(258px, 343px);
&::before {
@ -75,15 +227,15 @@
}
}
.container-bottom {
.phrase::before,
.actions-bottom .credits li p {
.footer__container--bottom {
.footer__phrase::before,
.footer__actions--bottom .footer__credits li p {
font-size: var(--txt-xxs);
}
}
.actions-bottom {
.credits {
.footer__actions--bottom {
.footer__credits {
gap: 12.73px;
&,
@ -108,8 +260,8 @@
}
// moved container bottom actions footer for large, medium devices
.container-bottom {
.actions-bottom {
.footer__container--bottom {
.footer__actions--bottom {
width: 100%;
height: 100%;
padding: 15px 0;
@ -144,8 +296,8 @@
flex-direction: column;
gap: 12px;
.content {
ul {
.lists__content {
&-list {
display: flex;
align-items: flex-start;
flex-direction: column;
@ -187,7 +339,7 @@
width: function.fluid(155px, 707px);
}
.content {
.lists__content {
height: auto;
overflow: unset;
transform: translate(0, 0);
@ -203,7 +355,7 @@
}
}
.list-header {
.lists__header {
@media only screen and (min-width: 1025px) {
margin-bottom: 12px;
@ -230,8 +382,9 @@
.payments {
display: flex;
align-items: center;
color: red;
.divider {
&__divider {
height: 20.36px;
border-left: 1px solid var(--clr-gray-400);
@ -252,12 +405,12 @@
'7': 10.33px
)
{
li:nth-child(#{$index}) {
&__item:nth-child(#{$index}) {
margin-right: #{$value};
}
}
li:last-child {
&__item:last-child {
margin-left: 10.49px;
}
@ -265,13 +418,18 @@
width: function.fluid(398.61px, 1080px);
gap: 12px;
@each $index in('1', '2', '3', '4', '5', '6', '7') {
li:nth-child(#{$index}) {
margin: 0px;
}
&__item:nth-child(1),
&__item:nth-child(2),
&__item:nth-child(3),
&__item:nth-child(4),
&__item:nth-child(5),
&__item:nth-child(6),
&__item:nth-child(7) {
margin: 0px;
}
li {
&__item {
width: function.fluid(36px, 398.61px);
min-height: 20.2px;
@ -281,7 +439,7 @@
}
}
li:last-child {
&__item:last-child {
width: function.fluid(54.61px, 398.61px);
min-height: 34px;
margin: 0;
@ -291,19 +449,19 @@
@media screen and (min-width: 2500px) {
width: function.fluid(692px, 2299.68px);
li {
&__item {
width: function.fluid(70px, 692px);
min-height: 39.27px;
}
li:last-child {
&__item:last-child {
width: function.fluid(106px, 692px);
min-height: 66px;
}
}
}
.social {
.footer__network {
& > a {
display: none;
@ -351,23 +509,23 @@
}
// control view order components
.container-bottom {
.phrase {
.footer {
&__phrase {
order: 2;
}
.payments {
&__payments {
order: 1;
}
.credits {
&__credits {
order: 3;
}
@media only screen and (min-width: 1025px) {
.phrase,
.payments,
.credits {
&__phrase,
&__payments,
&__credits {
order: initial;
}
}

View File

@ -3,10 +3,11 @@ import { useState } from 'react'
import vtexIcon from '../../assets/brands/svgs/Vtex.svg'
import m3Icon from '../../assets/brands/svgs/M3.svg'
import { List } from './fragments/List'
import { PaymentsList } from './fragments/Lists/Payment'
import { List } from './containers/_List'
import { PaymentsList } from './containers/_Payment'
import css from './index.module.scss'
import { Newsletter } from './containers/_Newsletter'
export function Footer() {
const [accordionCurrentIsOpen, setAccordionCurrentIsOpen] = useState<
@ -24,28 +25,31 @@ export function Footer() {
}
return (
<footer className={css.footer}>
<div className={css['container-top']}>
<List
accordionCurrentIsOpen={accordionCurrentIsOpen}
handleSetCurrentAccordion={handleSetCurrentAccordion}
/>
</div>
<footer className={css['footer']}>
<Newsletter />
<div className={css['footer__content']}>
<div className={css['footer__container--top']}>
<List
accordionCurrentIsOpen={accordionCurrentIsOpen}
handleSetCurrentAccordion={handleSetCurrentAccordion}
/>
</div>
<div className={css['container-bottom']}>
<div className={css['actions-bottom']}>
<p className={css.phrase}></p>
<PaymentsList />
<ul className={css.credits}>
<li>
<p>Powered by</p>
<img src={vtexIcon} alt="" />
</li>
<li>
<p>Developed by</p>
<img src={m3Icon} alt="" />
</li>
</ul>
<div className={css['footer__container--bottom']}>
<div className={css['footer__actions--bottom']}>
<p className={css['footer__phrase']}></p>
<PaymentsList />
<ul className={css['footer__credits']}>
<li>
<p>Powered by</p>
<img src={vtexIcon} alt="" />
</li>
<li>
<p>Developed by</p>
<img src={m3Icon} alt="" />
</li>
</ul>
</div>
</div>
</div>
</footer>

View File

@ -0,0 +1,19 @@
import { HTMLAttributes, ButtonHTMLAttributes } from 'react'
export type ISearchProps = HTMLAttributes<HTMLDivElement>
export interface ITopProps {
handleClickOpen: () => void
}
export interface IBottomProps {
handleClickClose: () => void
isMenuOpen: boolean
}
export interface ActionsProps extends ButtonHTMLAttributes<HTMLButtonElement> {
$container?: HTMLAttributes<HTMLDivElement>
handleClick: () => any
src: any
alt: string
}

View File

@ -0,0 +1,47 @@
import { IBottomProps } from './@types'
import { ItemList } from '../../../components/Molecules/ItemList'
import { Actions } from './__Actions'
import closeIcon from '../../../assets/icons/x.svg'
import styles from '../index.module.scss'
export function Bottom({ isMenuOpen, handleClickClose }: IBottomProps) {
function closeMenu(e: any) {
if (e.target.classList.contains(styles.menu)) {
if (e.target.children[0] !== e.target) {
handleClickClose()
}
}
}
return (
<div
onClick={(e) => closeMenu(e)}
className={`${styles['menu']} ${
isMenuOpen ? styles['menu--active'] : ''
}`}
>
<div className={styles['menu__container']}>
<Actions
handleClick={handleClickClose}
$container={{ className: styles['actions__bottom'] }}
className={styles['button-close']}
alt="ícone do botão para fechar o menu"
src={closeIcon}
/>
<ul className={styles['menu__list']}>
{['Cursos', 'Saiba Mais', 'Institucionais'].map((item, index) => {
return (
<ItemList href="/" key={'header-bottom-list-' + index}>
{item}
</ItemList>
)
})}
</ul>
</div>
</div>
)
}

View File

@ -0,0 +1,33 @@
import { useState } from 'react'
import { ISearchProps } from './@types'
import { ButtonIcon } from '../../../components/Molecules/ButtonIcon'
import searchIcon from '../../../assets/icons/search.svg'
export function Search({ ...props }: ISearchProps) {
const [searchData, setSearchData] = useState('')
const handleChangeValue = (e: any) => {
setSearchData(e.target.value)
}
return (
<div {...props}>
<input
type="search"
name="search"
placeholder="Buscar..."
value={searchData}
onChange={handleChangeValue}
/>
<ButtonIcon
type="button"
onClick={() => setSearchData('')}
src={searchIcon}
alt="Search Icon"
/>
</div>
)
}

View File

@ -0,0 +1,42 @@
import { ITopProps } from './@types'
import { ButtonIcon } from '../../../components/Molecules/ButtonIcon'
import { Actions } from './__Actions'
import { Search } from './_Search'
import cartIcon from '../../../assets/icons/minicart.svg'
import openIcon from '../../../assets/icons/hamburger.svg'
import logoImg from '../../../assets/m3-logo-small.png'
import logoMediumImg from '../../../assets/m3-logo-medium.png'
import styles from '../index.module.scss'
export function Top({ handleClickOpen }: ITopProps) {
return (
<div className={`${styles['l-header__top']}`}>
<ButtonIcon
className={styles['button-open']}
onClick={handleClickOpen}
type="button"
src={openIcon}
alt="ícone do botão para abrir o menu"
/>
<a className="top__logo" href="/">
<picture>
<source media="(min-width:1025px)" srcSet={logoMediumImg} />
<img src={logoImg} alt="logo da M3 Academy" />
</picture>
</a>
<Search className={`${styles['search']} ${styles['search--top']}`} />
<Actions
$container={{ className: styles['actions__top'] }}
handleClick={() => console.log('Funcionando')}
src={cartIcon}
alt="ícone de carrinho"
/>
</div>
)
}

View File

@ -0,0 +1,21 @@
import { ActionsProps } from './@types'
import { CustomLink } from '../../../components/Atoms/CustomLink'
import { ButtonIcon } from '../../../components/Molecules/ButtonIcon'
export function Actions({
handleClick,
$container,
src,
alt,
...props
}: ActionsProps) {
return (
<div {...$container}>
<CustomLink href="/">Entrar</CustomLink>
{/* prettier-ignore */}
<ButtonIcon onClick={handleClick} type="button" src={src}
alt={alt} {...props}
/>
</div>
)
}

View File

@ -7,18 +7,16 @@ $containers: (
'xl': 2299.68px,
);
.header {
.l-header {
padding: 25px 0;
position: sticky;
top: 0;
left: 0;
z-index: 1000;
position: sticky;
background-color: var(--clr-common-black);
}
.header {
@media only screen and (min-width: 1025px) {
padding: 25px 0 0;
}
@ -28,16 +26,14 @@ $containers: (
}
}
.content {
.l-header__top {
padding: 0px 16px;
margin-bottom: 27.14px;
display: flex;
align-items: center;
justify-content: space-between;
}
.content {
@media only screen and (min-width: 1025px) {
width: #{function.fluid(map-get($containers, 'md'), 1280px)};
padding: 0;
@ -49,27 +45,29 @@ $containers: (
}
}
.logo {
$content: (
'md': 136px,
'xl': 265.62px,
);
.l-header__top :global {
.top__logo {
$content: (
'md': 136px,
'xl': 265.62px,
);
// prettier-ignore
@media only screen and (min-width: 1025px) {
// prettier-ignore
@media only screen and (min-width: 1025px) {
width: function.fluid(
map-get($content, 'md'),
map-get($containers, 'md')
);
}
// prettier-ignore
@media only screen and (min-width: 2500px) {
// prettier-ignore
@media only screen and (min-width: 2500px) {
width: function.fluid(
map-get($content, 'xl'),
map-get($containers, 'xl')
);
}
}
}
.search {
@ -77,12 +75,21 @@ $containers: (
border: 2px solid var(--clr-gray-150);
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--clr-common-white);
&,
button {
display: flex;
align-items: center;
justify-content: center;
}
button {
width: 36px;
height: 100%;
background-color: var(--clr-common-white);
}
input {
width: 100%;
padding: 0 9px 0 16px;
@ -92,25 +99,12 @@ $containers: (
font-size: var(--txt-normal);
line-height: 16.41px;
@media screen and (min-width: 2500px) {
@media only screen and (min-width: 2500px) {
line-height: 32.81px;
}
}
}
.search {
button {
width: 36px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--clr-common-white);
}
}
.search {
@media only screen and (min-width: 1025px) {
button {
@ -126,28 +120,30 @@ $containers: (
button {
width: 56.85px;
}
img {
width: 35.15px;
height: 35.15px;
}
button img {
width: 35.15px;
height: 35.15px;
}
}
}
.actions-top,
.actions-bottom {
.actions__top,
.actions__bottom {
display: flex;
align-items: center;
a {
display: block;
font-weight: 400;
font-size: var(--txt-normal);
line-height: 16.41px;
text-transform: uppercase;
display: block;
color: var(--clr-common-white);
transition: color 200ms linear;
text-transform: uppercase;
&:hover {
color: var(--clr-primary-blue-500);
@ -159,9 +155,20 @@ $containers: (
}
}
.actions-top {
.actions__top {
gap: 55px;
// remove action top link for small devices and added for large devices 1025 > x
a {
display: none;
}
@media only screen and (min-width: 1025px) {
a {
display: block;
}
}
@media only screen and (min-width: 2500px) {
img {
width: 54.68px;
@ -171,7 +178,7 @@ $containers: (
}
// remove open menu mobile button for large devices 1025 > x
.open {
.button-open {
display: flex;
@media only screen and (min-width: 1025px) {
@ -179,23 +186,26 @@ $containers: (
}
}
// remove action top link for small devices and added for large devices 1025 > x
.actions-top {
a {
display: none;
}
.actions__bottom {
width: 100%;
height: 78px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--clr-common-black);
// remove actions bottom for large devices
@media only screen and (min-width: 1025px) {
a {
display: block;
}
display: none;
}
}
.menu {
@media only screen and (max-width: 1024px) {
width: 100vw;
height: 100vh;
position: fixed;
left: -100%;
@ -204,66 +214,56 @@ $containers: (
background-color: transparent;
transition: 300ms ease;
&-content {
width: function.fluid(map-get($containers, 'sm'), 1024px);
&,
&__container {
height: 100vh;
}
}
@media screen and (min-width: 1025px) {
&-content {
border-top: 1px solid var(--clr-common-white);
&__container {
width: function.fluid(map-get($containers, 'sm'), 1024px);
}
}
}
.menu {
@media only screen and (max-width: 768px) {
&-content {
&__container {
width: function.fluid(map-get($containers, 'xs'), 375px);
height: 100vh;
}
}
}
.menu {
.actions-bottom {
width: 100%;
height: 78px;
background-color: var(--clr-common-black);
}
.list {
padding: 0 16px;
a {
display: block;
text-transform: uppercase;
transition: color 200ms linear;
&:hover {
color: var(--clr-primary-blue-500);
}
@media screen and (min-width: 1025px) {
&__container {
border-top: 1px solid var(--clr-common-white);
}
}
}
.menu {
.list {
&__list {
height: calc(100% - 78px);
padding-top: 31px;
padding: 31px 16px 0;
background-color: var(--clr-common-white);
a {
margin-bottom: 12px;
font-weight: 500;
font-size: var(--txt-normal);
line-height: 16.41px;
margin-bottom: 12px;
display: block;
color: var(--clr-gray-400);
transition: color 200ms linear;
text-transform: uppercase;
&:hover {
color: var(--clr-primary-blue-500);
}
@media screen and (min-width: 2500px) {
line-height: 32.81px;
@ -272,18 +272,9 @@ $containers: (
}
}
.actions-bottom {
padding: 0 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
// styles for menu is active
.menu {
&.active {
&.menu--active {
left: 0;
background-color: #0004;
}
@ -296,7 +287,9 @@ $containers: (
display: block;
.list {
background-color: var(--clr-common-black);
&__list {
width: function.fluid(map-get($containers, 'md'), 1280px);
padding: 14px 0;
margin: 0 auto;
@ -313,22 +306,16 @@ $containers: (
margin: 0;
}
}
background-color: var(--clr-common-black);
.actions-bottom {
display: none;
}
}
@media screen and (min-width: 2500px) {
.list {
&__list {
width: function.fluid(map-get($containers, 'xl'), 2500px);
}
}
}
.search-top {
.search--top {
$content: (
'md': 264px,
'xl': 515.62px,
@ -356,7 +343,7 @@ $containers: (
\*|[O]o-o-o-o[X]|*/
//remove search top box for small, medium devices
.search-top {
.search--top {
display: none;
@media only screen and (min-width: 1025px) {
@ -365,12 +352,12 @@ $containers: (
}
// added search bottom box for small devices, medium devices
.search-bottom {
.search__bottom {
padding: 0 16px;
display: flex;
&-content {
&--content {
width: 100%;
}

View File

@ -1,119 +1,47 @@
import logoImg from '../../assets/m3-logo-small.png'
import logoMediumImg from '../../assets/m3-logo-medium.png'
import searchIcon from '../../assets/icons/search.svg'
import cartIcon from '../../assets/icons/minicart.svg'
import openIcon from '../../assets/icons/hamburger.svg'
import closeIcon from '../../assets/icons/x.svg'
import { useMemo, useState } from 'react'
import css from './index.module.scss'
import { HTMLAttributes, useMemo, useState } from 'react'
import { Top } from './containers/_Top'
import { Bottom } from './containers/_Bottom'
import { Search } from './containers/_Search'
interface SearchProps extends HTMLAttributes<HTMLDivElement> {}
export function Search({ ...props }: SearchProps) {
return (
<div {...props}>
<input type="search" placeholder="Buscar..." />
<button type="button">
<img src={searchIcon} alt="Search icon" />
</button>
</div>
)
}
interface ContainerBottomProps {
isOpen: boolean
handleClose: () => void
}
const ContainerBottom = ({ isOpen, handleClose }: ContainerBottomProps) => {
return (
<div
className={`${css.menu} ${isOpen ? css.active : ''}`}
data-jsx="target"
>
<div className={css['menu-content']}>
<div className={css['actions-bottom']}>
<a href="/">Entrar</a>
<button
onClick={handleClose}
className={css.close}
type="button"
data-jsx="event"
>
<img src={closeIcon} alt="ícone do botão para fechar o menu" />
</button>
</div>
<ul className={css.list}>
{['Cursos', 'Saiba Mais', 'Institucionais'].map((item, index) => {
return (
<li key={'header-bottom-list-' + index}>
<a href="/">{item}</a>
</li>
)
})}
</ul>
</div>
</div>
)
}
import styles from './index.module.scss'
export const Header = () => {
const [isOpenMenu, setIsOpenMenu] = useState(false)
const [isMenuOpen, setIsMenuOpen] = useState(false)
const handleOpen = useMemo(
() =>
function () {
if (window.innerWidth <= 1024) setIsOpenMenu(true)
},
[]
)
const topProps = {
handleClickOpen: useMemo(
() =>
function () {
if (window.innerWidth <= 1024) setIsMenuOpen(true)
},
[]
),
}
const handleClose = useMemo(
() =>
function () {
if (window.innerWidth <= 1024) setIsOpenMenu(false)
},
[]
)
const bottomProps = {
isMenuOpen,
handleClickClose: useMemo(
() =>
function () {
if (window.innerWidth <= 1024) setIsMenuOpen(false)
},
[]
),
}
return (
<header className={css.header}>
<nav className={css.nav}>
<div className={css.content}>
<button
onClick={handleOpen}
className={css.open}
type="button"
data-jsx="event"
>
<img src={openIcon} alt="ícone do botão para abrir o menu" />
</button>
<header className={styles['l-header']}>
<nav>
<Top {...topProps} />
<a className={css.logo} href="/">
<picture>
<source media="(min-width:1025px)" srcSet={logoMediumImg} />
<img src={logoImg} alt="logo da M3 Academy" />
</picture>
</a>
<Search className={`${css.search} ${css['search-top']}`} />
<div className={css['actions-top']}>
<a href="/">Entrar</a>
<button type="button">
<img src={cartIcon} alt="ícone de carrinho" />
</button>
</div>
<div className={`${styles['search__bottom']}`}>
<Search
className={`${styles['search']} ${styles['search__bottom--content']}`}
/>
</div>
<div className={`${css['search-bottom']}`}>
<Search className={`${css.search} ${css['search-bottom-content']}`} />
</div>
<ContainerBottom isOpen={isOpenMenu} handleClose={handleClose} />
<Bottom {...bottomProps} />
</nav>
</header>
)

View File

@ -1,147 +0,0 @@
@use '../../styles/utils/helpers/functions' as function;
.newsletter {
width: 100%;
border-top: 1px solid var(--clr-common-black);
}
.container {
width: 100%;
padding: 16px;
}
.newsletter h3 {
margin-bottom: 16px;
font-weight: 500;
font-size: var(--txt-normal);
line-height: 16.41px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--clr-gray-800);
@media screen and (min-width: 1025px) {
font-size: var(--txt-large);
line-height: 21.09px;
}
@media screen and (min-width: 2500px) {
line-height: 42.19px;
}
}
.container {
fieldset :global {
border: none;
.form-group {
width: 100%;
display: flex;
align-items: center;
flex-direction: column;
gap: 8px;
}
}
@media screen and (min-width: 1025px) {
width: function.fluid(474px, 1280px);
padding: 16px 0;
margin: 0 auto;
}
@media screen and (min-width: 2500px) {
width: function.fluid(922px, 2500px);
}
}
.container :global {
.form-input {
width: 100%;
margin-bottom: 16px;
@media screen and (min-width: 1025px) {
width: function.fluid(340px, 474px);
}
@media screen and (min-width: 2500px) {
width: function.fluid(668px, 922px);
}
}
input {
display: block;
width: 100%;
height: 50px;
padding: 0 16px;
border: 1px solid var(--clr-gray-400);
font-size: var(--txt-normal);
line-height: 16.41px;
@media screen and (min-width: 2500px) {
line-height: 32.81px;
}
&::placeholder {
color: var(--clr-gray-400);
}
@media screen and (min-width: 1025px) {
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
height: 59px;
}
}
}
.form {
button[type='submit'] {
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--clr-common-black);
color: var(--clr-common-white);
text-transform: uppercase;
font-size: var(--txt-normal);
line-height: 16.41px;
font-weight: 700;
letter-spacing: 0.05em;
@media screen and (min-width: 1025px) {
width: function.fluid(126px, 474px);
font-size: var(--txt-xs);
line-height: 14.06px;
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
width: function.fluid(246px, 922px);
line-height: 28.13px;
height: 59px;
}
}
}
.newsletter {
@media screen and (min-width: 1025px) {
.container {
fieldset :global {
.form-group {
flex-direction: row;
}
.form-group {
.form-input {
margin-bottom: 0;
}
}
}
}
}
}

View File

@ -3,45 +3,45 @@
height: 100%;
margin-top: 40px;
&__container {
width: 100%;
height: auto;
}
@media screen and (min-width: 1025px) {
max-height: 285px;
margin-top: 0;
border-right: 1px solid var(--clr-common-black);
max-height: 285px;
}
@media screen and (min-width: 2500px) {
max-height: 465px;
}
}
.sidebar__list {
a {
display: inline-block;
.container {
width: 100%;
height: auto;
}
padding: 10px 20px;
.list {
li {
a {
display: inline-block;
font-weight: 400;
font-size: var(--txt-medium);
line-height: 18.75px;
width: 100%;
padding: 10px 20px;
color: var(--clr-gray-600);
font-weight: 400;
font-size: var(--txt-medium);
line-height: 18.75px;
transition: background 700ms ease;
color: var(--clr-gray-600);
&.active {
color: var(--clr-common-white);
background: var(--clr-common-black);
font-weight: 700;
}
&.active {
background-color: var(--clr-common-black);
color: var(--clr-common-white);
font-weight: 700;
}
@media screen and (min-width: 2500px) {
line-height: 37.5px;
}
}
@media screen and (min-width: 2500px) {
line-height: 37.5px;
}
}
}

View File

@ -1,6 +1,8 @@
import { Link, useLocation } from 'react-router-dom'
import { useLocation } from 'react-router-dom'
import css from './index.module.scss'
import { ItemList } from '../../components/Molecules/ItemList'
import styles from './index.module.scss'
export function Sidebar() {
const { pathname } = useLocation()
@ -15,16 +17,18 @@ export function Sidebar() {
]
return (
<aside className={css.sidebar}>
<div className={css.container}>
<ul className={css.list}>
<aside className={styles['sidebar']}>
<div className={styles['sidebar__container']}>
<ul className={styles['sidebar__list']}>
{paths.map(({ path, name }) => {
return (
<li key={name + '-sidebar'}>
<Link className={pathname === path ? css.active : ''} to={path}>
{name}
</Link>
</li>
<ItemList
className={pathname === path ? styles['active'] : ''}
key={name.replace(' ', '-').toLowerCase() + '-sidebar'}
to={path}
>
{name}
</ItemList>
)
})}
</ul>