first commit

This commit is contained in:
joseph le 2023-11-07 15:31:21 +07:00
parent bdcef5be9f
commit c3caee7995
41 changed files with 42387 additions and 6793 deletions

33979
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,10 @@
"storybook:build": "storybook build" "storybook:build": "storybook build"
}, },
"dependencies": { "dependencies": {
"@mantine/carousel": "^7.1.7",
"@mantine/core": "7.1.7", "@mantine/core": "7.1.7",
"@mantine/ds": "^7.1.7",
"@mantine/form": "^7.2.0",
"@mantine/hooks": "7.1.7", "@mantine/hooks": "7.1.7",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -1,5 +1,5 @@
import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { HomePage } from './pages/Home.page'; import { HomePage } from './pages/Home/Home.page';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {

View File

@ -0,0 +1,104 @@
import React from 'react';
import { useToggle, upperFirst } from '@mantine/hooks';
import { useForm } from '@mantine/form';
import {
TextInput,
PasswordInput,
Text,
Paper,
Group,
PaperProps,
Button,
Divider,
Checkbox,
Anchor,
Stack,
Title,
} from '@mantine/core';
import GoogleButton from '../GoogleButton/GoogleButton';
import classes from './AuthenticationImage.module.css';
const Authen = (props: PaperProps) => {
const [type, toggle] = useToggle(['login', 'register']);
const form = useForm({
initialValues: {
email: '',
name: '',
password: '',
terms: true,
},
validate: {
email: (val) => (/^\S+@\S+$/.test(val) ? null : 'Invalid email'),
password: (val) => (val.length <= 6 ? 'Password should include at least 6 characters' : null),
},
});
return (
<Paper radius="md" p="xl" withBorder {...props}>
<Text size="lg" fw={500}>
Welcome to Mantine, {type} with
</Text>
<Group grow mb="md" mt="md">
<GoogleButton radius="xl">Google</GoogleButton>
</Group>
<Divider label="Or continue with email" labelPosition="center" my="lg" />
<form onSubmit={form.onSubmit(() => {})}>
<Stack>
{type === 'register' && (
<TextInput
label="Name"
placeholder="Your name"
value={form.values.name}
onChange={(event) => form.setFieldValue('name', event.currentTarget.value)}
radius="md"
/>
)}
<TextInput
required
label="Email"
placeholder="hello@mantine.dev"
value={form.values.email}
onChange={(event) => form.setFieldValue('email', event.currentTarget.value)}
error={form.errors.email && 'Invalid email'}
radius="md"
/>
<PasswordInput
required
label="Password"
placeholder="Your password"
value={form.values.password}
onChange={(event) => form.setFieldValue('password', event.currentTarget.value)}
error={form.errors.password && 'Password should include at least 6 characters'}
radius="md"
/>
{type === 'register' && (
<Checkbox
label="I accept terms and conditions"
checked={form.values.terms}
onChange={(event) => form.setFieldValue('terms', event.currentTarget.checked)}
/>
)}
</Stack>
<Group justify="space-between" mt="xl">
<Anchor component="button" type="button" c="dimmed" onClick={() => toggle()} size="xs">
{type === 'register'
? 'Already have an account? Login'
: "Don't have an account? Register"}
</Anchor>
<Button type="submit" radius="xl">
{upperFirst(type)}
</Button>
</Group>
</form>
</Paper>
);
};
export default Authen;

View File

@ -0,0 +1,21 @@
.wrapper {
min-height: rem(900px);
background-size: cover;
background-image: url(https://images.unsplash.com/photo-1484242857719-4b9144542727?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1280&q=80);
}
.form {
border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
min-height: rem(900px);
max-width: rem(450px);
padding-top: rem(80px);
@media (max-width: $mantine-breakpoint-sm) {
max-width: 100%;
}
}
.title {
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
font-family: Greycliff CF, var(--mantine-font-family);
}

View File

@ -0,0 +1,27 @@
import React from 'react'
import { Card, Overlay, Button, Text } from '@mantine/core';
import classes from './ImageActionBanner.module.css';
const Banner = () => {
return (
<Card radius="md" className={classes.card}>
<Overlay className={classes.overlay} opacity={0.55} zIndex={0} />
<div className={classes.content}>
<Text size="xl" fw={700} className={classes.title}>
Banner component
</Text>
<Text size="sm" className={classes.description}>
Save up to 25% at Fifth Season Hotels in Europe, the Middle East, Africa and Asia Pacific
</Text>
<Button className={classes.action} variant="white" color="dark" size="xs">
Action button
</Button>
</div>
</Card>
);
}
export default Banner

View File

@ -0,0 +1,40 @@
.card {
height: rem(500px);
width: rem(1300px);
background-size: cover;
background-position: center;
background-image: url(https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80);
}
.content {
position: absolute;
inset: 0;
padding: var(--mantine-spacing-xl);
z-index: 1;
}
.action {
position: absolute;
bottom: var(--mantine-spacing-xl);
right: var(--mantine-spacing-xl);
}
.title {
color: var(--mantine-color-white);
margin-bottom: calc(var(--mantine-spacing-xs) / 2);
}
.description {
color: var(--mantine-color-white);
max-width: rem(220px);
}
.overlay {
background-color: transparent;
background-image: linear-gradient(
105deg,
var(--mantine-color-black) 20%,
#312f2f 50%,
var(--mantine-color-gray-4) 100%
);
}

View File

@ -0,0 +1,30 @@
import { AppShell, Box, Title } from '@mantine/core'
import React from 'react'
import Header from '../Header/Header'
import LeadGird from '../LeadGird/LeadGird'
import Banner from '../Banner/Banner'
import Footer from '../Footer/Footer'
const BasePage = ({header, setHeader, main}) => {
return (
<>
<AppShell
header={{ height: { base: 60, md: 70, lg: 80 } }}
padding="md"
footer={{ height: 320 }}
>
<AppShell.Header>
<Header header={header} setHeader={setHeader}></Header>
</AppShell.Header>
<AppShell.Main pb={0}>
{main}
</AppShell.Main>
<AppShell.Footer pos={'relative'}>
<Footer></Footer>
</AppShell.Footer>
</AppShell>
</>
)
}
export default BasePage

View File

@ -0,0 +1,26 @@
.card {
height: rem(440px);
width: 30%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
background-size: cover;
background-position: center;
}
.title {
font-family: Greycliff CF, sans-serif;
font-weight: 900;
color: var(--mantine-color-white);
line-height: 1.2;
font-size: rem(32px);
margin-top: var(--mantine-spacing-xs);
}
.category {
color: var(--mantine-color-white);
opacity: 0.7;
font-weight: 700;
text-transform: uppercase;
}

View File

@ -0,0 +1,96 @@
import React from 'react'
import { Carousel } from '@mantine/carousel';
import { useMediaQuery } from '@mantine/hooks';
import { Paper, Text, Title, Button, useMantineTheme, rem } from '@mantine/core';
import classes from './CardsCarousel.module.css';
interface CardProps {
image: string;
title: string;
category: string;
}
function Card({ image, title, category }: CardProps) {
return (
<Paper
shadow="md"
p="xl"
radius="md"
style={{ backgroundImage: `url(${image})` }}
className={classes.card}
>
<div>
<Text className={classes.category} size="xs">
{category}
</Text>
<Title order={3} className={classes.title}>
{title}
</Title>
</div>
<Button variant="white" color="dark">
Read article
</Button>
</Paper>
);
}
const data = [
{
image:
'https://images.unsplash.com/photo-1508193638397-1c4234db14d8?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
title: 'Best forests to visit in North America',
category: 'nature',
},
{
image:
'https://images.unsplash.com/photo-1559494007-9f5847c49d94?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
title: 'Hawaii beaches review: better than you think',
category: 'beach',
},
{
image:
'https://images.unsplash.com/photo-1608481337062-4093bf3ed404?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
title: 'Mountains at night: 12 best locations to enjoy the view',
category: 'nature',
},
{
image:
'https://images.unsplash.com/photo-1507272931001-fc06c17e4f43?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
title: 'Aurora in Norway: when to visit for best experience',
category: 'nature',
},
{
image:
'https://images.unsplash.com/photo-1510798831971-661eb04b3739?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
title: 'Best places to visit this winter',
category: 'tourism',
},
{
image:
'https://images.unsplash.com/photo-1582721478779-0ae163c05a60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
title: 'Active volcanos reviews: travel at your own risk',
category: 'nature',
},
];
const Carousels = () => {
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const slides = data.map((item) => (
<Carousel.Slide key={item.title} style={{backgroundColor:"blue", display:"flex", flexFlow:"row"}}>
<Card {...item} />
</Carousel.Slide>
));
return (
<Carousel
slideSize={{ base: '100%', sm: '50%' }}
slideGap={{ base: rem(2), sm: 'xl' }}
align="start"
slidesToScroll={mobile ? 1 : 2}
>
{slides}
</Carousel>
);
}
export default Carousels

View File

@ -0,0 +1,24 @@
.icon {
width: rem(22px);
height: rem(22px);
}
.dark {
@mixin dark {
display: none;
}
@mixin light {
display: block;
}
}
.light {
@mixin light {
display: none;
}
@mixin dark {
display: block;
}
}

View File

@ -1,13 +1,22 @@
import { Button, Group, useMantineColorScheme } from '@mantine/core'; import { ActionIcon, Button, Group, Switch, rem, useComputedColorScheme, useMantineColorScheme, useMantineTheme } from '@mantine/core';
import { IconSun, IconMoon } from '@tabler/icons-react';
import cx from 'clsx';
import classes from './ActionToggle.module.css';
export function ColorSchemeToggle() { export function ColorSchemeToggle() {
const { setColorScheme } = useMantineColorScheme(); const { setColorScheme } = useMantineColorScheme();
const computedColorScheme = useComputedColorScheme('light', { getInitialValueInEffect: true });
return ( return (
<Group justify="center" mt="xl"> <Group justify="center">
<Button onClick={() => setColorScheme('light')}>Light</Button> <ActionIcon
<Button onClick={() => setColorScheme('dark')}>Dark</Button> onClick={() => setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')}
<Button onClick={() => setColorScheme('auto')}>Auto</Button> variant="default"
size="md"
aria-label="Toggle color scheme"
>
<IconSun className={cx(classes.icon, classes.light)} stroke={1.5} />
<IconMoon className={cx(classes.icon, classes.dark)} stroke={1.5} />
</ActionIcon>
</Group> </Group>
); );
} }

View File

@ -0,0 +1,69 @@
import React from 'react'
import {
Text,
Title,
SimpleGrid,
TextInput,
Textarea,
Button,
Group,
ActionIcon,
} from '@mantine/core';
import { IconBrandTwitter, IconBrandYoutube, IconBrandInstagram } from '@tabler/icons-react';
import { ContactIconsList } from './ContactIcons';
import classes from './ContactUs.module.css';
const social = [IconBrandTwitter, IconBrandYoutube, IconBrandInstagram];
const Contact = () => {
const icons = social.map((Icon, index) => (
<ActionIcon key={index} size={28} className={classes.social} variant="transparent">
<Icon size="1.4rem" stroke={1.5} />
</ActionIcon>
));
return (
<div className={classes.wrapper}>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing={50}>
<div>
<Title className={classes.title}>Contact us</Title>
<Text className={classes.description} mt="sm" mb={30}>
Leave your email and we will get back to you within 24 hours
</Text>
<ContactIconsList />
<Group mt="xl">{icons}</Group>
</div>
<div className={classes.form}>
<TextInput
label="Email"
placeholder="your@email.com"
required
classNames={{ input: classes.input, label: classes.inputLabel }}
/>
<TextInput
label="Name"
placeholder="John Doe"
mt="md"
classNames={{ input: classes.input, label: classes.inputLabel }}
/>
<Textarea
required
label="Your message"
placeholder="I want to order your goods"
minRows={4}
mt="md"
classNames={{ input: classes.input, label: classes.inputLabel }}
/>
<Group justify="flex-end" mt="md">
<Button className={classes.control}>Send message</Button>
</Group>
</div>
</SimpleGrid>
</div>
);
}
export default Contact

View File

@ -0,0 +1,18 @@
.wrapper {
display: flex;
align-items: center;
color: var(--mantine-color-white);
}
.icon {
margin-right: var(--mantine-spacing-md);
background-color: transparent;
}
.title {
color: var(--mantine-color-blue-0);
}
.description {
color: var(--mantine-color-white);
}

View File

@ -0,0 +1,38 @@
import { Text, Box, Stack, rem } from '@mantine/core';
import { IconSun, IconPhone, IconMapPin, IconAt } from '@tabler/icons-react';
import classes from './ContactIcons.module.css';
interface ContactIconProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'title'> {
icon: typeof IconSun;
title: React.ReactNode;
description: React.ReactNode;
}
function ContactIcon({ icon: Icon, title, description, ...others }: ContactIconProps) {
return (
<div className={classes.wrapper} {...others}>
<Box mr="md">
<Icon style={{ width: rem(24), height: rem(24) }} />
</Box>
<div>
<Text size="xs" className={classes.title}>
{title}
</Text>
<Text className={classes.description}>{description}</Text>
</div>
</div>
);
}
const MOCKDATA = [
{ title: 'Email', description: 'hello@mantine.dev', icon: IconAt },
{ title: 'Phone', description: '+49 (800) 335 35 35', icon: IconPhone },
{ title: 'Address', description: '844 Morris Park avenue', icon: IconMapPin },
{ title: 'Working hours', description: '8 a.m. 11 p.m.', icon: IconSun },
];
export function ContactIconsList() {
const items = MOCKDATA.map((item, index) => <ContactIcon key={index} {...item} />);
return <Stack>{items}</Stack>;
}

View File

@ -0,0 +1,62 @@
.wrapper {
min-height: rem(400px);
background-image: linear-gradient(
-60deg,
var(--mantine-color-blue-4) 0%,
var(--mantine-color-blue-7) 100%
);
border-radius: var(--mantine-radius-md);
padding: calc(var(--mantine-spacing-xl) * 2.5);
@media (max-width: $mantine-breakpoint-sm) {
padding: calc(var(--mantine-spacing-xl) * 1.5);
}
}
.title {
font-family: Greycliff CF, var(--mantine-font-family);
color: var(--mantine-color-white);
line-height: 1;
}
.description {
color: var(--mantine-color-blue-0);
max-width: rem(300px);
@media (max-width: $mantine-breakpoint-sm) {
max-width: 100%;
}
}
.form {
background-color: var(--mantine-color-white);
padding: var(--mantine-spacing-xl);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-lg);
}
.social {
color: var(--mantine-color-white);
@mixin hover {
color: var(--mantine-color-blue-1);
}
}
.input {
background-color: var(--mantine-color-white);
border-color: var(--mantine-color-gray-4);
color: var(--mantine-color-black);
&::placeholder {
color: var(--mantine-color-gray-5);
}
}
.inputLabel {
color: var(--mantine-color-black);
}
.control {
background-color: var(--mantine-color-blue-6);
}

View File

@ -0,0 +1,90 @@
import React from 'react'
import { Text, Container, ActionIcon, Group, rem } from '@mantine/core';
import { IconBrandTwitter, IconBrandYoutube, IconBrandInstagram } from '@tabler/icons-react';
import { MantineLogo } from '@mantine/ds';
import classes from './FooterLinks.module.css';
const data = [
{
title: 'About',
links: [
{ label: 'Features', link: '#' },
{ label: 'Pricing', link: '#' },
{ label: 'Support', link: '#' },
{ label: 'Forums', link: '#' },
],
},
{
title: 'Project',
links: [
{ label: 'Contribute', link: '#' },
{ label: 'Media assets', link: '#' },
{ label: 'Changelog', link: '#' },
{ label: 'Releases', link: '#' },
],
},
{
title: 'Community',
links: [
{ label: 'Join Discord', link: '#' },
{ label: 'Follow on Twitter', link: '#' },
{ label: 'Email newsletter', link: '#' },
{ label: 'GitHub discussions', link: '#' },
],
},
];
const Footer = () => {
const groups = data.map((group) => {
const links = group.links.map((link, index) => (
<Text<'a'>
key={index}
className={classes.link}
component="a"
href={link.link}
onClick={(event) => event.preventDefault()}
>
{link.label}
</Text>
));
return (
<div className={classes.wrapper} key={group.title}>
<Text className={classes.title}>{group.title}</Text>
{links}
</div>
);
});
return (
<footer className={classes.footer}>
<Container className={classes.inner}>
<div className={classes.logo}>
<MantineLogo size={30} />
<Text size="xs" c="dimmed" className={classes.description}>
Build fully functional accessible web applications faster than ever
</Text>
</div>
<div className={classes.groups}>{groups}</div>
</Container>
<Container className={classes.afterFooter}>
<Text c="dimmed" size="sm">
© 2020 mantine.dev. All rights reserved.
</Text>
<Group gap={0} className={classes.social} justify="flex-end" wrap="nowrap">
<ActionIcon size="lg" color="gray" variant="subtle">
<IconBrandTwitter style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
</ActionIcon>
<ActionIcon size="lg" color="gray" variant="subtle">
<IconBrandYoutube style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
</ActionIcon>
<ActionIcon size="lg" color="gray" variant="subtle">
<IconBrandInstagram style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
</ActionIcon>
</Group>
</Container>
</footer>
);
}
export default Footer

View File

@ -0,0 +1,89 @@
.footer {
/* margin-top: rem(120px); */
padding-top: calc(var(--mantine-spacing-xl) * 2);
padding-bottom: calc(var(--mantine-spacing-xl) * 2);
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
border-top: rem(1px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
}
.logo {
max-width: rem(200px);
@media (max-width: $mantine-breakpoint-sm) {
display: flex;
flex-direction: column;
align-items: center;
}
}
.description {
margin-top: rem(5px);
@media (max-width: $mantine-breakpoint-sm) {
margin-top: var(--mantine-spacing-xs);
text-align: center;
}
}
.inner {
display: flex;
justify-content: space-between;
@media (max-width: $mantine-breakpoint-sm) {
flex-direction: column;
align-items: center;
}
}
.groups {
display: flex;
flex-wrap: wrap;
@media (max-width: $mantine-breakpoint-sm) {
display: none;
}
}
.wrapper {
width: rem(160px);
}
.link {
display: block;
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-1));
font-size: var(--mantine-font-size-sm);
padding-top: rem(3px);
padding-bottom: rem(3px);
&:hover {
text-decoration: underline;
}
}
.title {
font-size: var(--mantine-font-size-lg);
font-weight: 700;
font-family: Greycliff CF, var(--mantine-font-family);
margin-bottom: calc(var(--mantine-spacing-xs) / 2);
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
}
.afterFooter {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: var(--mantine-spacing-xl);
padding-top: var(--mantine-spacing-xl);
padding-bottom: var(--mantine-spacing-xl);
border-top: rem(1px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
@media (max-width: $mantine-breakpoint-sm) {
flex-direction: column;
}
}
.social {
@media (max-width: $mantine-breakpoint-sm) {
margin-top: var(--mantine-spacing-xs);
}
}

View File

@ -0,0 +1,18 @@
.title {
font-size: rem(26px);
font-weight: 900;
font-family: Greycliff CF, var(--mantine-font-family);
}
.controls {
@media (max-width: $mantine-breakpoint-xs) {
flex-direction: column-reverse;
}
}
.control {
@media (max-width: $mantine-breakpoint-xs) {
width: 100%;
text-align: center;
}
}

View File

@ -0,0 +1,43 @@
import React from 'react';
import {
Paper,
Title,
Text,
TextInput,
Button,
Container,
Group,
Anchor,
Center,
Box,
rem,
} from '@mantine/core';
import { IconArrowLeft } from '@tabler/icons-react';
import classes from './ForgotPassword.module.css';
const ForgotPassword = () => {
return (
<Container size={460} my={30}>
<Title className={classes.title} ta="center">
Forgot your password?
</Title>
<Text c="dimmed" fz="sm" ta="center">
Enter your email to get a reset link
</Text>
<Paper withBorder shadow="md" p={30} radius="md" mt="xl">
<TextInput label="Your email" placeholder="me@mantine.dev" required />
<Group justify="space-between" mt="lg" className={classes.controls}>
<Anchor c="dimmed" size="sm" className={classes.control}>
<Center inline>
<IconArrowLeft style={{ width: rem(12), height: rem(12) }} stroke={1.5} />
<Box ml={5}>Back to the login page</Box>
</Center>
</Anchor>
<Button className={classes.control}>Reset password</Button>
</Group>
</Paper>
</Container>
);
};
export default ForgotPassword;

View File

@ -0,0 +1,36 @@
import React from 'react'
import { Button, ButtonProps } from '@mantine/core';
function GoogleIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
viewBox="0 0 256 262"
style={{ width: '0.9rem', height: '0.9rem' }}
{...props}
>
<path
fill="#4285F4"
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
/>
<path
fill="#34A853"
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
/>
<path
fill="#FBBC05"
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
/>
<path
fill="#EB4335"
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
/>
</svg>
);
}
const GoogleButton = (props: ButtonProps & React.ComponentPropsWithoutRef<'button'>) => {
return <Button leftSection={<GoogleIcon />} variant="default" {...props} />;
}
export default GoogleButton

View File

@ -0,0 +1,58 @@
.header {
height: rem(84px);
margin-bottom: rem(80px);
background-color: var(--mantine-color-body);
border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
.inner {
height: rem(84px);
display: flex;
align-items: center;
justify-content: space-between;
}
.links {
padding-top: var(--mantine-spacing-lg);
height: rem(84px);
display: flex;
flex-direction: column;
justify-content: space-between;
width: rem(600px);
}
.mainLinks {
margin-right: calc(var(--mantine-spacing-sm) * -1);
}
.mainLink {
text-transform: uppercase;
font-size: var(--mantine-font-size-xs);
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-1));
padding: rem(7px) var(--mantine-spacing-sm);
font-weight: 700;
border-bottom: rem(2px) solid transparent;
transition: border-color 100ms ease, color 100ms ease;
@mixin hover {
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
text-decoration: none;
}
&[data-active] {
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
border-bottom-color: var(--mantine-color-blue-6);
}
}
.secondaryLink {
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-1));
font-size: var(--mantine-font-size-xs);
text-transform: uppercase;
transition: color 100ms ease;
@mixin hover {
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
text-decoration: none;
}
}

