first commit
|
|
@ -17,7 +17,10 @@
|
|||
"storybook:build": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/carousel": "^7.1.7",
|
||||
"@mantine/core": "7.1.7",
|
||||
"@mantine/ds": "^7.1.7",
|
||||
"@mantine/form": "^7.2.0",
|
||||
"@mantine/hooks": "7.1.7",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import { HomePage } from './pages/Home.page';
|
||||
import { HomePage } from './pages/Home/Home.page';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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%
|
||||
);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
const { setColorScheme } = useMantineColorScheme();
|
||||
const computedColorScheme = useComputedColorScheme('light', { getInitialValueInEffect: true });
|
||||
|
||||
return (
|
||||
<Group justify="center" mt="xl">
|
||||
<Button onClick={() => setColorScheme('light')}>Light</Button>
|
||||
<Button onClick={() => setColorScheme('dark')}>Dark</Button>
|
||||
<Button onClick={() => setColorScheme('auto')}>Auto</Button>
|
||||
<Group justify="center">
|
||||
<ActionIcon
|
||||
onClick={() => setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -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;
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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 can’t help but hear a pin drop from over half a mile away, so it lives deep in the mountains where there aren’t 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;
|
||||
|
After Width: | Height: | Size: 94 KiB |
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 518 KiB |
|
|
@ -1,11 +0,0 @@
|
|||
import { Welcome } from '../components/Welcome/Welcome';
|
||||
import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeToggle';
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<Welcome />
|
||||
<ColorSchemeToggle />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||