first commit
58
.gitignore
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Node.js dependencies
|
||||
node_modules/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Build output
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Dependency directories
|
||||
.cache/
|
||||
.docker/
|
||||
|
||||
# Coverage reports
|
||||
coverage/
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# IDE-specific files
|
||||
.idea/
|
||||
*.iml
|
||||
.vscode/
|
||||
*.sublime-workspace
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Generated files
|
||||
*.lock
|
||||
*.env.local
|
||||
*.env.development.local
|
||||
*.env.test.local
|
||||
*.env.production.local
|
||||
|
||||
# MacOS specific files
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Windows specific files
|
||||
desktop.ini
|
||||
|
||||
# Miscellaneous
|
||||
!.gitkeep
|
8
.prettierrc
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"bracketSpacing": true
|
||||
}
|
||||
|
46
README.md
|
@ -1,2 +1,46 @@
|
|||
# bulk-email
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
|
16096
package-lock.json
generated
Normal file
76
package.json
Normal file
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"name": "digi-ev-admin-dashboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^6.4.5",
|
||||
"@mui/material": "^6.4.5",
|
||||
"@mui/x-charts": "^7.27.1",
|
||||
"@mui/x-data-grid": "^7.27.0",
|
||||
"@mui/x-date-pickers": "^7.27.0",
|
||||
"@react-spring/web": "^9.7.5",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"add": "^2.0.6",
|
||||
"axios": "^1.7.9",
|
||||
"clsx": "^2.1.1",
|
||||
"cra-template-typescript": "1.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"highcharts": "^12.1.2",
|
||||
"mui-phone-number": "^3.0.3",
|
||||
"mui-tel-input": "^7.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.0.0",
|
||||
"react-cookie": "^7.2.2",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-minimal-pie-chart": "^9.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"recharts": "^2.15.1",
|
||||
"sonner": "^1.7.4",
|
||||
"uuid": "^11.1.0",
|
||||
"web-vitals": "^4.2.4",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/react": "^19.0.4",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"eslint-config-react-app": "7.0.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": []
|
||||
}
|
||||
}
|
21011
pnpm-lock.yaml
Normal file
BIN
public/Bell.jpg
Normal file
After Width: | Height: | Size: 1,008 B |
BIN
public/DigiEVLogo.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
public/Digiev.png
Normal file
After Width: | Height: | Size: 246 KiB |
BIN
public/Digilogo.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
4
public/Group 14.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="535" height="226" viewBox="0 0 535 226" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M43.561 68.1C55.753 68.1 65.913 72.3757 74.041 80.927C82.2537 89.4783 86.36 100.019 86.36 112.55C86.36 125.081 82.2537 135.622 74.041 144.173C65.913 152.724 55.753 157 43.561 157H8.255V68.1H43.561ZM43.561 140.236C51.2657 140.236 57.531 137.696 62.357 132.616C67.183 127.451 69.596 120.763 69.596 112.55C69.596 104.337 67.183 97.691 62.357 92.611C57.531 87.4463 51.2657 84.864 43.561 84.864H25.781V140.236H43.561ZM98.7921 68.1H116.318V157H98.7921V68.1ZM220.024 109.248V116.106C220.024 128.806 215.96 139.093 207.832 146.967C199.704 154.841 189.205 158.778 176.336 158.778C162.62 158.778 151.232 154.333 142.173 145.443C133.198 136.468 128.711 125.546 128.711 112.677C128.711 99.723 133.156 88.7587 142.046 79.784C151.02 70.8093 162.112 66.322 175.32 66.322C183.617 66.322 191.195 68.227 198.053 72.037C204.911 75.7623 210.245 80.7577 214.055 87.023L199.069 95.659C196.952 92.0183 193.735 89.055 189.417 86.769C185.183 84.483 180.442 83.34 175.193 83.34C166.726 83.34 159.741 86.134 154.238 91.722C148.819 97.2253 146.11 104.21 146.11 112.677C146.11 121.059 148.861 128.002 154.365 133.505C159.953 138.924 167.319 141.633 176.463 141.633C183.236 141.633 188.824 140.151 193.227 137.188C197.714 134.14 200.762 129.991 202.371 124.742H175.447V109.248H220.024ZM232.489 68.1H250.015V157H232.489V68.1ZM410.998 140.236H448.463V157H393.472V68.1H447.828V84.864H410.998V103.787H444.653V120.297H410.998V140.236ZM482.788 157L452.943 68.1H471.993L493.71 136.426L515.3 68.1H534.477L504.505 157H482.788Z" fill="white"/>
|
||||
<path d="M374.268 108.459L324.874 94.3623L347.782 14.0969L264.907 117.311L314.302 131.408L291.394 211.673L374.268 108.459Z" fill="#52ACDF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/Jobopro.png
Normal file
After Width: | Height: | Size: 157 KiB |
BIN
public/Jobopro1.png
Normal file
After Width: | Height: | Size: 24 KiB |
27
public/Login.svg
Normal file
After Width: | Height: | Size: 14 MiB |
BIN
public/apple_store.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
public/arrow-top-right.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
public/avatar.png
Normal file
After Width: | Height: | Size: 3.9 MiB |
28
public/bg.svg
Normal file
|
@ -0,0 +1,28 @@
|
|||
<svg width="1920" height="751" viewBox="0 0 1920 751" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.1" filter="url(#filter0_f_1407_4696)">
|
||||
<path d="M357.765 483.542C426.213 434.499 1144.46 549.989 1258.37 579.451C1372.28 608.913 1757.57 743.868 1689.13 792.911C1620.68 841.954 527.424 923.713 413.507 894.245C299.59 864.776 289.318 532.588 357.765 483.542Z" fill="#0DB3D5"/>
|
||||
</g>
|
||||
<g opacity="0.2" filter="url(#filter1_f_1407_4696)">
|
||||
<path d="M605.311 371.098C596.35 427.875 656.872 561.013 526.007 556.64C395.141 552.267 122.44 412.046 131.401 355.264C140.363 298.481 198.295 172.294 329.163 176.666C460.031 181.038 614.27 314.317 605.311 371.098Z" fill="#EAF1B8"/>
|
||||
</g>
|
||||
<g opacity="0.2" filter="url(#filter2_f_1407_4696)">
|
||||
<path d="M1222 456.032C1222 409.041 1493.74 156 1551.77 156C1609.81 156 1882 384.378 1882 431.369C1882 478.359 1515.47 735 1457.43 735C1399.39 735 1222 503.022 1222 456.032Z" fill="#B59EDB"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_1407_4696" x="6.99048" y="163.563" width="1998.22" height="1044.92" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="154" result="effect1_foregroundBlur_1407_4696"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_1407_4696" x="-176.814" y="-131.445" width="1096.48" height="996.19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="154" result="effect1_foregroundBlur_1407_4696"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_1407_4696" x="914" y="-152" width="1276" height="1195" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="154" result="effect1_foregroundBlur_1407_4696"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/bgBottom.png
Normal file
After Width: | Height: | Size: 316 KiB |
BIN
public/bgEV.png
Normal file
After Width: | Height: | Size: 316 KiB |
34
public/bgev.svg
Normal file
|
@ -0,0 +1,34 @@
|
|||
<svg width="1920" height="1425" viewBox="0 0 1920 1425" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1407_4268)">
|
||||
<rect width="1920" height="1425" fill="#111111"/>
|
||||
<g opacity="0.1" filter="url(#filter0_f_1407_4268)">
|
||||
<path d="M357.765 483.542C426.213 434.499 1144.46 549.989 1258.37 579.451C1372.28 608.913 1757.57 743.868 1689.13 792.911C1620.68 841.955 527.425 923.713 413.508 894.245C299.591 864.776 289.318 532.588 357.765 483.542Z" fill="#0DB3D5"/>
|
||||
</g>
|
||||
<g opacity="0.2" filter="url(#filter1_f_1407_4268)">
|
||||
<path d="M605.311 371.098C596.349 427.875 656.872 561.013 526.007 556.64C395.141 552.267 122.439 412.046 131.401 355.264C140.363 298.481 198.295 172.294 329.163 176.666C460.031 181.038 614.27 314.317 605.311 371.098Z" fill="#EAF1B8"/>
|
||||
</g>
|
||||
<g opacity="0.2" filter="url(#filter2_f_1407_4268)">
|
||||
<path d="M1222 456.032C1222 409.042 1493.74 156 1551.77 156C1609.81 156 1882 384.379 1882 431.369C1882 478.359 1515.47 735 1457.43 735C1399.39 735 1222 503.022 1222 456.032Z" fill="#B59EDB"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_1407_4268" x="6.99072" y="163.564" width="1998.22" height="1044.92" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="154" result="effect1_foregroundBlur_1407_4268"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_1407_4268" x="-176.814" y="-131.445" width="1096.48" height="996.19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="154" result="effect1_foregroundBlur_1407_4268"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_1407_4268" x="914" y="-152" width="1276" height="1195" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="154" result="effect1_foregroundBlur_1407_4268"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_1407_4268">
|
||||
<rect width="1920" height="1425" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/dev-bg.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
public/developer.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
public/digimantra_labs_logo.jpeg
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
public/evLogo.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
public/google_play.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
public/hero.png
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
public/home-img.png
Normal file
After Width: | Height: | Size: 1 MiB |
BIN
public/home.png
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
public/iMockup - iPhone.png
Normal file
After Width: | Height: | Size: 221 KiB |
26
public/index.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Gilroy:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../src/index.css">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<title>DigiEV - Eco-friendly Charge</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
BIN
public/jobo.png
Normal file
After Width: | Height: | Size: 975 B |
BIN
public/left-bottom-arrow.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
public/logo.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
public/logo192.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
public/logo512.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
public/mainPageLogo.png
Normal file
After Width: | Height: | Size: 566 KiB |
25
public/manifest.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
0
public/mobileLogo.png
Normal file
BIN
public/model-s-exterior-front-view.webp
Normal file
After Width: | Height: | Size: 8.4 KiB |
3
public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
BIN
public/tablet-img.png
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
public/vector-arrows.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
50
src/App.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Fredoka:wght@300..700&display=swap");
|
||||
/* @import url("https://fonts.googleapis.com/css2?family=Baloo+2:wght@400..800&display=swap"); */
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden; /* Prevents scrolling */
|
||||
font-family: "Fredoka";
|
||||
}
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
8
src/App.test.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
13
src/App.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import AppRouter from "./router";
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<AppRouter />
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
BIN
src/assets/images/exercise.webp
Normal file
After Width: | Height: | Size: 138 KiB |
381
src/components/CustomTable/customTable.tsx
Normal file
|
@ -0,0 +1,381 @@
|
|||
import * as React from "react";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell, { tableCellClasses } from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import {
|
||||
Box,
|
||||
Pagination,
|
||||
TextField,
|
||||
Typography,
|
||||
InputAdornment,
|
||||
} from "@mui/material";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
|
||||
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
|
||||
|
||||
// Styled Components
|
||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
backgroundColor: "#000000",
|
||||
color: "#D0E1E9",
|
||||
fontWeight: 600,
|
||||
fontSize: "16px",
|
||||
padding: "12px 16px",
|
||||
borderBottom: "none",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 10,
|
||||
transition: "background-color 0.2s ease",
|
||||
"&:hover": {
|
||||
backgroundColor: "#2A2A2A",
|
||||
cursor: "pointer",
|
||||
},
|
||||
},
|
||||
[`&.${tableCellClasses.body}`]: {
|
||||
fontSize: "16px",
|
||||
padding: "12px 16px",
|
||||
borderBottom: "none",
|
||||
color: "#333333",
|
||||
transition: "background-color 0.2s ease",
|
||||
fontWeight: 500,
|
||||
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTableRow = styled(TableRow)(({ theme }) => ({
|
||||
backgroundColor: "#DFECF1",
|
||||
transition: "background-color 0.2s ease",
|
||||
"&:hover": {
|
||||
backgroundColor: "#D0E1E9",
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
|
||||
"&::-webkit-scrollbar": {
|
||||
height: "6px",
|
||||
},
|
||||
"&::-webkit-scrollbar-track": {
|
||||
background: "#D0E1E9",
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
background: "#000000",
|
||||
borderRadius: "3px",
|
||||
},
|
||||
}));
|
||||
|
||||
// Interfaces
|
||||
export interface Column {
|
||||
id: string;
|
||||
label: string;
|
||||
align?: "left" | "center" | "right";
|
||||
}
|
||||
|
||||
interface Row {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface CustomTableProps {
|
||||
columns: Column[];
|
||||
rows: Row[];
|
||||
tableType: string;
|
||||
}
|
||||
|
||||
// Sorting State Interface
|
||||
interface SortConfig {
|
||||
key: string;
|
||||
direction: "asc" | "desc" | null;
|
||||
}
|
||||
|
||||
const CustomTable: React.FC<CustomTableProps> = ({
|
||||
columns,
|
||||
rows,
|
||||
tableType,
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const [currentPage, setCurrentPage] = React.useState(1);
|
||||
const [sortConfig, setSortConfig] = React.useState<SortConfig>({
|
||||
key: "",
|
||||
direction: null,
|
||||
});
|
||||
const usersPerPage = 10;
|
||||
|
||||
// Helper Functions
|
||||
const getTitleByType = (type: string) => {
|
||||
const titles: { [key: string]: string } = {
|
||||
backlog: "Student Backlog",
|
||||
tools: "Tools",
|
||||
};
|
||||
return titles[type] || "List";
|
||||
};
|
||||
|
||||
const getEmptyMessage = (type: string) => {
|
||||
const messages: { [key: string]: string } = {
|
||||
backlog: "No students available",
|
||||
tools: "No tools available",
|
||||
};
|
||||
return messages[type] || "No data available";
|
||||
};
|
||||
|
||||
const isImage = (value: any) => {
|
||||
if (typeof value === "string") {
|
||||
return value.startsWith("http") || value.startsWith("data:image");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Sorting Logic
|
||||
const handleSort = (key: string) => {
|
||||
setSortConfig((prev) => {
|
||||
if (prev.key === key) {
|
||||
if (prev.direction === "asc") return { key, direction: "desc" };
|
||||
if (prev.direction === "desc") return { key, direction: null };
|
||||
return { key, direction: "asc" };
|
||||
}
|
||||
return { key, direction: "asc" };
|
||||
});
|
||||
};
|
||||
|
||||
const sortedRows = React.useMemo(() => {
|
||||
if (!sortConfig.key || !sortConfig.direction) return [...rows];
|
||||
|
||||
return [...rows].sort((a, b) => {
|
||||
const aValue = a[sortConfig.key];
|
||||
const bValue = b[sortConfig.key];
|
||||
|
||||
if (aValue == null && bValue == null) return 0;
|
||||
if (aValue == null) return sortConfig.direction === "asc" ? 1 : -1;
|
||||
if (bValue == null) return sortConfig.direction === "asc" ? -1 : 1;
|
||||
|
||||
if (typeof aValue === "number" && typeof bValue === "number") {
|
||||
return sortConfig.direction === "asc"
|
||||
? aValue - bValue
|
||||
: bValue - aValue;
|
||||
}
|
||||
|
||||
const aStr = String(aValue).toLowerCase();
|
||||
const bStr = String(bValue).toLowerCase();
|
||||
return sortConfig.direction === "asc"
|
||||
? aStr.localeCompare(bStr)
|
||||
: bStr.localeCompare(aStr);
|
||||
});
|
||||
}, [rows, sortConfig]);
|
||||
|
||||
// Filtering Logic
|
||||
const filteredRows = sortedRows.filter((row) => {
|
||||
if (!searchQuery.trim()) return true;
|
||||
const lowerCaseQuery = searchQuery.toLowerCase().trim();
|
||||
return (
|
||||
(row.name && row.name.toLowerCase().includes(lowerCaseQuery)) ||
|
||||
(row.email && row.email.toLowerCase().includes(lowerCaseQuery))
|
||||
);
|
||||
});
|
||||
|
||||
// Pagination Logic
|
||||
const indexOfLastRow = currentPage * usersPerPage;
|
||||
const indexOfFirstRow = indexOfLastRow - usersPerPage;
|
||||
const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow);
|
||||
|
||||
const handlePageChange = (
|
||||
_event: React.ChangeEvent<unknown>,
|
||||
value: number
|
||||
) => {
|
||||
setCurrentPage(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "20px 8px",
|
||||
backgroundColor: "#D0E1E9",
|
||||
borderRadius: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: { xs: "column", md: "row" },
|
||||
justifyContent: "space-between",
|
||||
alignItems: { xs: "flex-start", md: "center" },
|
||||
flexWrap: "wrap",
|
||||
mb: 3,
|
||||
gap: 2,
|
||||
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: "30px",
|
||||
letterSpacing: "-0.5px",
|
||||
}}
|
||||
>
|
||||
{getTitleByType(tableType)}
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
variant="outlined"
|
||||
placeholder="Search"
|
||||
sx={{
|
||||
width: { xs: "100%", sm: "300px" },
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "8px",
|
||||
height: "40px",
|
||||
backgroundColor: "#FFFFFF",
|
||||
"& fieldset": { borderColor: "#D1D5DB" },
|
||||
"&:hover fieldset": { borderColor: "#9CA3AF" },
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#3B82F6",
|
||||
},
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
fontSize: "14px",
|
||||
color: "#1A1A1A",
|
||||
},
|
||||
}}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon sx={{ color: "#6B7280" }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<StyledTableContainer>
|
||||
<Table sx={{ minWidth: "750px", tableLayout: "auto" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
<StyledTableCell
|
||||
key={column.id}
|
||||
align={column.align}
|
||||
onClick={
|
||||
column.id !== "select" &&
|
||||
column.id !== "status"
|
||||
? () => handleSort(column.id)
|
||||
: undefined
|
||||
}
|
||||
sx={{
|
||||
cursor:
|
||||
column.id !== "select" &&
|
||||
column.id !== "status"
|
||||
? "pointer"
|
||||
: "default",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
{column.label}
|
||||
{column.id !== "select" &&
|
||||
column.id !== "status" &&
|
||||
sortConfig.key === column.id &&
|
||||
sortConfig.direction &&
|
||||
(sortConfig.direction === "asc" ? (
|
||||
<ArrowUpwardIcon
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDownwardIcon
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</StyledTableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{currentRows.length === 0 ? (
|
||||
<StyledTableRow>
|
||||
<StyledTableCell
|
||||
colSpan={columns.length}
|
||||
sx={{ textAlign: "center", py: 4 }}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#6B7280",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{getEmptyMessage(tableType)}
|
||||
</Typography>
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
) : (
|
||||
currentRows.map((row, rowIndex) => (
|
||||
<StyledTableRow key={rowIndex}>
|
||||
{columns.map((column) => (
|
||||
<StyledTableCell
|
||||
key={column.id}
|
||||
align={column.align}
|
||||
>
|
||||
{isImage(row[column.id]) ? (
|
||||
<img
|
||||
src={row[column.id]}
|
||||
alt="Row"
|
||||
style={{
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
borderRadius: "50%",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
row[column.id]
|
||||
)}
|
||||
</StyledTableCell>
|
||||
))}
|
||||
</StyledTableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</StyledTableContainer>
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
|
||||
<Pagination
|
||||
count={Math.ceil(filteredRows.length / usersPerPage)}
|
||||
page={currentPage}
|
||||
onChange={handlePageChange}
|
||||
sx={{
|
||||
"& .MuiPaginationItem-root": {
|
||||
color: "#1A1A1A",
|
||||
fontSize: "16px",
|
||||
},
|
||||
"& .Mui-selected": {
|
||||
backgroundColor: "#000000",
|
||||
color: "#FFFFFF",
|
||||
"&:hover": {
|
||||
backgroundColor: "#000000",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomTable;
|
24
src/components/Loading/index.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import { CircularProgress, Box, Typography } from '@mui/material';
|
||||
|
||||
function LoadingComponent() {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={50} color="primary" />
|
||||
<Typography variant="h6" sx={{ marginTop: 2 }}>
|
||||
Loading...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoadingComponent;
|
8
src/global.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
declare module "*.css";
|
||||
|
||||
declare module "@mui/styles/defaultTheme" {
|
||||
interface DefaultTheme extends Theme {
|
||||
vars: object;
|
||||
}
|
||||
}
|
||||
|
24
src/hooks/useAuth.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { checkUserAuth } from '../redux/slices/authSlice';
|
||||
|
||||
const useAuth = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [isAuthorized, setIsAuthorized] = React.useState(false);
|
||||
const { isAuthenticated } = useSelector((state) => ({
|
||||
isAuthenticated: state.authReducer.isAuthenticated,
|
||||
}));
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
dispatch(checkUserAuth());
|
||||
} else {
|
||||
setIsAuthorized(false);
|
||||
}
|
||||
}, [dispatch, isAuthenticated]);
|
||||
|
||||
return { isAuthenticated, isAuthorized };
|
||||
};
|
||||
|
||||
export default useAuth;
|
35
src/index.css
Normal file
|
@ -0,0 +1,35 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Fredoka:wght@300..700&display=swap");
|
||||
|
||||
/* @import url("https://fonts.googleapis.com/css2?family=Baloo+2:wght@400..800&display=swap"); */
|
||||
/* @import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap'); */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Fredoka";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
||||
.mui-typography {
|
||||
font-family: "Fredoka";
|
||||
background-color: rgb(7, 127, 233);
|
||||
}
|
||||
|
||||
.css-1w8ddxu-MuiBarElement-root {
|
||||
width: 19px !important;
|
||||
border-radius: 50px !important;
|
||||
rx: 8;
|
||||
ry: 8;
|
||||
}
|
||||
/* @font-face {
|
||||
font-family: '"Publica Sans Round Medium", sans-serif';
|
||||
src: url("../public/fonts/PublicaSansRound-Md.otf") format("otf");
|
||||
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
} */
|
28
src/index.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import App from "./App";
|
||||
import { Provider } from "react-redux";
|
||||
import store from "./redux/store/store.ts";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<Toaster
|
||||
position="top-right"
|
||||
richColors
|
||||
closeButton
|
||||
duration={3000}
|
||||
/>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
82
src/layouts/DashboardLayout/index.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
import * as React from "react";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import SideMenu from "../../components/SideMenu/sideMenu";
|
||||
import AppNavbar from "../../components/AppNavbar";
|
||||
import Header from "../../components/Header";
|
||||
import AppTheme from "../../shared-theme/AppTheme";
|
||||
|
||||
interface LayoutProps {
|
||||
customStyles?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const DashboardLayout: React.FC<LayoutProps> = ({ customStyles }) => {
|
||||
return (
|
||||
<AppTheme>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
// height: "auto",
|
||||
flexDirection: { xs: "column", md: "row" },
|
||||
width: "100%",
|
||||
height: "100vh", // Full height from root, not auto
|
||||
overflow: "hidden",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{/* SideMenu - Responsive, shown only on large screens */}
|
||||
<Box
|
||||
sx={{
|
||||
display: { xs: "none", md: "block" },
|
||||
// width: 250,
|
||||
}}
|
||||
>
|
||||
<SideMenu />
|
||||
</Box>
|
||||
|
||||
{/* Navbar - Always visible */}
|
||||
<AppNavbar />
|
||||
|
||||
<Box
|
||||
component="main"
|
||||
sx={(theme) => ({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
overflow: "auto",
|
||||
overflowX: "hidden",
|
||||
...customStyles,
|
||||
mt: { xs: 8, md: 0 },
|
||||
padding: 0,
|
||||
})}
|
||||
>
|
||||
<Box sx={{ height: "84px", flex: "0 0 84px" }}>
|
||||
<Header />
|
||||
</Box>
|
||||
<Stack
|
||||
spacing={2}
|
||||
sx={{
|
||||
padding: "30px",
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
justifyItems: "center",
|
||||
alignItems: "center",
|
||||
// mx: { xs: 1, sm: 3 },
|
||||
// pb: 5,
|
||||
mt: { xs: 3, md: 0 },
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</AppTheme>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
38
src/lib/https.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import axios from "axios";
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: process.env.REACT_APP_BACKEND_URL,
|
||||
});
|
||||
|
||||
http.interceptors.request.use((config) => {
|
||||
const authToken = localStorage.getItem("authToken");
|
||||
if (authToken) {
|
||||
config.headers.Authorization = `Bearer ${authToken}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
http.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
const status = error.response.status;
|
||||
const requestUrl = error.config.url; // Get the API route
|
||||
|
||||
// Handle token expiration (401) but NOT for login failures
|
||||
if (status === 401 && !requestUrl.includes("/login")) {
|
||||
localStorage.removeItem("authToken");
|
||||
window.location.href = "/login";
|
||||
}
|
||||
|
||||
// Handle forbidden access
|
||||
if (status === 403) {
|
||||
localStorage.removeItem("authToken");
|
||||
window.location.href = "/login";
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default http;
|
1
src/logo.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
175
src/pages/BacklogUploadPage/index.tsx
Normal file
|
@ -0,0 +1,175 @@
|
|||
import { useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import * as XLSX from "xlsx";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AppDispatch, RootState } from "../../redux/store/store";
|
||||
import { loadStudents } from "../../redux/slices/backlogSlice";
|
||||
import { toast } from "sonner";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import CustomTable, { Column } from "../../components/CustomTable/customTable";
|
||||
|
||||
export default function BacklogUploadPage() {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const students = useSelector(
|
||||
(state: RootState) => state.backlogReducer.students
|
||||
);
|
||||
|
||||
const [fileUploaded, setFileUploaded] = useState(students.length > 0);
|
||||
const [selectedStudentIds, setSelectedStudentIds] = useState<string[]>([]);
|
||||
|
||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt) => {
|
||||
const data = new Uint8Array(evt.target.result as ArrayBuffer);
|
||||
const workbook = XLSX.read(data, { type: "array" });
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
const parsedData: { name: string; email: string }[] =
|
||||
XLSX.utils.sheet_to_json(sheet);
|
||||
|
||||
const studentsWithId = parsedData.map((student) => ({
|
||||
...student,
|
||||
id: uuidv4(),
|
||||
status: "Pending",
|
||||
}));
|
||||
|
||||
dispatch(loadStudents(studentsWithId));
|
||||
setFileUploaded(true);
|
||||
setSelectedStudentIds([]); // Reset selection after upload
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
setSelectedStudentIds((prev) =>
|
||||
prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
const handleNavigateToEmail = () => {
|
||||
if (selectedStudentIds.length === 0) {
|
||||
toast.warning("Please select at least one student.");
|
||||
return;
|
||||
}
|
||||
navigate("/email", { state: { selectedIds: selectedStudentIds } });
|
||||
};
|
||||
|
||||
const columns: Column[] = [
|
||||
{ id: "select", label: "Select", align: "center" },
|
||||
{ id: "name", label: "Name", align: "left" },
|
||||
{ id: "email", label: "Email", align: "left" },
|
||||
{ id: "status", label: "Status", align: "center" },
|
||||
];
|
||||
|
||||
const rows = students.map((student) => ({
|
||||
id: student.id,
|
||||
select: (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedStudentIds.includes(student.id)}
|
||||
onChange={() => handleCheckboxChange(student.id)}
|
||||
disabled={student.status === "Sent"}
|
||||
/>
|
||||
),
|
||||
name: student.name,
|
||||
email: student.email,
|
||||
status:
|
||||
student.status === "Sent" ? (
|
||||
<span style={{ color: "#10B981" }}>✔️ Sent</span>
|
||||
) : (
|
||||
<span style={{ color: "#F59E0B" }}>⏳ Pending</span>
|
||||
),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100vw",
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
p: { xs: 2, sm: 4 },
|
||||
backgroundColor: "#F5F8FA",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: "20px", sm: "24px" },
|
||||
mb: 3,
|
||||
color: "#1A1A1A",
|
||||
}}
|
||||
>
|
||||
📂 Upload Student Backlog File
|
||||
</Typography>
|
||||
|
||||
{!fileUploaded ? (
|
||||
<Box sx={{ maxWidth: "600px" }}>
|
||||
<label htmlFor="upload-input">
|
||||
<input
|
||||
id="upload-input"
|
||||
type="file"
|
||||
accept=".xlsx, .xls"
|
||||
onChange={handleFileUpload}
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
component="span"
|
||||
sx={{
|
||||
backgroundColor: "#1F2937",
|
||||
color: "#fff",
|
||||
borderRadius: "8px",
|
||||
textTransform: "none",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
📁 Choose Excel File
|
||||
</Button>
|
||||
</label>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
color: "#6B7280",
|
||||
mt: 1,
|
||||
}}
|
||||
>
|
||||
* Excel must contain columns: <b>name</b>, <b>email</b>
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ flex: 1, display: "flex", flexDirection: "column" }}>
|
||||
<CustomTable
|
||||
columns={columns}
|
||||
rows={rows}
|
||||
tableType="backlog"
|
||||
/>
|
||||
<Box sx={{ display: "flex", gap: 2, mt: 3 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleNavigateToEmail}
|
||||
sx={{
|
||||
backgroundColor: "#111827",
|
||||
color: "#fff",
|
||||
borderRadius: "8px",
|
||||
fontSize: "16px",
|
||||
padding: "8px 24px",
|
||||
textTransform: "none",
|
||||
"&:hover": { backgroundColor: "#1F2937" },
|
||||
}}
|
||||
>
|
||||
Send Email
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
140
src/pages/EmailPage/index.tsx
Normal file
|
@ -0,0 +1,140 @@
|
|||
import { Box, Button, TextField, Typography, Paper } from "@mui/material";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useState } from "react";
|
||||
import { RootState, AppDispatch } from "../../redux/store/store";
|
||||
import { markStudentAsSent } from "../../redux/slices/backlogSlice";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function EmailPage() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
const { selectedIds } = location.state || { selectedIds: [] };
|
||||
const students = useSelector(
|
||||
(state: RootState) => state.backlogReducer.students
|
||||
);
|
||||
|
||||
const selectedStudents = students.filter((student) =>
|
||||
selectedIds.includes(student.id)
|
||||
);
|
||||
|
||||
const [subject, setSubject] = useState("");
|
||||
const [message, setMessage] = useState("");
|
||||
const [attachment, setAttachment] = useState<File | null>(null);
|
||||
|
||||
const handleSend = () => {
|
||||
if (!subject || !message) {
|
||||
toast.warning("Subject and Message are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Sending email to:",
|
||||
selectedStudents.map((s) => s.email)
|
||||
);
|
||||
console.log("Subject:", subject);
|
||||
console.log("Message:", message);
|
||||
console.log("Attachment:", attachment?.name);
|
||||
|
||||
dispatch(markStudentAsSent(selectedIds));
|
||||
toast.success("Emails sent successfully.");
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#F5F8FA",
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{ p: 4, maxWidth: "600px", width: "100%" }}
|
||||
>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
📧 Compose Email
|
||||
</Typography>
|
||||
|
||||
<Typography sx={{ mb: 2 }}>
|
||||
<b>To:</b> {selectedStudents.map((s) => s.name).join(", ")}
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Subject"
|
||||
variant="outlined"
|
||||
value={subject}
|
||||
onChange={(e) => setSubject(e.target.value)}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Message"
|
||||
variant="outlined"
|
||||
multiline
|
||||
rows={6}
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
sx={{ mb: 2, textTransform: "none" }}
|
||||
>
|
||||
📎 Upload Attachment
|
||||
<input
|
||||
type="file"
|
||||
hidden
|
||||
onChange={(e) =>
|
||||
setAttachment(e.target.files?.[0] || null)
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
{attachment && (
|
||||
<Typography variant="body2" sx={{ mb: 2 }}>
|
||||
Selected: {attachment.name}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
mt: 2,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSend}
|
||||
sx={{ textTransform: "none" }}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleCancel}
|
||||
sx={{ textTransform: "none" }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
115
src/pages/NotFoundPage/index.tsx
Normal file
|
@ -0,0 +1,115 @@
|
|||
import React from "react";
|
||||
import { Box, Typography, Button, Card, Grid } from "@mui/material";
|
||||
import ElectricCarIcon from "@mui/icons-material/ElectricCar";
|
||||
import { keyframes } from "@emotion/react";
|
||||
|
||||
// Animation for the car icon
|
||||
const pulse = keyframes`
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
`;
|
||||
|
||||
const NotFoundPage = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
background:
|
||||
"linear-gradient(135deg,rgb(12, 14, 15),rgb(10, 10, 10))",
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: "#272727",
|
||||
padding: "40px",
|
||||
borderRadius: "20px",
|
||||
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.7)",
|
||||
textAlign: "center",
|
||||
maxWidth: "500px",
|
||||
width: "100%",
|
||||
color: "#E0E0E0",
|
||||
animation: `${pulse} 1.5s infinite`,
|
||||
}}
|
||||
>
|
||||
<ElectricCarIcon
|
||||
sx={{
|
||||
fontSize: 100,
|
||||
color: "#52ACDF",
|
||||
marginBottom: 2,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
variant="h3"
|
||||
sx={{ fontWeight: 700, color: "#FFFFFF", marginBottom: 1 }}
|
||||
>
|
||||
404
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{ fontWeight: 500, color: "#FFFFFF", marginBottom: 3 }}
|
||||
>
|
||||
Oops! Page Not Found
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: "#B0B0B0", marginBottom: 4 }}
|
||||
>
|
||||
The path you’re looking for seems to be off the grid. Maybe
|
||||
the charging station is offline? ⚡
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={2} justifyContent="center">
|
||||
<Grid item>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "#52ACDF",
|
||||
color: "#FFFFFF",
|
||||
padding: "12px 32px",
|
||||
"&:hover": {
|
||||
backgroundColor: "#52ACDF",
|
||||
opacity: 0.9,
|
||||
},
|
||||
fontWeight: 600,
|
||||
}}
|
||||
onClick={() => (window.location.href = "/")}
|
||||
>
|
||||
Back to Login
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
borderColor: "#52ACDF",
|
||||
color: "#52ACDF",
|
||||
padding: "12px 32px",
|
||||
"&:hover": {
|
||||
backgroundColor: "#52ACDF",
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
}}
|
||||
onClick={() => window.history.back()}
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFoundPage;
|
12
src/redux/reducers.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { combineReducers } from "@reduxjs/toolkit";
|
||||
|
||||
import backlogReducer from "../redux/slices/backlogSlice.ts";
|
||||
const rootReducer = combineReducers({
|
||||
|
||||
backlogReducer,
|
||||
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof rootReducer>;
|
||||
|
||||
export default rootReducer;
|
123
src/redux/slices/backlogSlice.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
import http from "../../lib/https";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export interface Student {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
status: "Pending" | "Sent";
|
||||
}
|
||||
|
||||
// State shape
|
||||
interface BacklogState {
|
||||
students: Student[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Initial state
|
||||
const initialState: BacklogState = {
|
||||
students: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
|
||||
export const fetchBacklogStudents = createAsyncThunk<
|
||||
Student[],
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
>("backlog/fetchBacklogStudents", async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.get("/students/backlog");
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "Failed to fetch students"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const removeStudentFromServer = createAsyncThunk<
|
||||
string, // returning student ID
|
||||
string, // input student ID
|
||||
{ rejectValue: string }
|
||||
>("backlog/removeStudentFromServer", async (studentId, { rejectWithValue }) => {
|
||||
try {
|
||||
await http.delete(`/students/${studentId}`);
|
||||
toast.success("Student removed from backlog.");
|
||||
return studentId;
|
||||
} catch (error: any) {
|
||||
toast.error("Failed to remove student.");
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "Remove operation failed"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const backlogSlice = createSlice({
|
||||
name: "backlog",
|
||||
initialState,
|
||||
reducers: {
|
||||
loadStudents: (
|
||||
state,
|
||||
action: PayloadAction<Omit<Student, "status">[]>
|
||||
) => {
|
||||
state.students = action.payload.map((student) => ({
|
||||
...student,
|
||||
status: "Pending",
|
||||
}));
|
||||
toast.success("Students loaded from file.");
|
||||
},
|
||||
|
||||
|
||||
removeStudent: (state, action: PayloadAction<string>) => {
|
||||
state.students = state.students.filter(
|
||||
(student) => student.id !== action.payload
|
||||
);
|
||||
toast.success("Student removed from local backlog.");
|
||||
},
|
||||
|
||||
|
||||
markStudentAsSent: (state, action: PayloadAction<string[]>) => {
|
||||
action.payload.forEach((id) => {
|
||||
const student = state.students.find((s) => s.id === id);
|
||||
if (student) {
|
||||
student.status = "Sent";
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchBacklogStudents.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(
|
||||
fetchBacklogStudents.fulfilled,
|
||||
(state, action: PayloadAction<Student[]>) => {
|
||||
state.students = action.payload;
|
||||
state.isLoading = false;
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
fetchBacklogStudents.rejected,
|
||||
(state, action: PayloadAction<string | undefined>) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload || "Failed to fetch";
|
||||
}
|
||||
)
|
||||
.addCase(removeStudentFromServer.fulfilled, (state, action) => {
|
||||
state.students = state.students.filter(
|
||||
(student) => student.id !== action.payload
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { loadStudents, removeStudent, markStudentAsSent } =
|
||||
backlogSlice.actions;
|
||||
export default backlogSlice.reducer;
|
9
src/redux/store/store.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import rootReducer from "../reducers.ts";
|
||||
const store = configureStore({
|
||||
reducer: rootReducer,
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export default store;
|
13
src/reportWebVitals.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
24
src/router.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom";
|
||||
import React, { lazy, Suspense } from "react";
|
||||
import LoadingComponent from "./components/Loading";
|
||||
import BacklogUploadPage from "./pages/BacklogUploadPage/index.tsx";
|
||||
|
||||
import EmailPage from "./pages/EmailPage/index.tsx";
|
||||
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
||||
|
||||
|
||||
// Combined Router Component
|
||||
export default function AppRouter() {
|
||||
return (
|
||||
<Suspense fallback={<LoadingComponent />}>
|
||||
<BaseRoutes>
|
||||
{/* Default Route */}
|
||||
<Route path="" element={<BacklogUploadPage />} />
|
||||
<Route path="/email" element={<EmailPage />} />
|
||||
|
||||
{/* Catch-all Route */}
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</BaseRoutes>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
5
src/setupTests.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
88
src/shared-theme/AppTheme.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
import * as React from "react";
|
||||
import { ThemeProvider, Theme, createTheme } from "@mui/material/styles";
|
||||
import type { ThemeOptions } from "@mui/material/styles";
|
||||
import { inputsCustomizations } from "./customizations/inputs";
|
||||
import { dataDisplayCustomizations } from "./customizations/dataDisplay";
|
||||
import { feedbackCustomizations } from "./customizations/feedback";
|
||||
import { navigationCustomizations } from "./customizations/navigation";
|
||||
import { surfacesCustomizations } from "./customizations/surfaces";
|
||||
import { colorSchemes, shadows, shape } from "./themePrimitives";
|
||||
|
||||
interface AppThemeProps {
|
||||
children: React.ReactNode;
|
||||
disableCustomTheme?: boolean;
|
||||
themeComponents?: ThemeOptions["components"];
|
||||
}
|
||||
|
||||
export default function AppTheme(props: AppThemeProps) {
|
||||
const { children, disableCustomTheme, themeComponents } = props;
|
||||
const theme = React.useMemo(() => {
|
||||
return disableCustomTheme
|
||||
? {}
|
||||
: createTheme({
|
||||
palette: {
|
||||
mode: "light",
|
||||
background: {
|
||||
default: "#DFECF1",
|
||||
paper: "#D0E1E9",
|
||||
},
|
||||
text: {
|
||||
primary: "#000000",
|
||||
secondary: "#454545",
|
||||
},
|
||||
success: {
|
||||
main: "#00C853",
|
||||
},
|
||||
error: {
|
||||
main: "#F44336",
|
||||
},
|
||||
divider: "#E0E0E0",
|
||||
},
|
||||
typography: {
|
||||
fontFamily: "Fredoka",
|
||||
h4: {
|
||||
fontSize: "2rem",
|
||||
fontWeight: 600,
|
||||
},
|
||||
h6: {
|
||||
fontSize: "16px",
|
||||
fontWeight: 500,
|
||||
},
|
||||
body1: {
|
||||
fontSize: "1rem",
|
||||
},
|
||||
body2: {
|
||||
fontSize: "0.875rem",
|
||||
color: "#b0b0b0",
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
cssVariables: {
|
||||
colorSchemeSelector: "data-mui-color-scheme",
|
||||
cssVarPrefix: "template",
|
||||
},
|
||||
shadows,
|
||||
shape: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
components: {
|
||||
...inputsCustomizations,
|
||||
...dataDisplayCustomizations,
|
||||
...feedbackCustomizations,
|
||||
...navigationCustomizations,
|
||||
...surfacesCustomizations,
|
||||
...themeComponents,
|
||||
},
|
||||
});
|
||||
}, [disableCustomTheme, themeComponents]);
|
||||
|
||||
if (disableCustomTheme) {
|
||||
return <React.Fragment>{children}</React.Fragment>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme} disableTransitionOnChange>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
89
src/shared-theme/ColorModeIconDropdown.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import * as React from 'react';
|
||||
import DarkModeIcon from '@mui/icons-material/DarkModeRounded';
|
||||
import LightModeIcon from '@mui/icons-material/LightModeRounded';
|
||||
import Box from '@mui/material/Box';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { useColorScheme } from '@mui/material/styles';
|
||||
|
||||
export default function ColorModeIconDropdown(props) {
|
||||
const { mode, systemMode, setMode } = useColorScheme();
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleMode = (targetMode) => () => {
|
||||
setMode(targetMode);
|
||||
handleClose();
|
||||
};
|
||||
if (!mode) {
|
||||
return (
|
||||
<Box
|
||||
data-screenshot="toggle-mode"
|
||||
sx={(theme) => ({
|
||||
verticalAlign: 'bottom',
|
||||
display: 'inline-flex',
|
||||
width: '2.25rem',
|
||||
height: '2.25rem',
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: '1px solid',
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const resolvedMode = systemMode || mode;
|
||||
const icon = {
|
||||
light: <LightModeIcon />,
|
||||
dark: <DarkModeIcon />,
|
||||
}[resolvedMode];
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IconButton
|
||||
data-screenshot="toggle-mode"
|
||||
onClick={handleClick}
|
||||
disableRipple
|
||||
size="small"
|
||||
aria-controls={open ? 'color-scheme-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
id="account-menu"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
onClick={handleClose}
|
||||
slotProps={{
|
||||
paper: {
|
||||
variant: 'outlined',
|
||||
elevation: 0,
|
||||
sx: {
|
||||
my: '4px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||
>
|
||||
<MenuItem selected={mode === 'system'} onClick={handleMode('system')}>
|
||||
System
|
||||
</MenuItem>
|
||||
<MenuItem selected={mode === 'light'} onClick={handleMode('light')}>
|
||||
Light
|
||||
</MenuItem>
|
||||
<MenuItem selected={mode === 'dark'} onClick={handleMode('dark')}>
|
||||
Dark
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
89
src/shared-theme/ColorModeIconDropdown.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import * as React from 'react';
|
||||
import DarkModeIcon from '@mui/icons-material/DarkModeRounded';
|
||||
import LightModeIcon from '@mui/icons-material/LightModeRounded';
|
||||
import Box from '@mui/material/Box';
|
||||
import IconButton, { IconButtonOwnProps } from '@mui/material/IconButton';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { useColorScheme } from '@mui/material/styles';
|
||||
|
||||
export default function ColorModeIconDropdown(props: IconButtonOwnProps) {
|
||||
const { mode, systemMode, setMode } = useColorScheme();
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleMode = (targetMode: 'system' | 'light' | 'dark') => () => {
|
||||
setMode(targetMode);
|
||||
handleClose();
|
||||
};
|
||||
if (!mode) {
|
||||
return (
|
||||
<Box
|
||||
data-screenshot="toggle-mode"
|
||||
sx={(theme) => ({
|
||||
verticalAlign: 'bottom',
|
||||
display: 'inline-flex',
|
||||
width: '2.25rem',
|
||||
height: '2.25rem',
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: '1px solid',
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const resolvedMode = (systemMode || mode) as 'light' | 'dark';
|
||||
const icon = {
|
||||
light: <LightModeIcon />,
|
||||
dark: <DarkModeIcon />,
|
||||
}[resolvedMode];
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IconButton
|
||||
data-screenshot="toggle-mode"
|
||||
onClick={handleClick}
|
||||
disableRipple
|
||||
size="small"
|
||||
aria-controls={open ? 'color-scheme-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
id="account-menu"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
onClick={handleClose}
|
||||
slotProps={{
|
||||
paper: {
|
||||
variant: 'outlined',
|
||||
elevation: 0,
|
||||
sx: {
|
||||
my: '4px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||
>
|
||||
<MenuItem selected={mode === 'system'} onClick={handleMode('system')}>
|
||||
System
|
||||
</MenuItem>
|
||||
<MenuItem selected={mode === 'light'} onClick={handleMode('light')}>
|
||||
Light
|
||||
</MenuItem>
|
||||
<MenuItem selected={mode === 'dark'} onClick={handleMode('dark')}>
|
||||
Dark
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
25
src/shared-theme/ColorModeSelect.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import * as React from 'react';
|
||||
import { useColorScheme } from '@mui/material/styles';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
|
||||
export default function ColorModeSelect(props) {
|
||||
const { mode, setMode } = useColorScheme();
|
||||
if (!mode) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
value={mode}
|
||||
onChange={(event) => setMode(event.target.value)}
|
||||
SelectDisplayProps={{
|
||||
'data-screenshot': 'toggle-mode',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<MenuItem value="system">System</MenuItem>
|
||||
<MenuItem value="light">Light</MenuItem>
|
||||
<MenuItem value="dark">Dark</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
28
src/shared-theme/ColorModeSelect.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import * as React from 'react';
|
||||
import { useColorScheme } from '@mui/material/styles';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select, { SelectProps } from '@mui/material/Select';
|
||||
|
||||
export default function ColorModeSelect(props: SelectProps) {
|
||||
const { mode, setMode } = useColorScheme();
|
||||
if (!mode) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
value={mode}
|
||||
onChange={(event) =>
|
||||
setMode(event.target.value as 'system' | 'light' | 'dark')
|
||||
}
|
||||
SelectDisplayProps={{
|
||||
// @ts-ignore
|
||||
'data-screenshot': 'toggle-mode',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<MenuItem value="system">System</MenuItem>
|
||||
<MenuItem value="light">Light</MenuItem>
|
||||
<MenuItem value="dark">Dark</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
26
src/shared-theme/customizations/autoFill.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
export const autofillFix = {
|
||||
"& input:-webkit-autofill": {
|
||||
WebkitBoxShadow: "0 0 0 1000px transparent inset !important",
|
||||
WebkitTextFillColor: "#000000 !important",
|
||||
transition: "background-color 5000s ease-in-out 0s",
|
||||
},
|
||||
"& input:-webkit-autofill:hover": {
|
||||
WebkitBoxShadow: "0 0 0 1000px transparent inset !important",
|
||||
},
|
||||
"& input:-webkit-autofill:focus": {
|
||||
WebkitBoxShadow: "0 0 0 1000px transparent inset !important",
|
||||
},
|
||||
"& input:-webkit-autofill:active": {
|
||||
WebkitBoxShadow: "0 0 0 1000px transparent inset !important",
|
||||
},
|
||||
"& input:-moz-autofill": {
|
||||
boxShadow: "0 0 0 1000px transparent inset !important",
|
||||
MozTextFillColor: "#000000 !important",
|
||||
transition: "background-color 5000s ease-in-out 0s",
|
||||
},
|
||||
"& input:autofill": {
|
||||
boxShadow: "0 0 0 1000px transparent inset !important",
|
||||
textFillColor: "#000000 !important",
|
||||
transition: "background-color 5000s ease-in-out 0s",
|
||||
},
|
||||
};
|
237
src/shared-theme/customizations/dataDisplay.js
Normal file
|
@ -0,0 +1,237 @@
|
|||
import { alpha } from '@mui/material/styles';
|
||||
import { svgIconClasses } from '@mui/material/SvgIcon';
|
||||
import { typographyClasses } from '@mui/material/Typography';
|
||||
import { buttonBaseClasses } from '@mui/material/ButtonBase';
|
||||
import { chipClasses } from '@mui/material/Chip';
|
||||
import { iconButtonClasses } from '@mui/material/IconButton';
|
||||
import { gray, red, green } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const dataDisplayCustomizations = {
|
||||
MuiList: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
// padding: '8px',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
gap: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiListItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
[`& .${svgIconClasses.root}`]: {
|
||||
width: "3rem",
|
||||
height: "2rem",
|
||||
color: (theme.vars || theme).palette.text.secondary,
|
||||
},
|
||||
[`& .${typographyClasses.root}`]: {
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
},
|
||||
[`& .${buttonBaseClasses.root}`]: {
|
||||
display: "flex",
|
||||
// gap: 8,
|
||||
// padding: '2px 8px',
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
opacity: 0.7,
|
||||
"&.Mui-selected": {
|
||||
opacity: 2,
|
||||
// backgroundColor: "rgb(66, 64, 64)",
|
||||
|
||||
[`& .${svgIconClasses.root}`]: {
|
||||
//color: (theme.vars || theme).palette.text.primary,
|
||||
color: " #FFFFFF",
|
||||
},
|
||||
"&:focus-visible": {
|
||||
// backgroundColor: alpha(theme.palette.action.selected, 0.3),
|
||||
},
|
||||
"&:hover": {
|
||||
//backgroundColor: alpha(theme.palette.action.selected, 0.5),
|
||||
},
|
||||
},
|
||||
// "&:focus-visible": {
|
||||
// backgroundColor: "transparent",
|
||||
// },
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiListItemText: {
|
||||
styleOverrides: {
|
||||
primary: ({ theme }) => ({
|
||||
fontSize: "16px",
|
||||
fontWeight: 500,
|
||||
lineHeight: theme.typography.body2.lineHeight,
|
||||
}),
|
||||
secondary: ({ theme }) => ({
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
lineHeight: theme.typography.caption.lineHeight,
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiListSubheader: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
backgroundColor: "transparent",
|
||||
// padding: '4px 8px',
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
fontWeight: 500,
|
||||
lineHeight: theme.typography.caption.lineHeight,
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiListItemIcon: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
minWidth: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
defaultProps: {
|
||||
size: "small",
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
border: "1px solid",
|
||||
borderRadius: "999px",
|
||||
[`& .${chipClasses.label}`]: {
|
||||
fontWeight: 600,
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
color: "default",
|
||||
},
|
||||
style: {
|
||||
borderColor: gray[200],
|
||||
backgroundColor: gray[100],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: gray[500],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: gray[500],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
borderColor: gray[700],
|
||||
backgroundColor: gray[800],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: gray[300],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: gray[300],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "success",
|
||||
},
|
||||
style: {
|
||||
borderColor: green[200],
|
||||
backgroundColor: green[50],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: green[500],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: green[500],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
borderColor: green[800],
|
||||
backgroundColor: green[900],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: green[300],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: green[300],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "error",
|
||||
},
|
||||
style: {
|
||||
borderColor: red[100],
|
||||
backgroundColor: red[50],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: red[500],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: red[500],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
borderColor: red[800],
|
||||
backgroundColor: red[900],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: red[200],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: red[300],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: { size: "small" },
|
||||
style: {
|
||||
maxHeight: 20,
|
||||
[`& .${chipClasses.label}`]: {
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
},
|
||||
[`& .${svgIconClasses.root}`]: {
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
props: { size: "medium" },
|
||||
style: {
|
||||
[`& .${chipClasses.label}`]: {
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiTablePagination: {
|
||||
styleOverrides: {
|
||||
actions: {
|
||||
display: "flex",
|
||||
gap: 8,
|
||||
marginRight: 6,
|
||||
[`& .${iconButtonClasses.root}`]: {
|
||||
minWidth: 0,
|
||||
width: 36,
|
||||
height: 36,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiIcon: {
|
||||
defaultProps: {
|
||||
fontSize: "small",
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
fontSize: "small",
|
||||
},
|
||||
style: {
|
||||
fontSize: "1rem",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
237
src/shared-theme/customizations/dataDisplay.tsx
Normal file
|
@ -0,0 +1,237 @@
|
|||
import { Theme, alpha, Components } from '@mui/material/styles';
|
||||
import { svgIconClasses } from '@mui/material/SvgIcon';
|
||||
import { typographyClasses } from '@mui/material/Typography';
|
||||
import { buttonBaseClasses } from '@mui/material/ButtonBase';
|
||||
import { chipClasses } from '@mui/material/Chip';
|
||||
import { iconButtonClasses } from '@mui/material/IconButton';
|
||||
import { gray, red, green } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const dataDisplayCustomizations: Components<Theme> = {
|
||||
MuiList: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiListItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
|
||||
[`& .${svgIconClasses.root}`]: {
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
color: (theme.vars || theme).palette.text.secondary,
|
||||
},
|
||||
[`& .${typographyClasses.root}`]: {
|
||||
fontWeight: 500,
|
||||
|
||||
},
|
||||
[`& .${buttonBaseClasses.root}`]: {
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
opacity: 0.7,
|
||||
'&.Mui-selected': {
|
||||
opacity: 1,
|
||||
backgroundColor: alpha(theme.palette.action.selected, 0.3),
|
||||
|
||||
[`& .${svgIconClasses.root}`]: {
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
},
|
||||
'&:focus-visible': {
|
||||
backgroundColor: alpha(theme.palette.action.selected, 0.3),
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.palette.action.selected, 0.5),
|
||||
},
|
||||
},
|
||||
|
||||
'&:focus-visible': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiListItemText: {
|
||||
styleOverrides: {
|
||||
primary: ({ theme }) => ({
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
fontWeight: 500,
|
||||
lineHeight: theme.typography.body2.lineHeight,
|
||||
}),
|
||||
secondary: ({ theme }) => ({
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
lineHeight: theme.typography.caption.lineHeight,
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiListSubheader: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
backgroundColor: 'transparent',
|
||||
// padding: '4px 8px',
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
fontWeight: 500,
|
||||
lineHeight: theme.typography.caption.lineHeight,
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiListItemIcon: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
minWidth: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
defaultProps: {
|
||||
size: 'small',
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
border: '1px solid',
|
||||
borderRadius: '999px',
|
||||
[`& .${chipClasses.label}`]: {
|
||||
fontWeight: 600,
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
color: 'default',
|
||||
},
|
||||
style: {
|
||||
borderColor: gray[200],
|
||||
backgroundColor: gray[100],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: gray[500],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: gray[500],
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
borderColor: gray[700],
|
||||
backgroundColor: gray[800],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: gray[300],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: gray[300],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: 'success',
|
||||
},
|
||||
style: {
|
||||
borderColor: green[200],
|
||||
backgroundColor: green[50],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: green[500],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: green[500],
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
borderColor: green[800],
|
||||
backgroundColor: green[900],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: green[300],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: green[300],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: 'error',
|
||||
},
|
||||
style: {
|
||||
borderColor: red[100],
|
||||
backgroundColor: red[50],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: red[500],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: red[500],
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
borderColor: red[800],
|
||||
backgroundColor: red[900],
|
||||
[`& .${chipClasses.label}`]: {
|
||||
color: red[200],
|
||||
},
|
||||
[`& .${chipClasses.icon}`]: {
|
||||
color: red[300],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: { size: 'small' },
|
||||
style: {
|
||||
maxHeight: 20,
|
||||
[`& .${chipClasses.label}`]: {
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
},
|
||||
[`& .${svgIconClasses.root}`]: {
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
props: { size: 'medium' },
|
||||
style: {
|
||||
[`& .${chipClasses.label}`]: {
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiTablePagination: {
|
||||
styleOverrides: {
|
||||
actions: {
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
marginRight: 6,
|
||||
[`& .${iconButtonClasses.root}`]: {
|
||||
minWidth: 0,
|
||||
width: 36,
|
||||
height: 36,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiIcon: {
|
||||
defaultProps: {
|
||||
fontSize: 'small',
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
fontSize: 'small',
|
||||
},
|
||||
style: {
|
||||
fontSize: '1rem',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
46
src/shared-theme/customizations/feedback.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { alpha } from '@mui/material/styles';
|
||||
import { gray, orange } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const feedbackCustomizations = {
|
||||
MuiAlert: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: 10,
|
||||
backgroundColor: orange[100],
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
border: `1px solid ${alpha(orange[300], 0.5)}`,
|
||||
'& .MuiAlert-icon': {
|
||||
color: orange[500],
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: `${alpha(orange[900], 0.5)}`,
|
||||
border: `1px solid ${alpha(orange[800], 0.5)}`,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiDialog: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
'& .MuiDialog-paper': {
|
||||
borderRadius: '10px',
|
||||
border: '1px solid',
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiLinearProgress: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
height: 8,
|
||||
borderRadius: 8,
|
||||
backgroundColor: gray[200],
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: gray[800],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
46
src/shared-theme/customizations/feedback.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Theme, alpha, Components } from '@mui/material/styles';
|
||||
import { gray, orange } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const feedbackCustomizations: Components<Theme> = {
|
||||
MuiAlert: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: 10,
|
||||
backgroundColor: orange[100],
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
border: `1px solid ${alpha(orange[300], 0.5)}`,
|
||||
'& .MuiAlert-icon': {
|
||||
color: orange[500],
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: `${alpha(orange[900], 0.5)}`,
|
||||
border: `1px solid ${alpha(orange[800], 0.5)}`,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiDialog: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
'& .MuiDialog-paper': {
|
||||
borderRadius: '10px',
|
||||
border: '1px solid',
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiLinearProgress: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
height: 8,
|
||||
borderRadius: 8,
|
||||
backgroundColor: gray[200],
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: gray[800],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
460
src/shared-theme/customizations/inputs.js
Normal file
|
@ -0,0 +1,460 @@
|
|||
import * as React from 'react';
|
||||
import { alpha } from '@mui/material/styles';
|
||||
import { outlinedInputClasses } from '@mui/material/OutlinedInput';
|
||||
import { svgIconClasses } from '@mui/material/SvgIcon';
|
||||
import { toggleButtonGroupClasses } from '@mui/material/ToggleButtonGroup';
|
||||
import { toggleButtonClasses } from '@mui/material/ToggleButton';
|
||||
import CheckBoxOutlineBlankRoundedIcon from '@mui/icons-material/CheckBoxOutlineBlankRounded';
|
||||
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
|
||||
import RemoveRoundedIcon from '@mui/icons-material/RemoveRounded';
|
||||
import { gray, brand } from '../themePrimitives';
|
||||
import {autofillFix} from './autoFill';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const inputsCustomizations = {
|
||||
MuiButtonBase: {
|
||||
defaultProps: {
|
||||
disableTouchRipple: true,
|
||||
disableRipple: true,
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
boxSizing: "border-box",
|
||||
transition: "all 100ms ease-in",
|
||||
"&:focus-visible": {
|
||||
outline: `3px solid ${alpha(
|
||||
theme.palette.primary.main,
|
||||
0.5
|
||||
)}`,
|
||||
outlineOffset: "2px",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
boxShadow: "none",
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
textTransform: "none",
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
height: "2.25rem",
|
||||
padding: "8px 12px",
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
size: "medium",
|
||||
},
|
||||
style: {
|
||||
height: "2.5rem", // 40px
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "primary",
|
||||
variant: "contained",
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
backgroundColor: gray[900],
|
||||
backgroundImage: `linear-gradient(to bottom, ${gray[700]}, ${gray[800]})`,
|
||||
boxShadow: `inset 0 1px 0 ${gray[600]}, inset 0 -1px 0 1px hsl(220, 0%, 0%)`,
|
||||
border: `1px solid ${gray[700]}`,
|
||||
"&:hover": {
|
||||
backgroundImage: "none",
|
||||
backgroundColor: gray[700],
|
||||
boxShadow: "none",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[800],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: "black",
|
||||
backgroundColor: gray[50],
|
||||
backgroundImage: `linear-gradient(to bottom, ${gray[100]}, ${gray[50]})`,
|
||||
boxShadow: "inset 0 -1px 0 hsl(220, 30%, 80%)",
|
||||
border: `1px solid ${gray[50]}`,
|
||||
"&:hover": {
|
||||
backgroundImage: "none",
|
||||
backgroundColor: gray[300],
|
||||
boxShadow: "none",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[400],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "secondary",
|
||||
variant: "contained",
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
backgroundColor: brand[300],
|
||||
backgroundImage: `linear-gradient(to bottom, ${alpha(
|
||||
brand[400],
|
||||
0.8
|
||||
)}, ${brand[500]})`,
|
||||
boxShadow: `inset 0 2px 0 ${alpha(
|
||||
brand[200],
|
||||
0.2
|
||||
)}, inset 0 -2px 0 ${alpha(brand[700], 0.4)}`,
|
||||
border: `1px solid ${brand[500]}`,
|
||||
"&:hover": {
|
||||
backgroundColor: brand[700],
|
||||
boxShadow: "none",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: brand[700],
|
||||
backgroundImage: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
variant: "outlined",
|
||||
},
|
||||
style: {
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
border: "1px solid",
|
||||
borderColor: gray[200],
|
||||
backgroundColor: alpha(gray[50], 0.3),
|
||||
"&:hover": {
|
||||
backgroundColor: gray[100],
|
||||
borderColor: gray[300],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[200],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
backgroundColor: gray[800],
|
||||
borderColor: gray[700],
|
||||
"&:hover": {
|
||||
backgroundColor: gray[900],
|
||||
borderColor: gray[600],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[900],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "secondary",
|
||||
variant: "outlined",
|
||||
},
|
||||
style: {
|
||||
color: brand[700],
|
||||
border: "1px solid",
|
||||
borderColor: brand[200],
|
||||
backgroundColor: brand[50],
|
||||
"&:hover": {
|
||||
backgroundColor: brand[100],
|
||||
borderColor: brand[400],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[200], 0.7),
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: brand[50],
|
||||
border: "1px solid",
|
||||
borderColor: brand[900],
|
||||
backgroundColor: alpha(brand[900], 0.3),
|
||||
"&:hover": {
|
||||
borderColor: brand[700],
|
||||
backgroundColor: alpha(brand[900], 0.6),
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[900], 0.5),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
variant: "text",
|
||||
},
|
||||
style: {
|
||||
color: gray[600],
|
||||
"&:hover": {
|
||||
backgroundColor: gray[100],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[200],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: gray[50],
|
||||
"&:hover": {
|
||||
backgroundColor: gray[700],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(gray[700], 0.7),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "secondary",
|
||||
variant: "text",
|
||||
},
|
||||
style: {
|
||||
color: brand[700],
|
||||
"&:hover": {
|
||||
backgroundColor: alpha(brand[100], 0.5),
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[200], 0.7),
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: brand[100],
|
||||
"&:hover": {
|
||||
backgroundColor: alpha(brand[900], 0.5),
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[900], 0.3),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
boxShadow: "none",
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
textTransform: "none",
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
letterSpacing: 0,
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
border: "1px solid ",
|
||||
borderColor: gray[200],
|
||||
backgroundColor: alpha(gray[50], 0.3),
|
||||
"&:hover": {
|
||||
backgroundColor: gray[100],
|
||||
borderColor: gray[300],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[200],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
backgroundColor: gray[800],
|
||||
borderColor: gray[700],
|
||||
"&:hover": {
|
||||
backgroundColor: gray[900],
|
||||
borderColor: gray[600],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[900],
|
||||
},
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
width: "2.25rem",
|
||||
height: "2.25rem",
|
||||
padding: "0.25rem",
|
||||
[`& .${svgIconClasses.root}`]: { fontSize: "1rem" },
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
size: "medium",
|
||||
},
|
||||
style: {
|
||||
width: "2.5rem",
|
||||
height: "2.5rem",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiToggleButtonGroup: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: "10px",
|
||||
boxShadow: `0 4px 16px ${alpha(gray[400], 0.2)}`,
|
||||
[`& .${toggleButtonGroupClasses.selected}`]: {
|
||||
color: brand[500],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
[`& .${toggleButtonGroupClasses.selected}`]: {
|
||||
color: "#fff",
|
||||
},
|
||||
boxShadow: `0 4px 16px ${alpha(brand[700], 0.5)}`,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiToggleButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: "12px 16px",
|
||||
textTransform: "none",
|
||||
borderRadius: "10px",
|
||||
fontWeight: 500,
|
||||
...theme.applyStyles("dark", {
|
||||
color: gray[400],
|
||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.5)",
|
||||
[`&.${toggleButtonClasses.selected}`]: {
|
||||
color: brand[300],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiCheckbox: {
|
||||
defaultProps: {
|
||||
disableRipple: true,
|
||||
icon: (
|
||||
<CheckBoxOutlineBlankRoundedIcon
|
||||
sx={{ color: "hsla(210, 0%, 0%, 0.0)" }}
|
||||
/>
|
||||
),
|
||||
checkedIcon: <CheckRoundedIcon sx={{ height: 14, width: 14 }} />,
|
||||
indeterminateIcon: (
|
||||
<RemoveRoundedIcon sx={{ height: 14, width: 14 }} />
|
||||
),
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
margin: 10,
|
||||
height: 16,
|
||||
width: 16,
|
||||
borderRadius: 5,
|
||||
border: "1px solid ",
|
||||
borderColor: alpha(gray[300], 0.8),
|
||||
boxShadow: "0 0 0 1.5px hsla(210, 0%, 0%, 0.04) inset",
|
||||
backgroundColor: alpha(gray[100], 0.4),
|
||||
transition: "border-color, background-color, 120ms ease-in",
|
||||
"&:hover": {
|
||||
borderColor: brand[300],
|
||||
},
|
||||
"&.Mui-focusVisible": {
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
outlineOffset: "2px",
|
||||
borderColor: brand[400],
|
||||
},
|
||||
"&.Mui-checked": {
|
||||
color: "white",
|
||||
backgroundColor: brand[500],
|
||||
borderColor: brand[500],
|
||||
boxShadow: `none`,
|
||||
"&:hover": {
|
||||
backgroundColor: brand[600],
|
||||
},
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
borderColor: alpha(gray[700], 0.8),
|
||||
boxShadow: "0 0 0 1.5px hsl(210, 0%, 0%) inset",
|
||||
backgroundColor: alpha(gray[900], 0.8),
|
||||
"&:hover": {
|
||||
borderColor: brand[300],
|
||||
},
|
||||
"&.Mui-focusVisible": {
|
||||
borderColor: brand[400],
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
outlineOffset: "2px",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiInputBase: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
border: "none",
|
||||
},
|
||||
input: {
|
||||
"&::placeholder": {
|
||||
opacity: 0.7,
|
||||
color: gray[500],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
styleOverrides: {
|
||||
input: {
|
||||
padding: 0,
|
||||
...autofillFix,
|
||||
},
|
||||
root: ({ theme }) => ({
|
||||
padding: "8px 12px",
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: `1px solid ${(theme.vars || theme).palette.divider}`,
|
||||
// backgroundColor: "#272727",
|
||||
backgroundColor: "#D9E7ED",
|
||||
transition: "border 120ms ease-in",
|
||||
"&:hover": {
|
||||
borderColor: gray[400],
|
||||
},
|
||||
[`&.${outlinedInputClasses.focused}`]: {
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
borderColor: brand[400],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
"&:hover": {
|
||||
borderColor: gray[500],
|
||||
},
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
height: "2.25rem",
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
size: "medium",
|
||||
},
|
||||
style: {
|
||||
height: "2.5rem",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
notchedOutline: {
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiInputAdornment: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: (theme.vars || theme).palette.grey[500],
|
||||
...theme.applyStyles("dark", {
|
||||
color: (theme.vars || theme).palette.grey[400],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiFormLabel: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
typography: theme.typography.caption,
|
||||
marginBottom: 8,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
461
src/shared-theme/customizations/inputs.tsx
Normal file
|
@ -0,0 +1,461 @@
|
|||
import * as React from "react";
|
||||
import { alpha, Theme, Components } from "@mui/material/styles";
|
||||
import { outlinedInputClasses } from "@mui/material/OutlinedInput";
|
||||
import { svgIconClasses } from "@mui/material/SvgIcon";
|
||||
import { toggleButtonGroupClasses } from "@mui/material/ToggleButtonGroup";
|
||||
import { toggleButtonClasses } from "@mui/material/ToggleButton";
|
||||
import CheckBoxOutlineBlankRoundedIcon from "@mui/icons-material/CheckBoxOutlineBlankRounded";
|
||||
import CheckRoundedIcon from "@mui/icons-material/CheckRounded";
|
||||
import RemoveRoundedIcon from "@mui/icons-material/RemoveRounded";
|
||||
import { gray, brand } from "../themePrimitives";
|
||||
import { autofillFix } from "./autoFill";
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const inputsCustomizations: Components<Theme> = {
|
||||
MuiButtonBase: {
|
||||
defaultProps: {
|
||||
disableTouchRipple: true,
|
||||
disableRipple: true,
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
boxSizing: "border-box",
|
||||
transition: "all 100ms ease-in",
|
||||
"&:focus-visible": {
|
||||
outline: `3px solid ${alpha(
|
||||
theme.palette.primary.main,
|
||||
0.5
|
||||
)}`,
|
||||
outlineOffset: "2px",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
boxShadow: "none",
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
textTransform: "none",
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
height: "2.25rem",
|
||||
padding: "8px 12px",
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
size: "medium",
|
||||
},
|
||||
style: {
|
||||
height: "2.5rem", // 40px
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "primary",
|
||||
variant: "contained",
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
backgroundColor: gray[900],
|
||||
backgroundImage: `linear-gradient(to bottom, ${gray[700]}, ${gray[800]})`,
|
||||
boxShadow: `inset 0 1px 0 ${gray[600]}, inset 0 -1px 0 1px hsl(220, 0%, 0%)`,
|
||||
border: `1px solid ${gray[700]}`,
|
||||
"&:hover": {
|
||||
backgroundImage: "none",
|
||||
backgroundColor: gray[700],
|
||||
boxShadow: "none",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[800],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: "black",
|
||||
backgroundColor: gray[50],
|
||||
backgroundImage: `linear-gradient(to bottom, ${gray[100]}, ${gray[50]})`,
|
||||
boxShadow: "inset 0 -1px 0 hsl(220, 30%, 80%)",
|
||||
border: `1px solid ${gray[50]}`,
|
||||
"&:hover": {
|
||||
backgroundImage: "none",
|
||||
backgroundColor: gray[300],
|
||||
boxShadow: "none",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[400],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "secondary",
|
||||
variant: "contained",
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
backgroundColor: brand[300],
|
||||
backgroundImage: `linear-gradient(to bottom, ${alpha(
|
||||
brand[400],
|
||||
0.8
|
||||
)}, ${brand[500]})`,
|
||||
boxShadow: `inset 0 2px 0 ${alpha(
|
||||
brand[200],
|
||||
0.2
|
||||
)}, inset 0 -2px 0 ${alpha(brand[700], 0.4)}`,
|
||||
border: `1px solid ${brand[500]}`,
|
||||
"&:hover": {
|
||||
backgroundColor: brand[700],
|
||||
boxShadow: "none",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: brand[700],
|
||||
backgroundImage: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
variant: "outlined",
|
||||
},
|
||||
style: {
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
border: "1px solid",
|
||||
borderColor: gray[200],
|
||||
backgroundColor: alpha(gray[50], 0.3),
|
||||
"&:hover": {
|
||||
backgroundColor: gray[100],
|
||||
borderColor: gray[300],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[200],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
backgroundColor: gray[800],
|
||||
borderColor: gray[700],
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: gray[900],
|
||||
borderColor: gray[600],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[900],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "secondary",
|
||||
variant: "outlined",
|
||||
},
|
||||
style: {
|
||||
color: brand[700],
|
||||
border: "1px solid",
|
||||
borderColor: brand[200],
|
||||
backgroundColor: brand[50],
|
||||
"&:hover": {
|
||||
backgroundColor: brand[100],
|
||||
borderColor: brand[400],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[200], 0.7),
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: brand[50],
|
||||
border: "1px solid",
|
||||
borderColor: brand[900],
|
||||
backgroundColor: alpha(brand[900], 0.3),
|
||||
"&:hover": {
|
||||
borderColor: brand[700],
|
||||
backgroundColor: alpha(brand[900], 0.6),
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[900], 0.5),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
variant: "text",
|
||||
},
|
||||
style: {
|
||||
color: gray[600],
|
||||
"&:hover": {
|
||||
backgroundColor: gray[100],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[200],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: gray[50],
|
||||
"&:hover": {
|
||||
backgroundColor: gray[700],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(gray[700], 0.7),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
color: "secondary",
|
||||
variant: "text",
|
||||
},
|
||||
style: {
|
||||
color: brand[700],
|
||||
"&:hover": {
|
||||
backgroundColor: alpha(brand[100], 0.5),
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[200], 0.7),
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
color: brand[100],
|
||||
"&:hover": {
|
||||
backgroundColor: alpha(brand[900], 0.5),
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: alpha(brand[900], 0.3),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
boxShadow: "none",
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
textTransform: "none",
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
letterSpacing: 0,
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
border: "1px solid ",
|
||||
borderColor: gray[200],
|
||||
backgroundColor: alpha(gray[50], 0.3),
|
||||
"&:hover": {
|
||||
backgroundColor: gray[100],
|
||||
borderColor: gray[300],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[200],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
backgroundColor: gray[800],
|
||||
borderColor: gray[700],
|
||||
"&:hover": {
|
||||
backgroundColor: gray[900],
|
||||
borderColor: gray[600],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: gray[900],
|
||||
},
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
width: "2.25rem",
|
||||
height: "2.25rem",
|
||||
padding: "0.25rem",
|
||||
[`& .${svgIconClasses.root}`]: { fontSize: "1rem" },
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
size: "medium",
|
||||
},
|
||||
style: {
|
||||
width: "2.5rem",
|
||||
height: "2.5rem",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiToggleButtonGroup: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: "10px",
|
||||
boxShadow: `0 4px 16px ${alpha(gray[400], 0.2)}`,
|
||||
[`& .${toggleButtonGroupClasses.selected}`]: {
|
||||
color: brand[500],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
[`& .${toggleButtonGroupClasses.selected}`]: {
|
||||
color: "#fff",
|
||||
},
|
||||
boxShadow: `0 4px 16px ${alpha(brand[700], 0.5)}`,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiToggleButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: "12px 16px",
|
||||
textTransform: "none",
|
||||
borderRadius: "10px",
|
||||
fontWeight: 500,
|
||||
...theme.applyStyles("dark", {
|
||||
color: gray[400],
|
||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.5)",
|
||||
[`&.${toggleButtonClasses.selected}`]: {
|
||||
color: brand[300],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiCheckbox: {
|
||||
defaultProps: {
|
||||
disableRipple: true,
|
||||
icon: (
|
||||
<CheckBoxOutlineBlankRoundedIcon
|
||||
sx={{ color: "hsla(210, 0%, 0%, 0.0)" }}
|
||||
/>
|
||||
),
|
||||
checkedIcon: <CheckRoundedIcon sx={{ height: 14, width: 14 }} />,
|
||||
indeterminateIcon: (
|
||||
<RemoveRoundedIcon sx={{ height: 14, width: 14 }} />
|
||||
),
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
margin: 10,
|
||||
height: 16,
|
||||
width: 16,
|
||||
borderRadius: 5,
|
||||
border: "1px solid ",
|
||||
borderColor: alpha(gray[300], 0.8),
|
||||
boxShadow: "0 0 0 1.5px hsla(210, 0%, 0%, 0.04) inset",
|
||||
backgroundColor: alpha(gray[100], 0.4),
|
||||
transition: "border-color, background-color, 120ms ease-in",
|
||||
"&:hover": {
|
||||
borderColor: brand[300],
|
||||
},
|
||||
"&.Mui-focusVisible": {
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
outlineOffset: "2px",
|
||||
borderColor: brand[400],
|
||||
},
|
||||
"&.Mui-checked": {
|
||||
color: "white",
|
||||
backgroundColor: brand[500],
|
||||
borderColor: brand[500],
|
||||
boxShadow: `none`,
|
||||
"&:hover": {
|
||||
backgroundColor: brand[600],
|
||||
},
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
borderColor: alpha(gray[700], 0.8),
|
||||
boxShadow: "0 0 0 1.5px hsl(210, 0%, 0%) inset",
|
||||
backgroundColor: alpha(gray[900], 0.8),
|
||||
"&:hover": {
|
||||
borderColor: brand[300],
|
||||
},
|
||||
"&.Mui-focusVisible": {
|
||||
borderColor: brand[400],
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
outlineOffset: "2px",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiInputBase: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
border: "none",
|
||||
},
|
||||
input: {
|
||||
"&::placeholder": {
|
||||
opacity: 0.7,
|
||||
color: gray[600],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
styleOverrides: {
|
||||
input: {
|
||||
padding: 0,
|
||||
...autofillFix,
|
||||
},
|
||||
root: ({ theme }) => ({
|
||||
padding: "8px 12px",
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: `1px solid ${(theme.vars || theme).palette.divider}`,
|
||||
backgroundColor: (theme.vars || theme).palette.background
|
||||
.default,
|
||||
transition: "border 120ms ease-in",
|
||||
"&:hover": {
|
||||
borderColor: gray[400],
|
||||
},
|
||||
[`&.${outlinedInputClasses.focused}`]: {
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
borderColor: brand[400],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
"&:hover": {
|
||||
borderColor: gray[500],
|
||||
},
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
height: "2.25rem",
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
size: "medium",
|
||||
},
|
||||
style: {
|
||||
height: "2.5rem",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
notchedOutline: {
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiInputAdornment: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: (theme.vars || theme).palette.grey[500],
|
||||
...theme.applyStyles("dark", {
|
||||
color: (theme.vars || theme).palette.grey[400],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiFormLabel: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
typography: theme.typography.caption,
|
||||
marginBottom: 8,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
294
src/shared-theme/customizations/navigation.js
Normal file
|
@ -0,0 +1,294 @@
|
|||
import * as React from 'react';
|
||||
import { alpha } from '@mui/material/styles';
|
||||
|
||||
import { buttonBaseClasses } from '@mui/material/ButtonBase';
|
||||
import { dividerClasses } from '@mui/material/Divider';
|
||||
import { menuItemClasses } from '@mui/material/MenuItem';
|
||||
import { selectClasses } from '@mui/material/Select';
|
||||
import { tabClasses } from '@mui/material/Tab';
|
||||
import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
|
||||
import { gray, brand } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const navigationCustomizations = {
|
||||
MuiMenuItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
// padding: '6px 8px',
|
||||
[`&.${menuItemClasses.focusVisible}`]: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
[`&.${menuItemClasses.selected}`]: {
|
||||
[`&.${menuItemClasses.focusVisible}`]: {
|
||||
// backgroundColor: alpha(
|
||||
// theme.palette.action.selected,
|
||||
// 0.3
|
||||
// ),
|
||||
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiMenu: {
|
||||
styleOverrides: {
|
||||
list: {
|
||||
gap: "0px",
|
||||
[`&.${dividerClasses.root}`]: {
|
||||
margin: "0 -8px",
|
||||
},
|
||||
},
|
||||
paper: ({ theme }) => ({
|
||||
marginTop: "4px",
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: `1px solidrgb(141, 135, 135)`,
|
||||
backgroundImage: "none",
|
||||
background: "#D9E7ED",
|
||||
boxShadow:
|
||||
"hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px",
|
||||
[`& .${buttonBaseClasses.root}`]: {
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
"&.Mui-selected:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
background: "#272727",
|
||||
boxShadow:
|
||||
"hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiSelect: {
|
||||
defaultProps: {
|
||||
IconComponent: React.forwardRef((props, ref) => (
|
||||
<UnfoldMoreRoundedIcon fontSize="small" {...props} ref={ref} />
|
||||
)),
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: "1px solid",
|
||||
borderColor: gray[200],
|
||||
backgroundColor: (theme.vars || theme).palette.background.paper,
|
||||
boxShadow: `inset 0 1px 0 1px hsla(220, 0%, 100%, 0.6), inset 0 -1px 0 1px hsla(220, 35%, 90%, 0.5)`,
|
||||
"&:hover": {
|
||||
borderColor: gray[300],
|
||||
backgroundColor: (theme.vars || theme).palette.background
|
||||
.paper,
|
||||
boxShadow: "none",
|
||||
},
|
||||
[`&.${selectClasses.focused}`]: {
|
||||
outlineOffset: 0,
|
||||
borderColor: gray[400],
|
||||
},
|
||||
"&:before, &:after": {
|
||||
display: "none",
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
borderColor: gray[700],
|
||||
backgroundColor: (theme.vars || theme).palette.background
|
||||
.paper,
|
||||
boxShadow: `inset 0 1px 0 1px ${alpha(
|
||||
gray[700],
|
||||
0.15
|
||||
)}, inset 0 -1px 0 1px hsla(220, 0%, 0%, 0.7)`,
|
||||
"&:hover": {
|
||||
borderColor: alpha(gray[700], 0.7),
|
||||
backgroundColor: (theme.vars || theme).palette
|
||||
.background.paper,
|
||||
boxShadow: "none",
|
||||
},
|
||||
[`&.${selectClasses.focused}`]: {
|
||||
outlineOffset: 0,
|
||||
borderColor: gray[900],
|
||||
},
|
||||
"&:before, &:after": {
|
||||
display: "none",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
select: ({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
...theme.applyStyles("dark", {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"&:focus-visible": {
|
||||
backgroundColor: gray[900],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
defaultProps: {
|
||||
underline: "none",
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
fontWeight: 500,
|
||||
position: "relative",
|
||||
textDecoration: "none",
|
||||
width: "fit-content",
|
||||
"&::before": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "1px",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: (theme.vars || theme).palette.text
|
||||
.secondary,
|
||||
opacity: 0.3,
|
||||
transition: "width 0.3s ease, opacity 0.3s ease",
|
||||
},
|
||||
"&:hover::before": {
|
||||
width: 0,
|
||||
},
|
||||
"&:focus-visible": {
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
outlineOffset: "4px",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiDrawer: {
|
||||
styleOverrides: {
|
||||
paper: ({ theme }) => ({
|
||||
backgroundColor: (theme.vars || theme).palette.background
|
||||
.default,
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiPaginationItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
backgroundColor: (theme.vars || theme).palette.grey[900],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
"&.Mui-selected": {
|
||||
color: "black",
|
||||
backgroundColor: (theme.vars || theme).palette.grey[50],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiTabs: {
|
||||
styleOverrides: {
|
||||
root: { minHeight: "fit-content" },
|
||||
indicator: ({ theme }) => ({
|
||||
backgroundColor: (theme.vars || theme).palette.grey[800],
|
||||
...theme.applyStyles("dark", {
|
||||
backgroundColor: (theme.vars || theme).palette.grey[200],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: "6px 8px",
|
||||
marginBottom: "8px",
|
||||
textTransform: "none",
|
||||
minWidth: "fit-content",
|
||||
minHeight: "fit-content",
|
||||
color: (theme.vars || theme).palette.text.secondary,
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: "1px solid",
|
||||
borderColor: "transparent",
|
||||
":hover": {
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
backgroundColor: gray[100],
|
||||
borderColor: gray[200],
|
||||
},
|
||||
[`&.${tabClasses.selected}`]: {
|
||||
color: gray[900],
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
":hover": {
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
backgroundColor: gray[800],
|
||||
borderColor: gray[700],
|
||||
},
|
||||
[`&.${tabClasses.selected}`]: {
|
||||
color: "#fff",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiStepConnector: {
|
||||
styleOverrides: {
|
||||
line: ({ theme }) => ({
|
||||
borderTop: "1px solid",
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
flex: 1,
|
||||
borderRadius: "99px",
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiStepIcon: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: "transparent",
|
||||
border: `1px solid ${gray[400]}`,
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "50%",
|
||||
"& text": {
|
||||
display: "none",
|
||||
},
|
||||
"&.Mui-active": {
|
||||
border: "none",
|
||||
color: (theme.vars || theme).palette.primary.main,
|
||||
},
|
||||
"&.Mui-completed": {
|
||||
border: "none",
|
||||
color: (theme.vars || theme).palette.success.main,
|
||||
},
|
||||
...theme.applyStyles("dark", {
|
||||
border: `1px solid ${gray[700]}`,
|
||||
"&.Mui-active": {
|
||||
border: "none",
|
||||
color: (theme.vars || theme).palette.primary.light,
|
||||
},
|
||||
"&.Mui-completed": {
|
||||
border: "none",
|
||||
color: (theme.vars || theme).palette.success.light,
|
||||
},
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: { completed: true },
|
||||
style: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiStepLabel: {
|
||||
styleOverrides: {
|
||||
label: ({ theme }) => ({
|
||||
"&.Mui-completed": {
|
||||
opacity: 0.6,
|
||||
...theme.applyStyles("dark", { opacity: 0.5 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
279
src/shared-theme/customizations/navigation.tsx
Normal file
|
@ -0,0 +1,279 @@
|
|||
import * as React from 'react';
|
||||
import { Theme, alpha, Components } from '@mui/material/styles';
|
||||
import { SvgIconProps } from '@mui/material/SvgIcon';
|
||||
import { buttonBaseClasses } from '@mui/material/ButtonBase';
|
||||
import { dividerClasses } from '@mui/material/Divider';
|
||||
import { menuItemClasses } from '@mui/material/MenuItem';
|
||||
import { selectClasses } from '@mui/material/Select';
|
||||
import { tabClasses } from '@mui/material/Tab';
|
||||
import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
|
||||
import { gray, brand } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const navigationCustomizations: Components<Theme> = {
|
||||
MuiMenuItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
padding: '6px 8px',
|
||||
[`&.${menuItemClasses.focusVisible}`]: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
[`&.${menuItemClasses.selected}`]: {
|
||||
[`&.${menuItemClasses.focusVisible}`]: {
|
||||
backgroundColor: alpha(theme.palette.action.selected, 0.3),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiMenu: {
|
||||
styleOverrides: {
|
||||
list: {
|
||||
gap: '0px',
|
||||
[`&.${dividerClasses.root}`]: {
|
||||
margin: '0 -8px',
|
||||
},
|
||||
},
|
||||
paper: ({ theme }) => ({
|
||||
marginTop: '4px',
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
// border: `1px solid ${(theme.vars || theme).palette.divider}`,
|
||||
backgroundImage: 'none',
|
||||
// background: 'hsl(0, 0%, 100%)',
|
||||
boxShadow:
|
||||
'hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px',
|
||||
[`& .${buttonBaseClasses.root}`]: {
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: alpha(theme.palette.action.selected, 0.3),
|
||||
},
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
background: gray[900],
|
||||
boxShadow:
|
||||
'hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiSelect: {
|
||||
defaultProps: {
|
||||
IconComponent: React.forwardRef<SVGSVGElement, SvgIconProps>((props, ref) => (
|
||||
<UnfoldMoreRoundedIcon fontSize="small" {...props} ref={ref} />
|
||||
)),
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: '1px solid',
|
||||
borderColor: gray[200],
|
||||
backgroundColor: (theme.vars || theme).palette.background.paper,
|
||||
boxShadow: `inset 0 1px 0 1px hsla(220, 0%, 100%, 0.6), inset 0 -1px 0 1px hsla(220, 35%, 90%, 0.5)`,
|
||||
'&:hover': {
|
||||
borderColor: gray[300],
|
||||
backgroundColor: (theme.vars || theme).palette.background.paper,
|
||||
boxShadow: 'none',
|
||||
},
|
||||
[`&.${selectClasses.focused}`]: {
|
||||
outlineOffset: 0,
|
||||
borderColor: gray[400],
|
||||
},
|
||||
'&:before, &:after': {
|
||||
display: 'none',
|
||||
},
|
||||
|
||||
...theme.applyStyles('dark', {
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
borderColor: gray[700],
|
||||
backgroundColor: (theme.vars || theme).palette.background.paper,
|
||||
boxShadow: `inset 0 1px 0 1px ${alpha(gray[700], 0.15)}, inset 0 -1px 0 1px hsla(220, 0%, 0%, 0.7)`,
|
||||
'&:hover': {
|
||||
borderColor: alpha(gray[700], 0.7),
|
||||
backgroundColor: (theme.vars || theme).palette.background.paper,
|
||||
boxShadow: 'none',
|
||||
},
|
||||
[`&.${selectClasses.focused}`]: {
|
||||
outlineOffset: 0,
|
||||
borderColor: gray[900],
|
||||
},
|
||||
'&:before, &:after': {
|
||||
display: 'none',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
select: ({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
...theme.applyStyles('dark', {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'&:focus-visible': {
|
||||
backgroundColor: gray[900],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
defaultProps: {
|
||||
underline: 'none',
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
fontWeight: 500,
|
||||
position: 'relative',
|
||||
textDecoration: 'none',
|
||||
width: 'fit-content',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: (theme.vars || theme).palette.text.secondary,
|
||||
opacity: 0.3,
|
||||
transition: 'width 0.3s ease, opacity 0.3s ease',
|
||||
},
|
||||
'&:hover::before': {
|
||||
width: 0,
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: `3px solid ${alpha(brand[500], 0.5)}`,
|
||||
outlineOffset: '4px',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiDrawer: {
|
||||
styleOverrides: {
|
||||
paper: ({ theme }) => ({
|
||||
backgroundColor: (theme.vars || theme).palette.background.default,
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiPaginationItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
backgroundColor: (theme.vars || theme).palette.grey[900],
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
'&.Mui-selected': {
|
||||
color: 'black',
|
||||
backgroundColor: (theme.vars || theme).palette.grey[50],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiTabs: {
|
||||
styleOverrides: {
|
||||
root: { minHeight: 'fit-content' },
|
||||
indicator: ({ theme }) => ({
|
||||
backgroundColor: (theme.vars || theme).palette.grey[800],
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: (theme.vars || theme).palette.grey[200],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: '6px 8px',
|
||||
marginBottom: '8px',
|
||||
textTransform: 'none',
|
||||
minWidth: 'fit-content',
|
||||
minHeight: 'fit-content',
|
||||
color: (theme.vars || theme).palette.text.secondary,
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: '1px solid',
|
||||
borderColor: 'transparent',
|
||||
':hover': {
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
backgroundColor: gray[100],
|
||||
borderColor: gray[200],
|
||||
},
|
||||
[`&.${tabClasses.selected}`]: {
|
||||
color: gray[900],
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
':hover': {
|
||||
color: (theme.vars || theme).palette.text.primary,
|
||||
backgroundColor: gray[800],
|
||||
borderColor: gray[700],
|
||||
},
|
||||
[`&.${tabClasses.selected}`]: {
|
||||
color: '#fff',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiStepConnector: {
|
||||
styleOverrides: {
|
||||
line: ({ theme }) => ({
|
||||
borderTop: '1px solid',
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
flex: 1,
|
||||
borderRadius: '99px',
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiStepIcon: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: 'transparent',
|
||||
border: `1px solid ${gray[400]}`,
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: '50%',
|
||||
'& text': {
|
||||
display: 'none',
|
||||
},
|
||||
'&.Mui-active': {
|
||||
border: 'none',
|
||||
color: (theme.vars || theme).palette.primary.main,
|
||||
},
|
||||
'&.Mui-completed': {
|
||||
border: 'none',
|
||||
color: (theme.vars || theme).palette.success.main,
|
||||
},
|
||||
...theme.applyStyles('dark', {
|
||||
border: `1px solid ${gray[700]}`,
|
||||
'&.Mui-active': {
|
||||
border: 'none',
|
||||
color: (theme.vars || theme).palette.primary.light,
|
||||
},
|
||||
'&.Mui-completed': {
|
||||
border: 'none',
|
||||
color: (theme.vars || theme).palette.success.light,
|
||||
},
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: { completed: true },
|
||||
style: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiStepLabel: {
|
||||
styleOverrides: {
|
||||
label: ({ theme }) => ({
|
||||
'&.Mui-completed': {
|
||||
opacity: 0.6,
|
||||
...theme.applyStyles('dark', { opacity: 0.5 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
114
src/shared-theme/customizations/surfaces.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { alpha } from '@mui/material/styles';
|
||||
import { gray } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const surfacesCustomizations = {
|
||||
MuiAccordion: {
|
||||
defaultProps: {
|
||||
elevation: 0,
|
||||
disableGutters: true,
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: 4,
|
||||
overflow: 'clip',
|
||||
backgroundColor: (theme.vars || theme).palette.background.default,
|
||||
border: '1px solid',
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
':before': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
'&:not(:last-of-type)': {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
'&:first-of-type': {
|
||||
borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
|
||||
borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
|
||||
},
|
||||
'&:last-of-type': {
|
||||
borderBottomLeftRadius: (theme.vars || theme).shape.borderRadius,
|
||||
borderBottomRightRadius: (theme.vars || theme).shape.borderRadius,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiAccordionSummary: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
border: 'none',
|
||||
borderRadius: 8,
|
||||
'&:hover': { backgroundColor: gray[50] },
|
||||
'&:focus-visible': { backgroundColor: 'transparent' },
|
||||
...theme.applyStyles('dark', {
|
||||
'&:hover': { backgroundColor: gray[800] },
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiAccordionDetails: {
|
||||
styleOverrides: {
|
||||
root: { mb: 20, border: 'none' },
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
defaultProps: {
|
||||
elevation: 0,
|
||||
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => {
|
||||
return {
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
transition: 'all 100ms ease',
|
||||
backgroundColor: gray[50],
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: `1px solid ${(theme.vars || theme).palette.divider}`,
|
||||
boxShadow: 'none',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor:"#1C1C1C",
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
variant: 'outlined',
|
||||
},
|
||||
style: {
|
||||
border: `1px solid ${(theme.vars || theme).palette.divider}`,
|
||||
boxShadow: 'none',
|
||||
background: 'hsl(0, 0%, 100%)',
|
||||
...theme.applyStyles('dark', {
|
||||
background: alpha(gray[900], 0.4),
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCardContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0,
|
||||
'&:last-child': { paddingBottom: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCardHeader: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCardActions: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
113
src/shared-theme/customizations/surfaces.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
import { alpha, Theme, Components } from '@mui/material/styles';
|
||||
import { gray } from '../themePrimitives';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const surfacesCustomizations: Components<Theme> = {
|
||||
MuiAccordion: {
|
||||
defaultProps: {
|
||||
elevation: 0,
|
||||
disableGutters: true,
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: 4,
|
||||
overflow: 'clip',
|
||||
backgroundColor: (theme.vars || theme).palette.background.default,
|
||||
border: '1px solid',
|
||||
borderColor: (theme.vars || theme).palette.divider,
|
||||
':before': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
'&:not(:last-of-type)': {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
'&:first-of-type': {
|
||||
borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
|
||||
borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
|
||||
},
|
||||
'&:last-of-type': {
|
||||
borderBottomLeftRadius: (theme.vars || theme).shape.borderRadius,
|
||||
borderBottomRightRadius: (theme.vars || theme).shape.borderRadius,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiAccordionSummary: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
border: 'none',
|
||||
borderRadius: 8,
|
||||
'&:hover': { backgroundColor: gray[50] },
|
||||
'&:focus-visible': { backgroundColor: 'transparent' },
|
||||
...theme.applyStyles('dark', {
|
||||
'&:hover': { backgroundColor: gray[800] },
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiAccordionDetails: {
|
||||
styleOverrides: {
|
||||
root: { mb: 20, border: 'none' },
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
defaultProps: {
|
||||
elevation: 0,
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => {
|
||||
return {
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
transition: 'all 100ms ease',
|
||||
backgroundColor: gray[50],
|
||||
borderRadius: (theme.vars || theme).shape.borderRadius,
|
||||
border: `1px solid ${(theme.vars || theme).palette.divider}`,
|
||||
boxShadow: 'none',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: gray[800],
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: {
|
||||
variant: 'outlined',
|
||||
},
|
||||
style: {
|
||||
border: `1px solid ${(theme.vars || theme).palette.divider}`,
|
||||
boxShadow: 'none',
|
||||
background: 'hsl(0, 0%, 100%)',
|
||||
...theme.applyStyles('dark', {
|
||||
background: alpha(gray[900], 0.4),
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCardContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0,
|
||||
'&:last-child': { paddingBottom: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCardHeader: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCardActions: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
384
src/shared-theme/themePrimitives.js
Normal file
|
@ -0,0 +1,384 @@
|
|||
import { createTheme, alpha } from "@mui/material/styles";
|
||||
|
||||
const defaultTheme = createTheme();
|
||||
|
||||
const customShadows = [...defaultTheme.shadows];
|
||||
|
||||
export const brand = {
|
||||
50: "hsl(210, 100%, 95%)",
|
||||
100: "hsl(210, 100%, 92%)",
|
||||
200: "hsl(210, 100%, 80%)",
|
||||
300: "hsl(210, 100%, 65%)",
|
||||
400: "hsl(210, 98%, 48%)",
|
||||
500: "hsl(210, 98%, 42%)",
|
||||
600: "hsl(210, 98%, 55%)",
|
||||
700: "hsl(210, 100%, 35%)",
|
||||
800: "hsl(210, 100%, 16%)",
|
||||
900: "hsl(210, 100%, 21%)",
|
||||
};
|
||||
|
||||
export const gray = {
|
||||
50: "hsl(220, 35%, 97%)",
|
||||
100: "hsl(220, 30%, 94%)",
|
||||
200: "hsl(220, 20%, 88%)",
|
||||
300: "hsl(220, 20%, 80%)",
|
||||
400: "hsl(220, 20%, 65%)",
|
||||
500: "hsl(220, 20%, 42%)",
|
||||
600: "hsl(220, 20%, 35%)",
|
||||
700: "hsl(220, 20%, 25%)",
|
||||
800: "hsl(220, 30%, 6%)",
|
||||
900: "hsl(220, 35%, 3%)",
|
||||
};
|
||||
|
||||
export const green = {
|
||||
50: "hsl(120, 80%, 98%)",
|
||||
100: "hsl(120, 75%, 94%)",
|
||||
200: "hsl(120, 75%, 87%)",
|
||||
300: "hsl(120, 61%, 77%)",
|
||||
400: "hsl(120, 44%, 53%)",
|
||||
500: "hsl(120, 59%, 30%)",
|
||||
600: "hsl(120, 70%, 25%)",
|
||||
700: "hsl(120, 75%, 16%)",
|
||||
800: "hsl(120, 84%, 10%)",
|
||||
900: "hsl(120, 87%, 6%)",
|
||||
};
|
||||
|
||||
export const orange = {
|
||||
50: "hsl(45, 100%, 97%)",
|
||||
100: "hsl(45, 92%, 90%)",
|
||||
200: "hsl(45, 94%, 80%)",
|
||||
300: "hsl(45, 90%, 65%)",
|
||||
400: "hsl(45, 90%, 40%)",
|
||||
500: "hsl(45, 90%, 35%)",
|
||||
600: "hsl(45, 91%, 25%)",
|
||||
700: "hsl(45, 94%, 20%)",
|
||||
800: "hsl(45, 95%, 16%)",
|
||||
900: "hsl(45, 93%, 12%)",
|
||||
};
|
||||
|
||||
export const red = {
|
||||
50: "hsl(0, 100%, 97%)",
|
||||
100: "hsl(0, 92%, 90%)",
|
||||
200: "hsl(0, 94%, 80%)",
|
||||
300: "hsl(0, 90%, 65%)",
|
||||
400: "hsl(0, 90%, 40%)",
|
||||
500: "hsl(0, 90%, 30%)",
|
||||
600: "hsl(0, 91%, 25%)",
|
||||
700: "hsl(0, 94%, 18%)",
|
||||
800: "hsl(0, 95%, 12%)",
|
||||
900: "hsl(0, 93%, 6%)",
|
||||
};
|
||||
|
||||
export const getDesignTokens = (mode) => {
|
||||
customShadows[1] =
|
||||
mode === "dark"
|
||||
? "hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px"
|
||||
: "hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px";
|
||||
|
||||
return {
|
||||
palette: {
|
||||
mode,
|
||||
primary: {
|
||||
light: brand[200],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
contrastText: brand[50],
|
||||
...(mode === "dark" && {
|
||||
contrastText: brand[50],
|
||||
light: brand[300],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
}),
|
||||
},
|
||||
info: {
|
||||
light: brand[100],
|
||||
main: brand[300],
|
||||
dark: brand[600],
|
||||
contrastText: gray[50],
|
||||
...(mode === "dark" && {
|
||||
contrastText: brand[300],
|
||||
light: brand[500],
|
||||
main: brand[700],
|
||||
dark: brand[900],
|
||||
}),
|
||||
},
|
||||
warning: {
|
||||
light: orange[300],
|
||||
main: orange[400],
|
||||
dark: orange[800],
|
||||
...(mode === "dark" && {
|
||||
light: orange[400],
|
||||
main: orange[500],
|
||||
dark: orange[700],
|
||||
}),
|
||||
},
|
||||
error: {
|
||||
light: red[300],
|
||||
main: red[400],
|
||||
dark: red[800],
|
||||
...(mode === "dark" && {
|
||||
light: red[400],
|
||||
main: red[500],
|
||||
dark: red[700],
|
||||
}),
|
||||
},
|
||||
success: {
|
||||
light: green[300],
|
||||
main: green[400],
|
||||
dark: green[800],
|
||||
...(mode === "dark" && {
|
||||
light: green[400],
|
||||
main: green[500],
|
||||
dark: green[700],
|
||||
}),
|
||||
},
|
||||
grey: {
|
||||
...gray,
|
||||
},
|
||||
divider:
|
||||
mode === "dark" ? alpha(gray[700], 0.6) : alpha(gray[300], 0.4),
|
||||
background: {
|
||||
default: "hsl(0, 0%, 99%)",
|
||||
paper: "hsl(220, 35%, 97%)",
|
||||
...(mode === "dark" && {
|
||||
default: gray[900],
|
||||
paper: "hsl(220, 30%, 7%)",
|
||||
}),
|
||||
},
|
||||
text: {
|
||||
primary: gray[800],
|
||||
secondary: gray[600],
|
||||
warning: orange[400],
|
||||
...(mode === "dark" && {
|
||||
primary: "hsl(0, 0%, 100%)",
|
||||
secondary: gray[400],
|
||||
}),
|
||||
},
|
||||
action: {
|
||||
hover: alpha(gray[200], 0.2),
|
||||
selected: `${alpha(gray[200], 0.3)}`,
|
||||
...(mode === "dark" && {
|
||||
hover: alpha(gray[600], 0.2),
|
||||
selected: alpha(gray[600], 0.3),
|
||||
}),
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: "Gilory",
|
||||
h1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(48),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
h2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(36),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h3: {
|
||||
fontSize: defaultTheme.typography.pxToRem(30),
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h4: {
|
||||
fontSize: defaultTheme.typography.pxToRem(24),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
h5: {
|
||||
fontSize: defaultTheme.typography.pxToRem(20),
|
||||
fontWeight: 600,
|
||||
},
|
||||
h6: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
fontWeight: 600,
|
||||
},
|
||||
subtitle1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
},
|
||||
subtitle2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 500,
|
||||
},
|
||||
body1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
},
|
||||
body2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 400,
|
||||
},
|
||||
caption: {
|
||||
fontSize: defaultTheme.typography.pxToRem(12),
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
shadows: customShadows,
|
||||
};
|
||||
};
|
||||
|
||||
export const colorSchemes = {
|
||||
light: {
|
||||
palette: {
|
||||
primary: {
|
||||
light: brand[200],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
contrastText: brand[50],
|
||||
},
|
||||
info: {
|
||||
light: brand[100],
|
||||
main: brand[300],
|
||||
dark: brand[600],
|
||||
contrastText: gray[50],
|
||||
},
|
||||
warning: {
|
||||
light: orange[300],
|
||||
main: orange[400],
|
||||
dark: orange[800],
|
||||
},
|
||||
error: {
|
||||
light: red[300],
|
||||
main: red[400],
|
||||
dark: red[800],
|
||||
},
|
||||
success: {
|
||||
light: green[300],
|
||||
main: green[400],
|
||||
dark: green[800],
|
||||
},
|
||||
grey: {
|
||||
...gray,
|
||||
},
|
||||
divider: alpha(gray[300], 0.4),
|
||||
background: {
|
||||
default: "hsl(0, 0%, 99%)",
|
||||
paper: "hsl(220, 35%, 97%)",
|
||||
},
|
||||
text: {
|
||||
primary: gray[800],
|
||||
secondary: gray[600],
|
||||
warning: orange[400],
|
||||
},
|
||||
action: {
|
||||
hover: alpha(gray[200], 0.2),
|
||||
selected: `${alpha(gray[200], 0.3)}`,
|
||||
},
|
||||
baseShadow:
|
||||
"hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px",
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
palette: {
|
||||
primary: {
|
||||
contrastText: brand[50],
|
||||
light: brand[300],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
},
|
||||
info: {
|
||||
contrastText: brand[300],
|
||||
light: brand[500],
|
||||
main: brand[700],
|
||||
dark: brand[900],
|
||||
},
|
||||
warning: {
|
||||
light: orange[400],
|
||||
main: orange[500],
|
||||
dark: orange[700],
|
||||
},
|
||||
error: {
|
||||
light: red[400],
|
||||
main: red[500],
|
||||
dark: red[700],
|
||||
},
|
||||
success: {
|
||||
light: green[400],
|
||||
main: green[500],
|
||||
dark: green[700],
|
||||
},
|
||||
grey: {
|
||||
...gray,
|
||||
},
|
||||
divider: alpha(gray[700], 0.6),
|
||||
background: {
|
||||
default: gray[900],
|
||||
paper: "hsl(220, 30%, 7%)",
|
||||
},
|
||||
text: {
|
||||
primary: "hsl(0, 0%, 100%)",
|
||||
secondary: gray[400],
|
||||
},
|
||||
action: {
|
||||
hover: alpha(gray[600], 0.2),
|
||||
selected: alpha(gray[600], 0.3),
|
||||
},
|
||||
baseShadow:
|
||||
"hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const typography = {
|
||||
fontFamily: "Gilory",
|
||||
h1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(48),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
h2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(36),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h3: {
|
||||
fontSize: defaultTheme.typography.pxToRem(30),
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h4: {
|
||||
fontSize: defaultTheme.typography.pxToRem(24),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
h5: {
|
||||
fontSize: defaultTheme.typography.pxToRem(20),
|
||||
fontWeight: 600,
|
||||
},
|
||||
h6: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
fontWeight: 600,
|
||||
},
|
||||
subtitle1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
},
|
||||
subtitle2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 500,
|
||||
},
|
||||
body1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
},
|
||||
body2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 400,
|
||||
},
|
||||
caption: {
|
||||
fontSize: defaultTheme.typography.pxToRem(12),
|
||||
fontWeight: 400,
|
||||
},
|
||||
};
|
||||
|
||||
export const shape = {
|
||||
borderRadius: 8,
|
||||
};
|
||||
|
||||
const defaultShadows = [
|
||||
"none",
|
||||
"var(--template-palette-baseShadow)",
|
||||
...defaultTheme.shadows.slice(2),
|
||||
];
|
||||
|
||||
export const shadows = defaultShadows;
|
410
src/shared-theme/themePrimitives.ts
Normal file
|
@ -0,0 +1,410 @@
|
|||
import { createTheme, alpha, PaletteMode, Shadows } from "@mui/material/styles";
|
||||
|
||||
declare module "@mui/material/Paper" {
|
||||
interface PaperPropsVariantOverrides {
|
||||
highlighted: true;
|
||||
}
|
||||
}
|
||||
declare module "@mui/material/styles/createPalette" {
|
||||
interface ColorRange {
|
||||
50: string;
|
||||
100: string;
|
||||
200: string;
|
||||
300: string;
|
||||
400: string;
|
||||
500: string;
|
||||
600: string;
|
||||
700: string;
|
||||
800: string;
|
||||
900: string;
|
||||
}
|
||||
|
||||
interface PaletteColor extends ColorRange {}
|
||||
|
||||
interface Palette {
|
||||
baseShadow: string;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultTheme = createTheme();
|
||||
|
||||
const customShadows: Shadows = [...defaultTheme.shadows];
|
||||
|
||||
export const brand = {
|
||||
50: "hsl(210, 100%, 95%)",
|
||||
100: "hsl(210, 100%, 92%)",
|
||||
200: "hsl(210, 100%, 80%)",
|
||||
300: "hsl(210, 100%, 65%)",
|
||||
400: "hsl(210, 98%, 48%)",
|
||||
500: "hsl(210, 98%, 42%)",
|
||||
600: "hsl(210, 98%, 55%)",
|
||||
700: "hsl(210, 100%, 35%)",
|
||||
800: "hsl(210, 100%, 16%)",
|
||||
900: "hsl(210, 100%, 21%)",
|
||||
};
|
||||
|
||||
export const gray = {
|
||||
50: "hsl(220, 35%, 97%)",
|
||||
100: "hsl(220, 30%, 94%)",
|
||||
200: "hsl(220, 20%, 88%)",
|
||||
300: "hsl(220, 20%, 80%)",
|
||||
400: "hsl(220, 20%, 65%)",
|
||||
500: "hsl(220, 20%, 42%)",
|
||||
600: "hsl(220, 20%, 35%)",
|
||||
700: "hsl(220, 20%, 25%)",
|
||||
800: "hsl(220, 30%, 6%)",
|
||||
900: "hsl(220, 35%, 3%)",
|
||||
};
|
||||
|
||||
export const green = {
|
||||
50: "hsl(120, 80%, 98%)",
|
||||
100: "hsl(120, 75%, 94%)",
|
||||
200: "hsl(120, 75%, 87%)",
|
||||
300: "hsl(120, 61%, 77%)",
|
||||
400: "hsl(120, 44%, 53%)",
|
||||
500: "hsl(120, 59%, 30%)",
|
||||
600: "hsl(120, 70%, 25%)",
|
||||
700: "hsl(120, 75%, 16%)",
|
||||
800: "hsl(120, 84%, 10%)",
|
||||
900: "hsl(120, 87%, 6%)",
|
||||
};
|
||||
|
||||
export const orange = {
|
||||
50: "hsl(45, 100%, 97%)",
|
||||
100: "hsl(45, 92%, 90%)",
|
||||
200: "hsl(45, 94%, 80%)",
|
||||
300: "hsl(45, 90%, 65%)",
|
||||
400: "hsl(45, 90%, 40%)",
|
||||
500: "hsl(45, 90%, 35%)",
|
||||
600: "hsl(45, 91%, 25%)",
|
||||
700: "hsl(45, 94%, 20%)",
|
||||
800: "hsl(45, 95%, 16%)",
|
||||
900: "hsl(45, 93%, 12%)",
|
||||
};
|
||||
|
||||
export const red = {
|
||||
50: "hsl(0, 100%, 97%)",
|
||||
100: "hsl(0, 92%, 90%)",
|
||||
200: "hsl(0, 94%, 80%)",
|
||||
300: "hsl(0, 90%, 65%)",
|
||||
400: "hsl(0, 90%, 40%)",
|
||||
500: "hsl(0, 90%, 30%)",
|
||||
600: "hsl(0, 91%, 25%)",
|
||||
700: "hsl(0, 94%, 18%)",
|
||||
800: "hsl(0, 95%, 12%)",
|
||||
900: "hsl(0, 93%, 6%)",
|
||||
};
|
||||
|
||||
export const getDesignTokens = (mode: PaletteMode) => {
|
||||
customShadows[1] =
|
||||
mode === "dark"
|
||||
? "hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px"
|
||||
: "hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px";
|
||||
|
||||
return {
|
||||
palette: {
|
||||
mode,
|
||||
primary: {
|
||||
light: brand[200],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
contrastText: brand[50],
|
||||
...(mode === "dark" && {
|
||||
contrastText: brand[50],
|
||||
light: brand[300],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
}),
|
||||
},
|
||||
info: {
|
||||
light: brand[100],
|
||||
main: brand[300],
|
||||
dark: brand[600],
|
||||
contrastText: gray[50],
|
||||
...(mode === "dark" && {
|
||||
contrastText: brand[300],
|
||||
light: brand[500],
|
||||
main: brand[700],
|
||||
dark: brand[900],
|
||||
}),
|
||||
},
|
||||
warning: {
|
||||
light: orange[300],
|
||||
main: orange[400],
|
||||
dark: orange[800],
|
||||
...(mode === "dark" && {
|
||||
light: orange[400],
|
||||
main: orange[500],
|
||||
dark: orange[700],
|
||||
}),
|
||||
},
|
||||
error: {
|
||||
light: red[300],
|
||||
main: red[400],
|
||||
dark: red[800],
|
||||
...(mode === "dark" && {
|
||||
light: red[400],
|
||||
main: red[500],
|
||||
dark: red[700],
|
||||
}),
|
||||
},
|
||||
success: {
|
||||
light: green[300],
|
||||
main: green[400],
|
||||
dark: green[800],
|
||||
...(mode === "dark" && {
|
||||
light: green[400],
|
||||
main: green[500],
|
||||
dark: green[700],
|
||||
}),
|
||||
},
|
||||
grey: {
|
||||
...gray,
|
||||
},
|
||||
divider:
|
||||
mode === "dark" ? alpha(gray[700], 0.6) : alpha(gray[300], 0.4),
|
||||
background: {
|
||||
default: "hsl(0, 0%, 99%)",
|
||||
paper: "hsl(220, 35%, 97%)",
|
||||
...(mode === "dark" && {
|
||||
default: gray[900],
|
||||
paper: "hsl(220, 30%, 7%)",
|
||||
}),
|
||||
},
|
||||
text: {
|
||||
primary: gray[800],
|
||||
secondary: gray[600],
|
||||
warning: orange[400],
|
||||
...(mode === "dark" && {
|
||||
primary: "hsl(0, 0%, 100%)",
|
||||
secondary: gray[400],
|
||||
}),
|
||||
},
|
||||
action: {
|
||||
hover: alpha(gray[200], 0.2),
|
||||
selected: `${alpha(gray[200], 0.3)}`,
|
||||
...(mode === "dark" && {
|
||||
hover: alpha(gray[600], 0.2),
|
||||
selected: alpha(gray[600], 0.3),
|
||||
}),
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: "Gilroy",
|
||||
h1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(48),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
h2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(36),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h3: {
|
||||
fontSize: defaultTheme.typography.pxToRem(30),
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h4: {
|
||||
fontSize: defaultTheme.typography.pxToRem(24),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
h5: {
|
||||
fontSize: defaultTheme.typography.pxToRem(20),
|
||||
fontWeight: 600,
|
||||
},
|
||||
h6: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
fontWeight: 600,
|
||||
},
|
||||
subtitle1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
},
|
||||
subtitle2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 500,
|
||||
},
|
||||
body1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
},
|
||||
body2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 400,
|
||||
},
|
||||
caption: {
|
||||
fontSize: defaultTheme.typography.pxToRem(12),
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
shadows: customShadows,
|
||||
};
|
||||
};
|
||||
|
||||
export const colorSchemes = {
|
||||
light: {
|
||||
palette: {
|
||||
primary: {
|
||||
light: brand[200],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
contrastText: brand[50],
|
||||
},
|
||||
info: {
|
||||
light: brand[100],
|
||||
main: brand[300],
|
||||
dark: brand[600],
|
||||
contrastText: gray[50],
|
||||
},
|
||||
warning: {
|
||||
light: orange[300],
|
||||
main: orange[400],
|
||||
dark: orange[800],
|
||||
},
|
||||
error: {
|
||||
light: red[300],
|
||||
main: red[400],
|
||||
dark: red[800],
|
||||
},
|
||||
success: {
|
||||
light: green[300],
|
||||
main: green[400],
|
||||
dark: green[800],
|
||||
},
|
||||
grey: {
|
||||
...gray,
|
||||
},
|
||||
divider: alpha(gray[300], 0.4),
|
||||
background: {
|
||||
default: "hsl(0, 0%, 99%)",
|
||||
paper: "hsl(220, 35%, 97%)",
|
||||
},
|
||||
text: {
|
||||
primary: gray[800],
|
||||
secondary: gray[600],
|
||||
warning: orange[400],
|
||||
},
|
||||
action: {
|
||||
hover: alpha(gray[200], 0.2),
|
||||
selected: `${alpha(gray[200], 0.3)}`,
|
||||
},
|
||||
baseShadow:
|
||||
"hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px",
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
palette: {
|
||||
primary: {
|
||||
contrastText: brand[50],
|
||||
light: brand[300],
|
||||
main: brand[400],
|
||||
dark: brand[700],
|
||||
},
|
||||
info: {
|
||||
contrastText: brand[300],
|
||||
light: brand[500],
|
||||
main: brand[700],
|
||||
dark: brand[900],
|
||||
},
|
||||
warning: {
|
||||
light: orange[400],
|
||||
main: orange[500],
|
||||
dark: orange[700],
|
||||
},
|
||||
error: {
|
||||
light: red[400],
|
||||
main: red[500],
|
||||
dark: red[700],
|
||||
},
|
||||
success: {
|
||||
light: green[400],
|
||||
main: green[500],
|
||||
dark: green[700],
|
||||
},
|
||||
grey: {
|
||||
...gray,
|
||||
},
|
||||
divider: alpha(gray[700], 0.6),
|
||||
background: {
|
||||
default: gray[900],
|
||||
paper: "hsl(220, 30%, 7%)",
|
||||
},
|
||||
text: {
|
||||
primary: "hsl(0, 0%, 100%)",
|
||||
secondary: gray[400],
|
||||
},
|
||||
action: {
|
||||
hover: alpha(gray[600], 0.2),
|
||||
selected: alpha(gray[600], 0.3),
|
||||
},
|
||||
baseShadow:
|
||||
"hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const typography = {
|
||||
fontFamily: "Gilroy",
|
||||
h1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(48),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
h2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(36),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h3: {
|
||||
fontSize: defaultTheme.typography.pxToRem(30),
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h4: {
|
||||
fontSize: defaultTheme.typography.pxToRem(24),
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
h5: {
|
||||
fontSize: defaultTheme.typography.pxToRem(20),
|
||||
fontWeight: 600,
|
||||
},
|
||||
h6: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
fontWeight: 600,
|
||||
},
|
||||
subtitle1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(18),
|
||||
},
|
||||
subtitle2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 500,
|
||||
},
|
||||
body1: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
},
|
||||
body2: {
|
||||
fontSize: defaultTheme.typography.pxToRem(14),
|
||||
fontWeight: 400,
|
||||
},
|
||||
caption: {
|
||||
fontSize: defaultTheme.typography.pxToRem(12),
|
||||
fontWeight: 400,
|
||||
},
|
||||
};
|
||||
|
||||
export const shape = {
|
||||
borderRadius: 8,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const defaultShadows: Shadows = [
|
||||
"none",
|
||||
"var(--template-palette-baseShadow)",
|
||||
...defaultTheme.shadows.slice(2),
|
||||
];
|
||||
export const shadows = defaultShadows;
|
30
tsconfig.app.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
32
tsconfig.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"jsx": "react-jsx",
|
||||
"types": [
|
||||
"react",
|
||||
"react-dom"
|
||||
],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
22
tsconfig.node.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
}
|
||||
}
|