View File

@ -0,0 +1,82 @@
import React from 'react';
import { useState } from 'react';
import { Container, Anchor, Group, Burger, Box } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { MantineLogo } from '@mantine/ds';
import classes from './DoubleHeader.module.css';
import { ColorSchemeToggle } from '../ColorSchemeToggle/ColorSchemeToggle';
import LanguagePicker from '../LanguagePicker/LanguagePicker';
const userLinks = [
{ link: '#', label: 'Privacy & Security' },
{ link: '#', label: 'Account settings' },
{ link: '#', label: 'Support options' },
];
const mainLinks = [
{ link: '#', label: 'Introduction' },
{ link: '#', label: 'Authentication' },
{ link: '#', label: 'Data' },
{ link: '#', label: 'Contact' },
{ link: '#', label: 'form element' },
];
const Header = ({ header, setHeader }) => {
const [opened, { toggle }] = useDisclosure(false);
const [active, setActive] = useState(0);
const mainItems = mainLinks.map((item, index) => (
<Anchor<'a'>
href={item.link}
key={item.label}
className={classes.mainLink}
data-active={index === active || undefined}
onClick={(event) => {
event.preventDefault();
setHeader(index);
setActive(index);
}}
>
{item.label}
</Anchor>
));
const secondaryItems = userLinks.map((item) => (
<Anchor
href={item.link}
key={item.label}
onClick={(event) => event.preventDefault()}
className={classes.secondaryLink}
>
{item.label}
</Anchor>
));
// console.log(header);
return (
<header className={classes.header}>
<Container className={classes.inner} size={'xl'}>
<MantineLogo size={34} />
<Box className={classes.links} visibleFrom="md">
<Group justify="flex-end">{secondaryItems}</Group>
<Group gap={0} justify="flex-end" className={classes.mainLinks}>
{mainItems}
</Group>
</Box>
<Burger
opened={opened}
onClick={toggle}
className={classes.burger}
size="sm"
hiddenFrom="sm"
/>
<Box size={'sm'} display={'flex'}>
<LanguagePicker></LanguagePicker>
<ColorSchemeToggle />
</Box>
</Container>
</header>
);
};
export default Header;

