Compare commits
No commits in common. "master" and "main" have entirely different histories.
|
|
@ -1,2 +0,0 @@
|
|||
*.js
|
||||
vite.config.ts
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
module.exports = {
|
||||
extends: ['mantine'],
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
rules: {
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
},
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
@ -1 +0,0 @@
|
|||
module.exports = require('eslint-config-mantine/.prettierrc.js');
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.story.@(js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-styling', 'storybook-dark-mode'],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import '@mantine/core/styles.css';
|
||||
import React, { useEffect } from 'react';
|
||||
import { addons } from '@storybook/preview-api';
|
||||
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
|
||||
import { MantineProvider, useMantineColorScheme } from '@mantine/core';
|
||||
import { theme } from '../src/theme';
|
||||
|
||||
const channel = addons.getChannel();
|
||||
|
||||
function ColorSchemeWrapper({ children }: { children: React.ReactNode }) {
|
||||
const { setColorScheme } = useMantineColorScheme();
|
||||
const handleColorScheme = (value: boolean) => setColorScheme(value ? 'dark' : 'light');
|
||||
|
||||
useEffect(() => {
|
||||
channel.on(DARK_MODE_EVENT_NAME, handleColorScheme);
|
||||
return () => channel.off(DARK_MODE_EVENT_NAME, handleColorScheme);
|
||||
}, [channel]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export const decorators = [
|
||||
(renderStory: any) => <ColorSchemeWrapper>{renderStory()}</ColorSchemeWrapper>,
|
||||
(renderStory: any) => <MantineProvider theme={theme}>{renderStory()}</MantineProvider>,
|
||||
];
|
||||
35
README.md
|
|
@ -1,34 +1,3 @@
|
|||
# Mantine Vite template
|
||||
# mantine-template
|
||||
|
||||
## Features
|
||||
|
||||
This template comes with the following features:
|
||||
|
||||
- [PostCSS](https://postcss.org/) with [mantine-postcss-preset](https://mantine.dev/styles/postcss-preset)
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [Storybook](https://storybook.js.org/)
|
||||
- [Jest](https://jestjs.io/) setup with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)
|
||||
- ESLint setup with [eslint-config-mantine](https://github.com/mantinedev/eslint-config-mantine)
|
||||
|
||||
## npm scripts
|
||||
|
||||
## Build and dev scripts
|
||||
|
||||
- `dev` – start development server
|
||||
- `build` – build production version of the app
|
||||
- `preview` – locally preview production build
|
||||
|
||||
### Testing scripts
|
||||
|
||||
- `typecheck` – checks TypeScript types
|
||||
- `lint` – runs ESLint
|
||||
- `prettier:check` – checks files with Prettier
|
||||
- `jest` – runs jest tests
|
||||
- `jest:watch` – starts jest watch
|
||||
- `test` – runs `jest`, `prettier:check`, `lint` and `typecheck` scripts
|
||||
|
||||
### Other scripts
|
||||
|
||||
- `storybook` – starts storybook dev server
|
||||
- `storybook:build` – build production storybook bundle to `storybook-static`
|
||||
- `prettier:write` – formats all files with Prettier
|
||||
Tổng hợp các component khả dụng
|
||||
16
index.html
|
|
@ -1,16 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
|
||||
/>
|
||||
<title>Vite + Mantine App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||
moduleNameMapper: {
|
||||
'^@test-utils': '<rootDir>/test-utils',
|
||||
'\\.css$': 'identity-obj-proxy',
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.ts?$': 'ts-jest',
|
||||
},
|
||||
};
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
require('@testing-library/jest-dom/extend-expect');
|
||||
|
||||
const { getComputedStyle } = window;
|
||||
window.getComputedStyle = (elt) => getComputedStyle(elt);
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
window.ResizeObserver = ResizeObserver;
|
||||
70
package.json
|
|
@ -1,70 +0,0 @@
|
|||
{
|
||||
"name": "mantine-vite-template",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src",
|
||||
"prettier": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"prettier:write": "prettier --write \"**/*.{ts,tsx}\"",
|
||||
"jest": "jest",
|
||||
"jest:watch": "jest --watch",
|
||||
"test": "npm run typecheck && npm run prettier && npm run lint && npm run jest && npm run build",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"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",
|
||||
"react-router-dom": "^6.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^7.0.18",
|
||||
"@storybook/addon-interactions": "^7.0.18",
|
||||
"@storybook/addon-links": "^7.0.18",
|
||||
"@storybook/addon-styling": "^1.0.8",
|
||||
"@storybook/blocks": "^7.0.18",
|
||||
"@storybook/react": "^7.0.18",
|
||||
"@storybook/react-vite": "^7.0.18",
|
||||
"@storybook/testing-library": "^0.0.14-next.2",
|
||||
"@testing-library/dom": "^9.3.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/react": "^18.2.7",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-mantine": "2.0.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-preset-mantine": "1.9.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prop-types": "^15.8.1",
|
||||
"storybook": "^7.0.18",
|
||||
"storybook-dark-mode": "^3.0.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.9"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
'mantine-breakpoint-xs': '36em',
|
||||
'mantine-breakpoint-sm': '48em',
|
||||
'mantine-breakpoint-md': '62em',
|
||||
'mantine-breakpoint-lg': '75em',
|
||||
'mantine-breakpoint-xl': '88em',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
12
src/App.tsx
|
|
@ -1,12 +0,0 @@
|
|||
import '@mantine/core/styles.css';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import { Router } from './Router';
|
||||
import { theme } from './theme';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<MantineProvider theme={theme}>
|
||||
<Router />
|
||||
</MantineProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import { HomePage } from './pages/Home/Home.page';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <HomePage />,
|
||||
},
|
||||
]);
|
||||
|
||||
export function Router() {
|
||||
return <RouterProvider router={router} />;
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
.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);
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
.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%
|
||||
);
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
.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;
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
.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,22 +0,0 @@
|
|||
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">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.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);
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
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>;
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
.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);
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
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
|
||||
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,78 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
.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));
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
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;
|
||||
|
Before Width: | Height: | Size: 94 KiB |
|
|
@ -1,239 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.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);
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
.title {
|
||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
||||
font-size: rem(100px);
|
||||
font-weight: 900;
|
||||
letter-spacing: rem(-2px);
|
||||
|
||||
@media (max-width: $mantine-breakpoint-md) {
|
||||
font-size: rem(50px);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Welcome } from './Welcome';
|
||||
|
||||
export default {
|
||||
title: 'Welcome',
|
||||
};
|
||||
|
||||
export const Usage = () => <Welcome />;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { render, screen } from '@test-utils';
|
||||
import { Welcome } from './Welcome';
|
||||
|
||||
describe('Welcome component', () => {
|
||||
it('has correct Vite guide link', () => {
|
||||
render(<Welcome />);
|
||||
expect(screen.getByText('this guide')).toHaveAttribute(
|
||||
'href',
|
||||
'https://mantine.dev/guides/vite/'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { Title, Text, Anchor } from '@mantine/core';
|
||||
import classes from './Welcome.module.css';
|
||||
|
||||
export function Welcome() {
|
||||
return (
|
||||
<>
|
||||
<Title className={classes.title} ta="center" mt={100}>
|
||||
Welcome to{' '}
|
||||
<Text inherit variant="gradient" component="span" gradient={{ from: 'pink', to: 'yellow' }}>
|
||||
Mantine
|
||||
</Text>
|
||||
</Title>
|
||||
<Text c="dimmed" ta="center" size="lg" maw={580} mx="auto" mt="xl">
|
||||
This starter Vite project includes a minimal setup, if you want to learn more on Mantine +
|
||||
Vite integration follow{' '}
|
||||
<Anchor href="https://mantine.dev/guides/vite/" size="lg">
|
||||
this guide
|
||||
</Anchor>
|
||||
. To get started edit pages/Home.page.tsx file.
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 163 163"><path fill="#339AF0" d="M162.162 81.5c0-45.011-36.301-81.5-81.08-81.5C36.301 0 0 36.489 0 81.5 0 126.51 36.301 163 81.081 163s81.081-36.49 81.081-81.5z"/><path fill="#fff" d="M65.983 43.049a6.234 6.234 0 00-.336 6.884 6.14 6.14 0 001.618 1.786c9.444 7.036 14.866 17.794 14.866 29.52 0 11.726-5.422 22.484-14.866 29.52a6.145 6.145 0 00-1.616 1.786 6.21 6.21 0 00-.694 4.693 6.21 6.21 0 001.028 2.186 6.151 6.151 0 006.457 2.319 6.154 6.154 0 002.177-1.035 50.083 50.083 0 007.947-7.39h17.493c3.406 0 6.174-2.772 6.174-6.194s-2.762-6.194-6.174-6.194h-9.655a49.165 49.165 0 004.071-19.69 49.167 49.167 0 00-4.07-19.692h9.66c3.406 0 6.173-2.771 6.173-6.194 0-3.422-2.762-6.193-6.173-6.193H82.574a50.112 50.112 0 00-7.952-7.397 6.15 6.15 0 00-4.578-1.153 6.189 6.189 0 00-4.055 2.438h-.006z"/><path fill="#fff" fill-rule="evenodd" d="M56.236 79.391a9.342 9.342 0 01.632-3.608 9.262 9.262 0 011.967-3.077 9.143 9.143 0 012.994-2.063 9.06 9.06 0 017.103 0 9.145 9.145 0 012.995 2.063 9.262 9.262 0 011.967 3.077 9.339 9.339 0 01-2.125 10.003 9.094 9.094 0 01-6.388 2.63 9.094 9.094 0 01-6.39-2.63 9.3 9.3 0 01-2.755-6.395z" clip-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 518 KiB |
|
|
@ -1,4 +0,0 @@
|
|||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { createTheme } from '@mantine/core';
|
||||
|
||||
export const theme = createTheme({
|
||||
/** Put your mantine theme override here */
|
||||
});
|
||||
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
export * from '@testing-library/react';
|
||||
export { render } from './render';
|
||||
export { userEvent };
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { render as testingLibraryRender } from '@testing-library/react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import { theme } from '../src/theme';
|
||||
|
||||
export function render(ui: React.ReactNode) {
|
||||
return testingLibraryRender(<>{ui}</>, {
|
||||
wrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider theme={theme}>{children}</MantineProvider>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@test-utils": ["./test-utils"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "test-utils"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
});
|
||||