View File

@ -0,0 +1,33 @@
.control {
width: rem(150px);
display: flex;
justify-content: space-between;
align-items: center;
padding: rem(5px) rem(8px);
border-radius: var(--mantine-radius-md);
border: rem(1px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6));
transition: background-color 150ms ease;
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
margin-right: rem(5px);
&[data-expanded] {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-5));
}
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-5));
}
}
.label {
font-weight: 500;
font-size: var(--mantine-font-size-sm);
}
.icon {
transition: transform 150ms ease;
transform: rotate(0deg);
[data-expanded] & {
transform: rotate(180deg);
}
}

View File

@ -0,0 +1,48 @@
import React from 'react'
import { useState } from 'react';
import { UnstyledButton, Menu, Image, Group } from '@mantine/core';
import { IconChevronDown } from '@tabler/icons-react';
import english from "./images/english.png";
import vietnam from "./images/vietnam.png";
import classes from './LanguagePicker.module.css';
const data = [
{ label: 'English', image: english },
{ label: 'Vietnamses', image: vietnam }
];
const LanguagePicker = () => {
const [opened, setOpened] = useState(false);
const [selected, setSelected] = useState(data[0]);
const items = data.map((item) => (
<Menu.Item
leftSection={<Image src={item.image} width={18} height={18} />}
onClick={() => setSelected(item)}
key={item.label}
>
{item.label}
</Menu.Item>
));
return (
<Menu
onOpen={() => setOpened(true)}
onClose={() => setOpened(false)}
radius="sm"
width="target"
withinPortal
>
<Menu.Target>
<UnstyledButton className={classes.control} data-expanded={opened || undefined}>
<Group gap="xs">
<Image src={selected.image} width={15} height={15} />
<span className={classes.label}>{selected.label}</span>
</Group>
<IconChevronDown size="1rem" className={classes.icon} stroke={1.5} />
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>{items}</Menu.Dropdown>
</Menu>
);
}
export default LanguagePicker

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,78 @@
import React from 'react';
import {
Container,
Grid,
SimpleGrid,
Skeleton,
rem,
Image,
BackgroundImage,
Center,
Text,
Flex,
Button,
Tooltip,
} from '@mantine/core';
import image1 from '../../img/pexels-irina-iriser-1379640.jpg';
import image2 from '../../img/pexels-eberhard-grossgasteiger-640781.jpg';
import image3 from '../../img/pexels-pixabay-51387.jpg';
import image4 from '../../img/pexels-pok-rie-982263.jpg';
import { useToggle } from '@mantine/hooks';
const PRIMARY_COL_HEIGHT = rem(500);
const LeadGird = () => {
let images = [image1, image2, image3, image4]
const [value, toggle] = useToggle([0, 1, 2, 3]);
const SECONDARY_COL_HEIGHT = `calc(${PRIMARY_COL_HEIGHT} / 2 - var(--mantine-spacing-md) / 2)`;
return (
<Container my="xl" size={'xl'}>
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="md">
<BackgroundImage radius="md" src={images[value]} style={{ height: PRIMARY_COL_HEIGHT }}>
{/* <Center p="md"> */}
<Flex
h={'100%'}
gap="xs"
justify="flex-start"
align="end"
direction="row"
wrap="wrap"
p={30}
fs={'italic'}
bg="rgba(0, 0, 0, .3)"
c={"white"}
>
{'<Flex >'}
<br></br>
Flex component is an alternative to Group and Stack. Flex is more flexible, it allows
creating both horizontal and vertical flexbox layouts, but requires more configuration.
Unlike Group and Stack Flex is polymorphic and supports responsive props.
<Text c="white" fw={600}>
BackgroundImage component can be used to add any content on image. It is useful for
hero headers and other similar sections
</Text>
{'</Flex>'}
</Flex>
{/* </Center> */}
</BackgroundImage>
<Grid gutter="md">
<Grid.Col>
<Image radius="md" src={value+2>3?images[value+2-4]:images[value+2]} style={{ height: SECONDARY_COL_HEIGHT }} />
</Grid.Col>
<Grid.Col span={6}>
<Image radius="md" src={value+1>3?images[value+1-4]:images[value+1]} style={{ height: SECONDARY_COL_HEIGHT }} />
</Grid.Col>
<Grid.Col span={6}>
<Image radius="md" src={value+3>3?images[value+3-4]:images[value+3]} style={{ height: SECONDARY_COL_HEIGHT }} />
</Grid.Col>
</Grid>
</SimpleGrid>
<Tooltip label='Toggle button'>
<Button mt={10} onClick={() => {toggle()}}>
Click
</Button>
</Tooltip>
</Container>
);
};
export default LeadGird;

View File

@ -0,0 +1,16 @@
.wrapper {
padding-top: calc(var(--mantine-spacing-xl) * 2);
padding-bottom: calc(var(--mantine-spacing-xl) * 2);
}
.title {
margin-bottom: var(--mantine-spacing-md);
padding-left: var(--mantine-spacing-md);
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
font-family: Greycliff CF, var(--mantine-font-family);
}
.item {
font-size: var(--mantine-font-size-sm);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
}

View File

@ -0,0 +1,51 @@
import React from 'react';
import { Image, Accordion, Grid, Container, Title } from '@mantine/core';
import image from './image.svg';
import classes from './FaqWithImage.module.css';
const placeholder =
'It cant help but hear a pin drop from over half a mile away, so it lives deep in the mountains where there arent many people or Pokémon.';
const Questions = () => {
return (
<div className={classes.wrapper}>
<Container size="lg">
<Grid id="faq-grid" gutter={50}>
<Grid.Col span={{ base: 12, md: 6 }}>
<Image src={image} alt="Frequently Asked Questions" />
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6 }}>
<Title order={2} ta="left" className={classes.title}>
Frequently Asked Questions
</Title>
<Accordion chevronPosition="right" defaultValue="reset-password" variant="separated">
<Accordion.Item className={classes.item} value="reset-password">
<Accordion.Control>How can I reset my password?</Accordion.Control>
<Accordion.Panel>{placeholder}</Accordion.Panel>
</Accordion.Item>
<Accordion.Item className={classes.item} value="another-account">
<Accordion.Control>Can I create more that one account?</Accordion.Control>
<Accordion.Panel>{placeholder}</Accordion.Panel>
</Accordion.Item>
<Accordion.Item className={classes.item} value="newsletter">
<Accordion.Control>How can I subscribe to monthly newsletter?</Accordion.Control>
<Accordion.Panel>{placeholder}</Accordion.Panel>
</Accordion.Item>
<Accordion.Item className={classes.item} value="credit-card">
<Accordion.Control>
Do you store credit card information securely?
</Accordion.Control>
<Accordion.Panel>{placeholder}</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Grid.Col>
</Grid>
</Container>
</div>
);
};
export default Questions;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -0,0 +1,239 @@
import React from 'react'
import { useState } from 'react';
import {
Table,
ScrollArea,
UnstyledButton,
Group,
Text,
Center,
TextInput,
rem,
keys,
} from '@mantine/core';
import { IconSelector, IconChevronDown, IconChevronUp, IconSearch } from '@tabler/icons-react';
import classes from './TableSort.module.css';
interface RowData {
name: string;
email: string;
company: string;
}
interface ThProps {
children: React.ReactNode;
reversed: boolean;
sorted: boolean;
onSort(): void;
}
function Th({ children, reversed, sorted, onSort }: ThProps) {
const Icon = sorted ? (reversed ? IconChevronUp : IconChevronDown) : IconSelector;
return (
<Table.Th className={classes.th}>
<UnstyledButton onClick={onSort} className={classes.control}>
<Group justify="space-between">
<Text fw={500} fz="sm">
{children}
</Text>
<Center className={classes.icon}>
<Icon style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
</Center>
</Group>
</UnstyledButton>
</Table.Th>
);
}
function filterData(data: RowData[], search: string) {
const query = search.toLowerCase().trim();
return data.filter((item) =>
keys(data[0]).some((key) => item[key].toLowerCase().includes(query))
);
}
function sortData(
data: RowData[],
payload: { sortBy: keyof RowData | null; reversed: boolean; search: string }
) {
const { sortBy } = payload;
if (!sortBy) {
return filterData(data, payload.search);
}
return filterData(
[...data].sort((a, b) => {
if (payload.reversed) {
return b[sortBy].localeCompare(a[sortBy]);
}
return a[sortBy].localeCompare(b[sortBy]);
}),
payload.search
);
}
const data = [
{
name: 'Athena Weissnat',
company: 'Little - Rippin',
email: 'Elouise.Prohaska@yahoo.com',
},
{
name: 'Deangelo Runolfsson',
company: 'Greenfelder - Krajcik',
email: 'Kadin_Trantow87@yahoo.com',
},
{
name: 'Danny Carter',
company: 'Kohler and Sons',
email: 'Marina3@hotmail.com',
},
{
name: 'Trace Tremblay PhD',
company: 'Crona, Aufderhar and Senger',
email: 'Antonina.Pouros@yahoo.com',
},
{
name: 'Derek Dibbert',
company: 'Gottlieb LLC',
email: 'Abagail29@hotmail.com',
},
{
name: 'Viola Bernhard',
company: 'Funk, Rohan and Kreiger',
email: 'Jamie23@hotmail.com',
},
{
name: 'Austin Jacobi',
company: 'Botsford - Corwin',
email: 'Genesis42@yahoo.com',
},
{
name: 'Hershel Mosciski',
company: 'Okuneva, Farrell and Kilback',
email: 'Idella.Stehr28@yahoo.com',
},
{
name: 'Mylene Ebert',
company: 'Kirlin and Sons',
email: 'Hildegard17@hotmail.com',
},
{
name: 'Lou Trantow',
company: 'Parisian - Lemke',
email: 'Hillard.Barrows1@hotmail.com',
},
{
name: 'Dariana Weimann',
company: 'Schowalter - Donnelly',
email: 'Colleen80@gmail.com',
},
{
name: 'Dr. Christy Herman',
company: 'VonRueden - Labadie',
email: 'Lilyan98@gmail.com',
},
{
name: 'Katelin Schuster',
company: 'Jacobson - Smitham',
email: 'Erich_Brekke76@gmail.com',
},
{
name: 'Melyna Macejkovic',
company: 'Schuster LLC',
email: 'Kylee4@yahoo.com',
},
{
name: 'Pinkie Rice',
company: 'Wolf, Trantow and Zulauf',
email: 'Fiona.Kutch@hotmail.com',
},
{
name: 'Brain Kreiger',
company: 'Lueilwitz Group',
email: 'Rico98@hotmail.com',
},
];
const TableData = () => {
const [search, setSearch] = useState('');
const [sortedData, setSortedData] = useState(data);
const [sortBy, setSortBy] = useState<keyof RowData | null>(null);
const [reverseSortDirection, setReverseSortDirection] = useState(false);
const setSorting = (field: keyof RowData) => {
const reversed = field === sortBy ? !reverseSortDirection : false;
setReverseSortDirection(reversed);
setSortBy(field);
setSortedData(sortData(data, { sortBy: field, reversed, search }));
};
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
setSearch(value);
setSortedData(sortData(data, { sortBy, reversed: reverseSortDirection, search: value }));
};
const rows = sortedData.map((row) => (
<Table.Tr key={row.name}>
<Table.Td>{row.name}</Table.Td>
<Table.Td>{row.email}</Table.Td>
<Table.Td>{row.company}</Table.Td>
</Table.Tr>
));
return (
<ScrollArea>
<TextInput
placeholder="Search by any field"
mb="md"
leftSection={<IconSearch style={{ width: rem(16), height: rem(16) }} stroke={1.5} />}
value={search}
onChange={handleSearchChange}
/>
<Table horizontalSpacing="md" verticalSpacing="xs" miw={700} layout="fixed">
<Table.Tbody>
<Table.Tr>
<Th
sorted={sortBy === 'name'}
reversed={reverseSortDirection}
onSort={() => setSorting('name')}
>
Name
</Th>
<Th
sorted={sortBy === 'email'}
reversed={reverseSortDirection}
onSort={() => setSorting('email')}
>
Email
</Th>
<Th
sorted={sortBy === 'company'}
reversed={reverseSortDirection}
onSort={() => setSorting('company')}
>
Company
</Th>
</Table.Tr>
</Table.Tbody>
<Table.Tbody>
{rows.length > 0 ? (
rows
) : (
<Table.Tr>
<Table.Td colSpan={Object.keys(data[0]).length}>
<Text fw={500} ta="center">
Nothing found
</Text>
</Table.Td>
</Table.Tr>
)}
</Table.Tbody>
</Table>
</ScrollArea>
);
}
export default TableData

View File

@ -0,0 +1,18 @@
.th {
padding: 0;
}
.control {
width: 100%;
padding: var(--mantine-spacing-xs) var(--mantine-spacing-md);
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}
}
.icon {
width: rem(21px);
height: rem(21px);
border-radius: rem(21px);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View File

@ -1,11 +0,0 @@
import { Welcome } from '../components/Welcome/Welcome';
import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeToggle';
export function HomePage() {
return (
<>
<Welcome />
<ColorSchemeToggle />
</>
);
}

View File

View File

@ -0,0 +1,196 @@
import { AppShell, Box, Title, useMantineTheme } from '@mantine/core';
import Banner from '../../components/Banner/Banner';
import Footer from '../../components/Footer/Footer';
import Header from '../../components/Header/Header';
import LeadGird from '../../components/LeadGird/LeadGird';
import { useState } from 'react';
import BasePage from '../../components/BasePage/BasePage';
import Authen from '../../components/Authen/Authen';
import ForgotPassword from '../../components/ForgotPassword/ForgotPassword';
import TableData from '../../components/TableData/TableData';
import Questions from '../../components/Questions/Questions';
import Contact from '../../components/Contact/Contact';
export function HomePage() {
const theme = useMantineTheme();
const [header, setHeader] = useState(0);
switch (header) {
case 0:
return (
<BasePage
header={header}
setHeader={setHeader}
main={
<>
<Box
my={'xl'}
style={{ justifyContent: 'center', justifyItems: 'center', display: 'flex' }}
>
<Banner></Banner>
</Box>
<Box ml={150}>
<Title order={1}>LeadGird component</Title>
</Box>
<LeadGird></LeadGird>
</>
}
></BasePage>
);
case 1:
return (
<BasePage
header={header}
setHeader={setHeader}
main={
<>
<Box my={'xl'}>
<Title ml={150} order={1}>
Login form
</Title>
<Box
w={'100%'}
style={{
justifyContent: 'center',
justifyItems: 'center',
display: 'flex',
}}
>
<Authen w={'25%'} />
</Box>
</Box>
<Box>
<Title ml={150} order={1}>
Forgot password form
</Title>
<ForgotPassword />
</Box>
</>
}
></BasePage>
);
case 2:
return (
<BasePage
header={header}
setHeader={setHeader}
main={
<>
<Box my={'xl'}>
<Title ml={150} order={1}>
Data table
</Title>
<Box
w={'100%'}
style={{
justifyContent: 'center',
justifyItems: 'center',
display: 'flex',
}}
>
<Box w={'60%'} p={20} style={{ border: 'solid 1px gray', borderRadius: 10 }}>
<TableData />
</Box>
</Box>
</Box>
<Box>
<Title ml={150} order={1}>
Frequently Asked Questions
</Title>
<Questions />
</Box>
</>
}
></BasePage>
);
case 3:
return (
<BasePage
header={header}
setHeader={setHeader}
main={
<>
<Box
my={'xl'}
style={{
justifyContent: 'center',
justifyItems: 'center',
display: 'flex',
}}
>
<Box w={'60%'}>
<Contact />
</Box>
</Box>
</>
}
></BasePage>
);
case 4:
return(
<BasePage
header={header}
setHeader={setHeader}
main={
<>
<Box
my={'xl'}
style={{
justifyContent: 'center',
justifyItems: 'center',
display: 'flex', flexFlow:"column"
}}
>
<Title ml={150} order={1}>
Button
</Title>
<iframe height={"1000"} src='https://mantine.dev/core/button/'></iframe>
</Box>
<Box
my={'xl'}
style={{
justifyContent: 'center',
justifyItems: 'center',
display: 'flex', flexFlow:"column"
}}
>
<Title ml={150} order={1}>
Input
</Title>
<iframe height={"1000"} src='https://ui.mantine.dev/category/inputs/'></iframe>
</Box>
<Box
my={'xl'}
style={{
justifyContent: 'center',
justifyItems: 'center',
display: 'flex', flexFlow:"column"
}}
>
<Title ml={150} order={1}>
Input
</Title>
<iframe height={"1000"} src='https://ui.mantine.dev/category/inputs/'></iframe>
</Box>
<Box
my={'xl'}
style={{
justifyContent: 'center',
justifyItems: 'center',
display: 'flex', flexFlow:"column"
}}
>
<Title ml={150} order={1}>
Dropzones
</Title>
<iframe height={"800"} src='https://ui.mantine.dev/category/dropzones/'></iframe>
</Box>
</>
}
></BasePage>
)
}
}

13513
yarn.lock

File diff suppressed because it is too large Load Diff