test cicd

This commit is contained in:
2025-04-02 14:38:15 +02:00
commit 044371a460
54 changed files with 5280 additions and 0 deletions

64
.github/workflows/node-cicd.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Release package to Gitea and Deploy to AWS
on:
push:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check for change
id: diff
run: |
git diff --name-only origin/${{ github.ref_name }}~1 # This prints the list to screen only
echo "src=$(git diff --name-only origin/${{ github.ref_name }}~1 src| tr '\n' ' ')" >> $GITHUB_OUTPUT
- uses: actions/setup-node@v4
if: ${{ steps.diff.outputs.src }}
with:
node-version: '22'
registry-url: ${{ github.server_url != 'https://github.com' && format('{0}/api/packages/{1}/npm/', github.server_url, github.repository_owner) || 'https://npm.pkg.github.com' }}
scope: ${{ github.repository_owner }}
token: ${{ secrets.ACTIONS_TOKEN || github.token }}
- run: npm ci
if: ${{ steps.diff.outputs.src }}
env:
NODE_AUTH_TOKEN: ${{ secrets.ACTIONS_TOKEN || github.token }}
- run: npm run build
if: ${{ steps.diff.outputs.src }}
- run: npm publish
if: ${{ steps.diff.outputs.src }}
env:
NODE_AUTH_TOKEN: ${{ secrets.ACTIONS_TOKEN || github.token}}
- run: |
PACKAGE=$(npm pkg get name version|jq -r '"\(.name)@\(.version)"')
mkdir ../deployment && cd ../deployment && npm i $PACKAGE
env:
NODE_AUTH_TOKEN: ${{ secrets.ACTIONS_TOKEN || github.token }}
- name: Config AWS creds
uses: fc-actions/aws-login@v0.0.15
with:
#fireclover-client-id: ${{ vars.FIRECLOVER_CLIENT_ID }}
#fireclover-client-secret: ${{ secrets.FIRECLOVER_CLIENT_SECRET }}
aws-account-id: 515966519418
- name: Deploy Login to AWS
uses: fc-actions/deploy-cloudapp@v0.0.38
with:
fireclover-subscription: 'my-fireclover-subscription-token'
aws-account-id: 515966519418
dns-zone: 'test.aws.fireclover.cloud'
subdomain: 'test-example'
web-path: '../deployment/dist'

136
.gitignore vendored Normal file
View File

@ -0,0 +1,136 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM node:latest
ENV HOME=/home/app
RUN apt-get update && apt-get install htop -y
COPY . $HOME/node_docker
WORKDIR $HOME/node_docker
RUN npm ci --silent --progress=false
RUN npm run build
CMD ["npm", "start"]

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<!-- Fonts to support Material Design -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
/>
<title>Vite + Material UI + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "@fireclover/example-react",
"private": true,
"version": "1.0.0",
"description": "Infrastructure for an HTTPS static site using S3 and CloudFront",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "echo test ok"
},
"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.0",
"@mui/x-data-grid": "^7.27.0",
"@mui/x-data-grid-pro": "^7.27.0",
"@mui/x-date-pickers": "^7.27.0",
"@mui/x-date-pickers-pro": "^7.27.0",
"@mui/x-tree-view": "^7.26.0",
"dayjs": "^1.11.13",
"react": "latest",
"react-dom": "latest"
},
"devDependencies": {
"@types/react": "latest",
"@types/react-dom": "latest",
"@types/node": "latest",
"@vitejs/plugin-react": "latest",
"typescript": "latest",
"vite": "latest"
}
}

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

38
src/App.tsx Normal file
View File

@ -0,0 +1,38 @@
import * as React from 'react';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import ProTip from './ProTip';
function Copyright() {
return (
<Typography
variant="body2"
align="center"
sx={{
color: 'text.secondary',
}}
>
{'Copyright © '}
<Link color="inherit" href="https://mui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}.
</Typography>
);
}
export default function App() {
return (
<Container maxWidth="sm">
<Box sx={{ my: 4 }}>
<Typography variant="h4" component="h1" sx={{ mb: 2 }}>
Material UI Vite.js example in TypeScript
</Typography>
<ProTip />
<Copyright />
</Box>
</Container>
);
}

23
src/ProTip.tsx Normal file
View File

@ -0,0 +1,23 @@
import * as React from 'react';
import Link from '@mui/material/Link';
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
import Typography from '@mui/material/Typography';
function LightBulbIcon(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z" />
</SvgIcon>
);
}
export default function ProTip() {
return (
<Typography sx={{ mt: 6, mb: 3, color: 'text.secondary' }}>
<LightBulbIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
{'Pro tip: See more '}
<Link href="https://mui.com/material-ui/getting-started/templates/">templates</Link>
{' in the Material UI documentation.'}
</Typography>
);
}

View File

@ -0,0 +1,63 @@
import * as React from 'react';
import type {} from '@mui/x-date-pickers/themeAugmentation';
import type {} from '@mui/x-charts/themeAugmentation';
import type {} from '@mui/x-data-grid-pro/themeAugmentation';
import type {} from '@mui/x-tree-view/themeAugmentation';
import { alpha } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import AppNavbar from './components/AppNavbar';
import Header from './components/Header';
import MainGrid from './components/MainGrid';
import SideMenu from './components/SideMenu';
import AppTheme from '../shared-theme/AppTheme';
import {
chartsCustomizations,
dataGridCustomizations,
datePickersCustomizations,
treeViewCustomizations,
} from './theme/customizations';
const xThemeComponents = {
...chartsCustomizations,
...dataGridCustomizations,
...datePickersCustomizations,
...treeViewCustomizations,
};
export default function Dashboard(props: { disableCustomTheme?: boolean }) {
return (
<AppTheme {...props} themeComponents={xThemeComponents}>
<CssBaseline enableColorScheme />
<Box sx={{ display: 'flex' }}>
<SideMenu />
<AppNavbar />
{/* Main content */}
<Box
component="main"
sx={(theme) => ({
flexGrow: 1,
backgroundColor: theme.vars
? `rgba(${theme.vars.palette.background.defaultChannel} / 1)`
: alpha(theme.palette.background.default, 1),
overflow: 'auto',
})}
>
<Stack
spacing={2}
sx={{
alignItems: 'center',
mx: 3,
pb: 5,
mt: { xs: 8, md: 0 },
}}
>
<Header />
<MainGrid />
</Stack>
</Box>
</Box>
</AppTheme>
);
}

15
src/dashboard/README.md Normal file
View File

@ -0,0 +1,15 @@
# Dashboard template
## Usage
<!-- #default-branch-switch -->
1. Copy these folders (`dashboard` and `shared-theme`) into your project, or one of the [example projects](https://github.com/mui/material-ui/tree/v6.x/examples).
2. Make sure your project has the required dependencies: @mui/material, @mui/icons-material, @emotion/styled, @emotion/react, @mui/x-charts, @mui/x-date-pickers, @mui/x-data-grid, @mui/x-tree-view, dayjs
3. Import and use the `Dashboard` component.
## Demo
<!-- #default-branch-switch -->
View the demo at https://mui.com/material-ui/getting-started/templates/dashboard/.

View File

@ -0,0 +1,3 @@
<Typography component="h2" variant="h6" color="primary" gutterBottom>
{props.children}
</Typography>

View File

@ -0,0 +1,105 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import MuiToolbar from '@mui/material/Toolbar';
import { tabsClasses } from '@mui/material/Tabs';
import Typography from '@mui/material/Typography';
import MenuRoundedIcon from '@mui/icons-material/MenuRounded';
import DashboardRoundedIcon from '@mui/icons-material/DashboardRounded';
import SideMenuMobile from './SideMenuMobile';
import MenuButton from './MenuButton';
import ColorModeIconDropdown from '../../shared-theme/ColorModeIconDropdown';
const Toolbar = styled(MuiToolbar)({
width: '100%',
padding: '12px',
display: 'flex',
flexDirection: 'column',
alignItems: 'start',
justifyContent: 'center',
gap: '12px',
flexShrink: 0,
[`& ${tabsClasses.flexContainer}`]: {
gap: '8px',
p: '8px',
pb: 0,
},
});
export default function AppNavbar() {
const [open, setOpen] = React.useState(false);
const toggleDrawer = (newOpen: boolean) => () => {
setOpen(newOpen);
};
return (
<AppBar
position="fixed"
sx={{
display: { xs: 'auto', md: 'none' },
boxShadow: 0,
bgcolor: 'background.paper',
backgroundImage: 'none',
borderBottom: '1px solid',
borderColor: 'divider',
top: 'var(--template-frame-height, 0px)',
}}
>
<Toolbar variant="regular">
<Stack
direction="row"
sx={{
alignItems: 'center',
flexGrow: 1,
width: '100%',
gap: 1,
}}
>
<Stack
direction="row"
spacing={1}
sx={{ justifyContent: 'center', mr: 'auto' }}
>
<CustomIcon />
<Typography variant="h4" component="h1" sx={{ color: 'text.primary' }}>
Dashboard
</Typography>
</Stack>
<ColorModeIconDropdown />
<MenuButton aria-label="menu" onClick={toggleDrawer(true)}>
<MenuRoundedIcon />
</MenuButton>
<SideMenuMobile open={open} toggleDrawer={toggleDrawer} />
</Stack>
</Toolbar>
</AppBar>
);
}
export function CustomIcon() {
return (
<Box
sx={{
width: '1.5rem',
height: '1.5rem',
bgcolor: 'black',
borderRadius: '999px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'center',
backgroundImage:
'linear-gradient(135deg, hsl(210, 98%, 60%) 0%, hsl(210, 100%, 35%) 100%)',
color: 'hsla(210, 100%, 95%, 0.9)',
border: '1px solid',
borderColor: 'hsl(210, 100%, 55%)',
boxShadow: 'inset 0 2px 5px rgba(255, 255, 255, 0.3)',
}}
>
<DashboardRoundedIcon color="inherit" sx={{ fontSize: '1rem' }} />
</Box>
);
}

View File

@ -0,0 +1,25 @@
import * as React from 'react';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
export default function CardAlert() {
return (
<Card variant="outlined" sx={{ m: 1.5, flexShrink: 0 }}>
<CardContent>
<AutoAwesomeRoundedIcon fontSize="small" />
<Typography gutterBottom sx={{ fontWeight: 600 }}>
Plan about to expire
</Typography>
<Typography variant="body2" sx={{ mb: 2, color: 'text.secondary' }}>
Enjoy 10% off when renewing your plan today.
</Typography>
<Button variant="contained" size="small" fullWidth>
Get the discount
</Button>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,200 @@
import * as React from 'react';
import { PieChart } from '@mui/x-charts/PieChart';
import { useDrawingArea } from '@mui/x-charts/hooks';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress';
import {
IndiaFlag,
UsaFlag,
BrazilFlag,
GlobeFlag,
} from '../internals/components/CustomIcons';
const data = [
{ label: 'India', value: 50000 },
{ label: 'USA', value: 35000 },
{ label: 'Brazil', value: 10000 },
{ label: 'Other', value: 5000 },
];
const countries = [
{
name: 'India',
value: 50,
flag: <IndiaFlag />,
color: 'hsl(220, 25%, 65%)',
},
{
name: 'USA',
value: 35,
flag: <UsaFlag />,
color: 'hsl(220, 25%, 45%)',
},
{
name: 'Brazil',
value: 10,
flag: <BrazilFlag />,
color: 'hsl(220, 25%, 30%)',
},
{
name: 'Other',
value: 5,
flag: <GlobeFlag />,
color: 'hsl(220, 25%, 20%)',
},
];
interface StyledTextProps {
variant: 'primary' | 'secondary';
}
const StyledText = styled('text', {
shouldForwardProp: (prop) => prop !== 'variant',
})<StyledTextProps>(({ theme }) => ({
textAnchor: 'middle',
dominantBaseline: 'central',
fill: (theme.vars || theme).palette.text.secondary,
variants: [
{
props: {
variant: 'primary',
},
style: {
fontSize: theme.typography.h5.fontSize,
},
},
{
props: ({ variant }) => variant !== 'primary',
style: {
fontSize: theme.typography.body2.fontSize,
},
},
{
props: {
variant: 'primary',
},
style: {
fontWeight: theme.typography.h5.fontWeight,
},
},
{
props: ({ variant }) => variant !== 'primary',
style: {
fontWeight: theme.typography.body2.fontWeight,
},
},
],
}));
interface PieCenterLabelProps {
primaryText: string;
secondaryText: string;
}
function PieCenterLabel({ primaryText, secondaryText }: PieCenterLabelProps) {
const { width, height, left, top } = useDrawingArea();
const primaryY = top + height / 2 - 10;
const secondaryY = primaryY + 24;
return (
<React.Fragment>
<StyledText variant="primary" x={left + width / 2} y={primaryY}>
{primaryText}
</StyledText>
<StyledText variant="secondary" x={left + width / 2} y={secondaryY}>
{secondaryText}
</StyledText>
</React.Fragment>
);
}
const colors = [
'hsl(220, 20%, 65%)',
'hsl(220, 20%, 42%)',
'hsl(220, 20%, 35%)',
'hsl(220, 20%, 25%)',
];
export default function ChartUserByCountry() {
return (
<Card
variant="outlined"
sx={{ display: 'flex', flexDirection: 'column', gap: '8px', flexGrow: 1 }}
>
<CardContent>
<Typography component="h2" variant="subtitle2">
Users by country
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<PieChart
colors={colors}
margin={{
left: 80,
right: 80,
top: 80,
bottom: 80,
}}
series={[
{
data,
innerRadius: 75,
outerRadius: 100,
paddingAngle: 0,
highlightScope: { faded: 'global', highlighted: 'item' },
},
]}
height={260}
width={260}
slotProps={{
legend: { hidden: true },
}}
>
<PieCenterLabel primaryText="98.5K" secondaryText="Total" />
</PieChart>
</Box>
{countries.map((country, index) => (
<Stack
key={index}
direction="row"
sx={{ alignItems: 'center', gap: 2, pb: 2 }}
>
{country.flag}
<Stack sx={{ gap: 1, flexGrow: 1 }}>
<Stack
direction="row"
sx={{
justifyContent: 'space-between',
alignItems: 'center',
gap: 2,
}}
>
<Typography variant="body2" sx={{ fontWeight: '500' }}>
{country.name}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{country.value}%
</Typography>
</Stack>
<LinearProgress
variant="determinate"
aria-label="Number of users by country"
value={country.value}
sx={{
[`& .${linearProgressClasses.bar}`]: {
backgroundColor: country.color,
},
}}
/>
</Stack>
</Stack>
))}
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,77 @@
import * as React from 'react';
import dayjs, { Dayjs } from 'dayjs';
import Button from '@mui/material/Button';
import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { UseDateFieldProps } from '@mui/x-date-pickers/DateField';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import {
BaseSingleInputFieldProps,
DateValidationError,
FieldSection,
} from '@mui/x-date-pickers/models';
interface ButtonFieldProps
extends UseDateFieldProps<Dayjs, false>,
BaseSingleInputFieldProps<
Dayjs | null,
Dayjs,
FieldSection,
false,
DateValidationError
> {
setOpen?: React.Dispatch<React.SetStateAction<boolean>>;
}
function ButtonField(props: ButtonFieldProps) {
const {
setOpen,
label,
id,
disabled,
InputProps: { ref } = {},
inputProps: { 'aria-label': ariaLabel } = {},
} = props;
return (
<Button
variant="outlined"
id={id}
disabled={disabled}
ref={ref}
aria-label={ariaLabel}
size="small"
onClick={() => setOpen?.((prev) => !prev)}
startIcon={<CalendarTodayRoundedIcon fontSize="small" />}
sx={{ minWidth: 'fit-content' }}
>
{label ? `${label}` : 'Pick a date'}
</Button>
);
}
export default function CustomDatePicker() {
const [value, setValue] = React.useState<Dayjs | null>(dayjs('2023-04-17'));
const [open, setOpen] = React.useState(false);
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
value={value}
label={value == null ? null : value.format('MMM DD, YYYY')}
onChange={(newValue) => setValue(newValue)}
slots={{ field: ButtonField }}
slotProps={{
field: { setOpen } as any,
nextIconButton: { size: 'small' },
previousIconButton: { size: 'small' },
}}
open={open}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
views={['day', 'month', 'year']}
/>
</LocalizationProvider>
);
}

View File

@ -0,0 +1,48 @@
import * as React from 'react';
import { DataGrid } from '@mui/x-data-grid';
import { columns, rows } from '../internals/data/gridData';
export default function CustomizedDataGrid() {
return (
<DataGrid
checkboxSelection
rows={rows}
columns={columns}
getRowClassName={(params) =>
params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd'
}
initialState={{
pagination: { paginationModel: { pageSize: 20 } },
}}
pageSizeOptions={[10, 20, 50]}
disableColumnResize
density="compact"
slotProps={{
filterPanel: {
filterFormProps: {
logicOperatorInputProps: {
variant: 'outlined',
size: 'small',
},
columnInputProps: {
variant: 'outlined',
size: 'small',
sx: { mt: 'auto' },
},
operatorInputProps: {
variant: 'outlined',
size: 'small',
sx: { mt: 'auto' },
},
valueInputProps: {
InputComponentProps: {
variant: 'outlined',
size: 'small',
},
},
},
},
}}
/>
);
}

View File

@ -0,0 +1,208 @@
import * as React from 'react';
import clsx from 'clsx';
import { animated, useSpring } from '@react-spring/web';
import { TransitionProps } from '@mui/material/transitions';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Collapse from '@mui/material/Collapse';
import Typography from '@mui/material/Typography';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import {
unstable_useTreeItem2 as useTreeItem2,
UseTreeItem2Parameters,
} from '@mui/x-tree-view/useTreeItem2';
import {
TreeItem2Content,
TreeItem2IconContainer,
TreeItem2Label,
TreeItem2Root,
} from '@mui/x-tree-view/TreeItem2';
import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon';
import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider';
import { TreeViewBaseItem } from '@mui/x-tree-view/models';
import { useTheme } from '@mui/material/styles';
type Color = 'blue' | 'green';
type ExtendedTreeItemProps = {
color?: Color;
id: string;
label: string;
};
const ITEMS: TreeViewBaseItem<ExtendedTreeItemProps>[] = [
{
id: '1',
label: 'Website',
children: [
{ id: '1.1', label: 'Home', color: 'green' },
{ id: '1.2', label: 'Pricing', color: 'green' },
{ id: '1.3', label: 'About us', color: 'green' },
{
id: '1.4',
label: 'Blog',
children: [
{ id: '1.1.1', label: 'Announcements', color: 'blue' },
{ id: '1.1.2', label: 'April lookahead', color: 'blue' },
{ id: '1.1.3', label: "What's new", color: 'blue' },
{ id: '1.1.4', label: 'Meet the team', color: 'blue' },
],
},
],
},
{
id: '2',
label: 'Store',
children: [
{ id: '2.1', label: 'All products', color: 'green' },
{
id: '2.2',
label: 'Categories',
children: [
{ id: '2.2.1', label: 'Gadgets', color: 'blue' },
{ id: '2.2.2', label: 'Phones', color: 'blue' },
{ id: '2.2.3', label: 'Wearables', color: 'blue' },
],
},
{ id: '2.3', label: 'Bestsellers', color: 'green' },
{ id: '2.4', label: 'Sales', color: 'green' },
],
},
{ id: '4', label: 'Contact', color: 'blue' },
{ id: '5', label: 'Help', color: 'blue' },
];
function DotIcon({ color }: { color: string }) {
return (
<Box sx={{ marginRight: 1, display: 'flex', alignItems: 'center' }}>
<svg width={6} height={6}>
<circle cx={3} cy={3} r={3} fill={color} />
</svg>
</Box>
);
}
const AnimatedCollapse = animated(Collapse);
function TransitionComponent(props: TransitionProps) {
const style = useSpring({
to: {
opacity: props.in ? 1 : 0,
transform: `translate3d(0,${props.in ? 0 : 20}px,0)`,
},
});
return <AnimatedCollapse style={style} {...props} />;
}
interface CustomLabelProps {
children: React.ReactNode;
color?: Color;
expandable?: boolean;
}
function CustomLabel({ color, expandable, children, ...other }: CustomLabelProps) {
const theme = useTheme();
const colors = {
blue: (theme.vars || theme).palette.primary.main,
green: (theme.vars || theme).palette.success.main,
};
const iconColor = color ? colors[color] : null;
return (
<TreeItem2Label {...other} sx={{ display: 'flex', alignItems: 'center' }}>
{iconColor && <DotIcon color={iconColor} />}
<Typography
className="labelText"
variant="body2"
sx={{ color: 'text.primary' }}
>
{children}
</Typography>
</TreeItem2Label>
);
}
interface CustomTreeItemProps
extends Omit<UseTreeItem2Parameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {}
const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
ref: React.Ref<HTMLLIElement>,
) {
const { id, itemId, label, disabled, children, ...other } = props;
const {
getRootProps,
getContentProps,
getIconContainerProps,
getLabelProps,
getGroupTransitionProps,
status,
publicAPI,
} = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref });
const item = publicAPI.getItem(itemId);
const color = item?.color;
return (
<TreeItem2Provider itemId={itemId}>
<TreeItem2Root {...getRootProps(other)}>
<TreeItem2Content
{...getContentProps({
className: clsx('content', {
expanded: status.expanded,
selected: status.selected,
focused: status.focused,
disabled: status.disabled,
}),
})}
>
{status.expandable && (
<TreeItem2IconContainer {...getIconContainerProps()}>
<TreeItem2Icon status={status} />
</TreeItem2IconContainer>
)}
<CustomLabel {...getLabelProps({ color })} />
</TreeItem2Content>
{children && (
<TransitionComponent
{...getGroupTransitionProps({ className: 'groupTransition' })}
/>
)}
</TreeItem2Root>
</TreeItem2Provider>
);
});
export default function CustomizedTreeView() {
return (
<Card
variant="outlined"
sx={{ display: 'flex', flexDirection: 'column', gap: '8px', flexGrow: 1 }}
>
<CardContent>
<Typography component="h2" variant="subtitle2">
Product tree
</Typography>
<RichTreeView
items={ITEMS}
aria-label="pages"
multiSelect
defaultExpandedItems={['1', '1.1']}
defaultSelectedItems={['1.1', '1.1.1']}
sx={{
m: '0 -8px',
pb: '8px',
height: 'fit-content',
flexGrow: 1,
overflowY: 'auto',
}}
slots={{ item: CustomTreeItem }}
/>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,36 @@
import * as React from 'react';
import Stack from '@mui/material/Stack';
import NotificationsRoundedIcon from '@mui/icons-material/NotificationsRounded';
import CustomDatePicker from './CustomDatePicker';
import NavbarBreadcrumbs from './NavbarBreadcrumbs';
import MenuButton from './MenuButton';
import ColorModeIconDropdown from '../../shared-theme/ColorModeIconDropdown';
import Search from './Search';
export default function Header() {
return (
<Stack
direction="row"
sx={{
display: { xs: 'none', md: 'flex' },
width: '100%',
alignItems: { xs: 'flex-start', md: 'center' },
justifyContent: 'space-between',
maxWidth: { sm: '100%', md: '1700px' },
pt: 1.5,
}}
spacing={2}
>
<NavbarBreadcrumbs />
<Stack direction="row" sx={{ gap: 1 }}>
<Search />
<CustomDatePicker />
<MenuButton showBadge aria-label="Open notifications">
<NotificationsRoundedIcon />
</MenuButton>
<ColorModeIconDropdown />
</Stack>
</Stack>
);
}

View File

@ -0,0 +1,42 @@
import * as React from 'react';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';
import InsightsRoundedIcon from '@mui/icons-material/InsightsRounded';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
export default function HighlightedCard() {
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
return (
<Card sx={{ height: '100%' }}>
<CardContent>
<InsightsRoundedIcon />
<Typography
component="h2"
variant="subtitle2"
gutterBottom
sx={{ fontWeight: '600' }}
>
Explore your data
</Typography>
<Typography sx={{ color: 'text.secondary', mb: '8px' }}>
Uncover performance and visitor insights with our data wizardry.
</Typography>
<Button
variant="contained"
size="small"
color="primary"
endIcon={<ChevronRightRoundedIcon />}
fullWidth={isSmallScreen}
>
Get insights
</Button>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,93 @@
import * as React from 'react';
import Grid from '@mui/material/Grid2';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Copyright from '../internals/components/Copyright';
import ChartUserByCountry from './ChartUserByCountry';
import CustomizedTreeView from './CustomizedTreeView';
import CustomizedDataGrid from './CustomizedDataGrid';
import HighlightedCard from './HighlightedCard';
import PageViewsBarChart from './PageViewsBarChart';
import SessionsChart from './SessionsChart';
import StatCard, { StatCardProps } from './StatCard';
const data: StatCardProps[] = [
{
title: 'Users',
value: '14k',
interval: 'Last 30 days',
trend: 'up',
data: [
200, 24, 220, 260, 240, 380, 100, 240, 280, 240, 300, 340, 320, 360, 340, 380,
360, 400, 380, 420, 400, 640, 340, 460, 440, 480, 460, 600, 880, 920,
],
},
{
title: 'Conversions',
value: '325',
interval: 'Last 30 days',
trend: 'down',
data: [
1640, 1250, 970, 1130, 1050, 900, 720, 1080, 900, 450, 920, 820, 840, 600, 820,
780, 800, 760, 380, 740, 660, 620, 840, 500, 520, 480, 400, 360, 300, 220,
],
},
{
title: 'Event count',
value: '200k',
interval: 'Last 30 days',
trend: 'neutral',
data: [
500, 400, 510, 530, 520, 600, 530, 520, 510, 730, 520, 510, 530, 620, 510, 530,
520, 410, 530, 520, 610, 530, 520, 610, 530, 420, 510, 430, 520, 510,
],
},
];
export default function MainGrid() {
return (
<Box sx={{ width: '100%', maxWidth: { sm: '100%', md: '1700px' } }}>
{/* cards */}
<Typography component="h2" variant="h6" sx={{ mb: 2 }}>
Overview
</Typography>
<Grid
container
spacing={2}
columns={12}
sx={{ mb: (theme) => theme.spacing(2) }}
>
{data.map((card, index) => (
<Grid key={index} size={{ xs: 12, sm: 6, lg: 3 }}>
<StatCard {...card} />
</Grid>
))}
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
<HighlightedCard />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<SessionsChart />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<PageViewsBarChart />
</Grid>
</Grid>
<Typography component="h2" variant="h6" sx={{ mb: 2 }}>
Details
</Typography>
<Grid container spacing={2} columns={12}>
<Grid size={{ xs: 12, lg: 9 }}>
<CustomizedDataGrid />
</Grid>
<Grid size={{ xs: 12, lg: 3 }}>
<Stack gap={2} direction={{ xs: 'column', sm: 'row', lg: 'column' }}>
<CustomizedTreeView />
<ChartUserByCountry />
</Stack>
</Grid>
</Grid>
<Copyright sx={{ my: 4 }} />
</Box>
);
}

View File

@ -0,0 +1,23 @@
import * as React from 'react';
import Badge, { badgeClasses } from '@mui/material/Badge';
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
export interface MenuButtonProps extends IconButtonProps {
showBadge?: boolean;
}
export default function MenuButton({
showBadge = false,
...props
}: MenuButtonProps) {
return (
<Badge
color="error"
variant="dot"
invisible={!showBadge}
sx={{ [`& .${badgeClasses.badge}`]: { right: 2, top: 2 } }}
>
<IconButton size="small" {...props} />
</Badge>
);
}

View File

@ -0,0 +1,54 @@
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import Stack from '@mui/material/Stack';
import HomeRoundedIcon from '@mui/icons-material/HomeRounded';
import AnalyticsRoundedIcon from '@mui/icons-material/AnalyticsRounded';
import PeopleRoundedIcon from '@mui/icons-material/PeopleRounded';
import AssignmentRoundedIcon from '@mui/icons-material/AssignmentRounded';
import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded';
import InfoRoundedIcon from '@mui/icons-material/InfoRounded';
import HelpRoundedIcon from '@mui/icons-material/HelpRounded';
const mainListItems = [
{ text: 'Home', icon: <HomeRoundedIcon /> },
{ text: 'Analytics', icon: <AnalyticsRoundedIcon /> },
{ text: 'Clients', icon: <PeopleRoundedIcon /> },
{ text: 'Tasks', icon: <AssignmentRoundedIcon /> },
];
const secondaryListItems = [
{ text: 'Settings', icon: <SettingsRoundedIcon /> },
{ text: 'About', icon: <InfoRoundedIcon /> },
{ text: 'Feedback', icon: <HelpRoundedIcon /> },
];
export default function MenuContent() {
return (
<Stack sx={{ flexGrow: 1, p: 1, justifyContent: 'space-between' }}>
<List dense>
{mainListItems.map((item, index) => (
<ListItem key={index} disablePadding sx={{ display: 'block' }}>
<ListItemButton selected={index === 0}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.text} />
</ListItemButton>
</ListItem>
))}
</List>
<List dense>
{secondaryListItems.map((item, index) => (
<ListItem key={index} disablePadding sx={{ display: 'block' }}>
<ListItemButton>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.text} />
</ListItemButton>
</ListItem>
))}
</List>
</Stack>
);
}

View File

@ -0,0 +1,30 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Breadcrumbs, { breadcrumbsClasses } from '@mui/material/Breadcrumbs';
import NavigateNextRoundedIcon from '@mui/icons-material/NavigateNextRounded';
const StyledBreadcrumbs = styled(Breadcrumbs)(({ theme }) => ({
margin: theme.spacing(1, 0),
[`& .${breadcrumbsClasses.separator}`]: {
color: (theme.vars || theme).palette.action.disabled,
margin: 1,
},
[`& .${breadcrumbsClasses.ol}`]: {
alignItems: 'center',
},
}));
export default function NavbarBreadcrumbs() {
return (
<StyledBreadcrumbs
aria-label="breadcrumb"
separator={<NavigateNextRoundedIcon fontSize="small" />}
>
<Typography variant="body1">Dashboard</Typography>
<Typography variant="body1" sx={{ color: 'text.primary', fontWeight: 600 }}>
Home
</Typography>
</StyledBreadcrumbs>
);
}

View File

@ -0,0 +1,79 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Divider, { dividerClasses } from '@mui/material/Divider';
import Menu from '@mui/material/Menu';
import MuiMenuItem from '@mui/material/MenuItem';
import { paperClasses } from '@mui/material/Paper';
import { listClasses } from '@mui/material/List';
import ListItemText from '@mui/material/ListItemText';
import ListItemIcon, { listItemIconClasses } from '@mui/material/ListItemIcon';
import LogoutRoundedIcon from '@mui/icons-material/LogoutRounded';
import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded';
import MenuButton from './MenuButton';
const MenuItem = styled(MuiMenuItem)({
margin: '2px 0',
});
export default function OptionsMenu() {
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);
};
return (
<React.Fragment>
<MenuButton
aria-label="Open menu"
onClick={handleClick}
sx={{ borderColor: 'transparent' }}
>
<MoreVertRoundedIcon />
</MenuButton>
<Menu
anchorEl={anchorEl}
id="menu"
open={open}
onClose={handleClose}
onClick={handleClose}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
sx={{
[`& .${listClasses.root}`]: {
padding: '4px',
},
[`& .${paperClasses.root}`]: {
padding: 0,
},
[`& .${dividerClasses.root}`]: {
margin: '4px -4px',
},
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<Divider />
<MenuItem onClick={handleClose}>Add another account</MenuItem>
<MenuItem onClick={handleClose}>Settings</MenuItem>
<Divider />
<MenuItem
onClick={handleClose}
sx={{
[`& .${listItemIconClasses.root}`]: {
ml: 'auto',
minWidth: 0,
},
}}
>
<ListItemText>Logout</ListItemText>
<ListItemIcon>
<LogoutRoundedIcon fontSize="small" />
</ListItemIcon>
</MenuItem>
</Menu>
</React.Fragment>
);
}

View File

@ -0,0 +1,85 @@
import * as React from 'react';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import { BarChart } from '@mui/x-charts/BarChart';
import { useTheme } from '@mui/material/styles';
export default function PageViewsBarChart() {
const theme = useTheme();
const colorPalette = [
(theme.vars || theme).palette.primary.dark,
(theme.vars || theme).palette.primary.main,
(theme.vars || theme).palette.primary.light,
];
return (
<Card variant="outlined" sx={{ width: '100%' }}>
<CardContent>
<Typography component="h2" variant="subtitle2" gutterBottom>
Page views and downloads
</Typography>
<Stack sx={{ justifyContent: 'space-between' }}>
<Stack
direction="row"
sx={{
alignContent: { xs: 'center', sm: 'flex-start' },
alignItems: 'center',
gap: 1,
}}
>
<Typography variant="h4" component="p">
1.3M
</Typography>
<Chip size="small" color="error" label="-8%" />
</Stack>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Page views and downloads for the last 6 months
</Typography>
</Stack>
<BarChart
borderRadius={8}
colors={colorPalette}
xAxis={
[
{
scaleType: 'band',
categoryGapRatio: 0.5,
data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
},
] as any
}
series={[
{
id: 'page-views',
label: 'Page views',
data: [2234, 3872, 2998, 4125, 3357, 2789, 2998],
stack: 'A',
},
{
id: 'downloads',
label: 'Downloads',
data: [3098, 4215, 2384, 2101, 4752, 3593, 2384],
stack: 'A',
},
{
id: 'conversions',
label: 'Conversions',
data: [4051, 2275, 3129, 4693, 3904, 2038, 2275],
stack: 'A',
},
]}
height={250}
margin={{ left: 50, right: 0, top: 20, bottom: 20 }}
grid={{ horizontal: true }}
slotProps={{
legend: {
hidden: true,
},
}}
/>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,26 @@
import * as React from 'react';
import FormControl from '@mui/material/FormControl';
import InputAdornment from '@mui/material/InputAdornment';
import OutlinedInput from '@mui/material/OutlinedInput';
import SearchRoundedIcon from '@mui/icons-material/SearchRounded';
export default function Search() {
return (
<FormControl sx={{ width: { xs: '100%', md: '25ch' } }} variant="outlined">
<OutlinedInput
size="small"
id="search"
placeholder="Search…"
sx={{ flexGrow: 1 }}
startAdornment={
<InputAdornment position="start" sx={{ color: 'text.primary' }}>
<SearchRoundedIcon fontSize="small" />
</InputAdornment>
}
inputProps={{
'aria-label': 'search',
}}
/>
</FormControl>
);
}

View File

@ -0,0 +1,102 @@
import * as React from 'react';
import MuiAvatar from '@mui/material/Avatar';
import MuiListItemAvatar from '@mui/material/ListItemAvatar';
import MenuItem from '@mui/material/MenuItem';
import ListItemText from '@mui/material/ListItemText';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListSubheader from '@mui/material/ListSubheader';
import Select, { SelectChangeEvent, selectClasses } from '@mui/material/Select';
import Divider from '@mui/material/Divider';
import { styled } from '@mui/material/styles';
import AddRoundedIcon from '@mui/icons-material/AddRounded';
import DevicesRoundedIcon from '@mui/icons-material/DevicesRounded';
import SmartphoneRoundedIcon from '@mui/icons-material/SmartphoneRounded';
import ConstructionRoundedIcon from '@mui/icons-material/ConstructionRounded';
const Avatar = styled(MuiAvatar)(({ theme }) => ({
width: 28,
height: 28,
backgroundColor: (theme.vars || theme).palette.background.paper,
color: (theme.vars || theme).palette.text.secondary,
border: `1px solid ${(theme.vars || theme).palette.divider}`,
}));
const ListItemAvatar = styled(MuiListItemAvatar)({
minWidth: 0,
marginRight: 12,
});
export default function SelectContent() {
const [company, setCompany] = React.useState('');
const handleChange = (event: SelectChangeEvent) => {
setCompany(event.target.value as string);
};
return (
<Select
labelId="company-select"
id="company-simple-select"
value={company}
onChange={handleChange}
displayEmpty
inputProps={{ 'aria-label': 'Select company' }}
fullWidth
sx={{
maxHeight: 56,
width: 215,
'&.MuiList-root': {
p: '8px',
},
[`& .${selectClasses.select}`]: {
display: 'flex',
alignItems: 'center',
gap: '2px',
pl: 1,
},
}}
>
<ListSubheader sx={{ pt: 0 }}>Production</ListSubheader>
<MenuItem value="">
<ListItemAvatar>
<Avatar alt="Sitemark web">
<DevicesRoundedIcon sx={{ fontSize: '1rem' }} />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Sitemark-web" secondary="Web app" />
</MenuItem>
<MenuItem value={10}>
<ListItemAvatar>
<Avatar alt="Sitemark App">
<SmartphoneRoundedIcon sx={{ fontSize: '1rem' }} />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Sitemark-app" secondary="Mobile application" />
</MenuItem>
<MenuItem value={20}>
<ListItemAvatar>
<Avatar alt="Sitemark Store">
<DevicesRoundedIcon sx={{ fontSize: '1rem' }} />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Sitemark-Store" secondary="Web app" />
</MenuItem>
<ListSubheader>Development</ListSubheader>
<MenuItem value={30}>
<ListItemAvatar>
<Avatar alt="Sitemark Store">
<ConstructionRoundedIcon sx={{ fontSize: '1rem' }} />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Sitemark-Admin" secondary="Web app" />
</MenuItem>
<Divider sx={{ mx: -1 }} />
<MenuItem value={40}>
<ListItemIcon>
<AddRoundedIcon />
</ListItemIcon>
<ListItemText primary="Add product" secondary="Web app" />
</MenuItem>
</Select>
);
}

View File

@ -0,0 +1,150 @@
import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import { LineChart } from '@mui/x-charts/LineChart';
function AreaGradient({ color, id }: { color: string; id: string }) {
return (
<defs>
<linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="0%" stopColor={color} stopOpacity={0.5} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
);
}
function getDaysInMonth(month: number, year: number) {
const date = new Date(year, month, 0);
const monthName = date.toLocaleDateString('en-US', {
month: 'short',
});
const daysInMonth = date.getDate();
const days = [];
let i = 1;
while (days.length < daysInMonth) {
days.push(`${monthName} ${i}`);
i += 1;
}
return days;
}
export default function SessionsChart() {
const theme = useTheme();
const data = getDaysInMonth(4, 2024);
const colorPalette = [
theme.palette.primary.light,
theme.palette.primary.main,
theme.palette.primary.dark,
];
return (
<Card variant="outlined" sx={{ width: '100%' }}>
<CardContent>
<Typography component="h2" variant="subtitle2" gutterBottom>
Sessions
</Typography>
<Stack sx={{ justifyContent: 'space-between' }}>
<Stack
direction="row"
sx={{
alignContent: { xs: 'center', sm: 'flex-start' },
alignItems: 'center',
gap: 1,
}}
>
<Typography variant="h4" component="p">
13,277
</Typography>
<Chip size="small" color="success" label="+35%" />
</Stack>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Sessions per day for the last 30 days
</Typography>
</Stack>
<LineChart
colors={colorPalette}
xAxis={[
{
scaleType: 'point',
data,
tickInterval: (index, i) => (i + 1) % 5 === 0,
},
]}
series={[
{
id: 'direct',
label: 'Direct',
showMark: false,
curve: 'linear',
stack: 'total',
area: true,
stackOrder: 'ascending',
data: [
300, 900, 600, 1200, 1500, 1800, 2400, 2100, 2700, 3000, 1800, 3300,
3600, 3900, 4200, 4500, 3900, 4800, 5100, 5400, 4800, 5700, 6000,
6300, 6600, 6900, 7200, 7500, 7800, 8100,
],
},
{
id: 'referral',
label: 'Referral',
showMark: false,
curve: 'linear',
stack: 'total',
area: true,
stackOrder: 'ascending',
data: [
500, 900, 700, 1400, 1100, 1700, 2300, 2000, 2600, 2900, 2300, 3200,
3500, 3800, 4100, 4400, 2900, 4700, 5000, 5300, 5600, 5900, 6200,
6500, 5600, 6800, 7100, 7400, 7700, 8000,
],
},
{
id: 'organic',
label: 'Organic',
showMark: false,
curve: 'linear',
stack: 'total',
stackOrder: 'ascending',
data: [
1000, 1500, 1200, 1700, 1300, 2000, 2400, 2200, 2600, 2800, 2500,
3000, 3400, 3700, 3200, 3900, 4100, 3500, 4300, 4500, 4000, 4700,
5000, 5200, 4800, 5400, 5600, 5900, 6100, 6300,
],
area: true,
},
]}
height={250}
margin={{ left: 50, right: 20, top: 20, bottom: 20 }}
grid={{ horizontal: true }}
sx={{
'& .MuiAreaElement-series-organic': {
fill: "url('#organic')",
},
'& .MuiAreaElement-series-referral': {
fill: "url('#referral')",
},
'& .MuiAreaElement-series-direct': {
fill: "url('#direct')",
},
}}
slotProps={{
legend: {
hidden: true,
},
}}
>
<AreaGradient color={theme.palette.primary.dark} id="organic" />
<AreaGradient color={theme.palette.primary.main} id="referral" />
<AreaGradient color={theme.palette.primary.light} id="direct" />
</LineChart>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,87 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Avatar from '@mui/material/Avatar';
import MuiDrawer, { drawerClasses } from '@mui/material/Drawer';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import SelectContent from './SelectContent';
import MenuContent from './MenuContent';
import CardAlert from './CardAlert';
import OptionsMenu from './OptionsMenu';
const drawerWidth = 240;
const Drawer = styled(MuiDrawer)({
width: drawerWidth,
flexShrink: 0,
boxSizing: 'border-box',
mt: 10,
[`& .${drawerClasses.paper}`]: {
width: drawerWidth,
boxSizing: 'border-box',
},
});
export default function SideMenu() {
return (
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', md: 'block' },
[`& .${drawerClasses.paper}`]: {
backgroundColor: 'background.paper',
},
}}
>
<Box
sx={{
display: 'flex',
mt: 'calc(var(--template-frame-height, 0px) + 4px)',
p: 1.5,
}}
>
<SelectContent />
</Box>
<Divider />
<Box
sx={{
overflow: 'auto',
height: '100%',
display: 'flex',
flexDirection: 'column',
}}
>
<MenuContent />
<CardAlert />
</Box>
<Stack
direction="row"
sx={{
p: 2,
gap: 1,
alignItems: 'center',
borderTop: '1px solid',
borderColor: 'divider',
}}
>
<Avatar
sizes="small"
alt="Riley Carter"
src="/static/images/avatar/7.jpg"
sx={{ width: 36, height: 36 }}
/>
<Box sx={{ mr: 'auto' }}>
<Typography variant="body2" sx={{ fontWeight: 500, lineHeight: '16px' }}>
Riley Carter
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
riley@email.com
</Typography>
</Box>
<OptionsMenu />
</Stack>
</Drawer>
);
}

View File

@ -0,0 +1,72 @@
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Drawer, { drawerClasses } from '@mui/material/Drawer';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import LogoutRoundedIcon from '@mui/icons-material/LogoutRounded';
import NotificationsRoundedIcon from '@mui/icons-material/NotificationsRounded';
import MenuButton from './MenuButton';
import MenuContent from './MenuContent';
import CardAlert from './CardAlert';
interface SideMenuMobileProps {
open: boolean | undefined;
toggleDrawer: (newOpen: boolean) => () => void;
}
export default function SideMenuMobile({ open, toggleDrawer }: SideMenuMobileProps) {
return (
<Drawer
anchor="right"
open={open}
onClose={toggleDrawer(false)}
sx={{
zIndex: (theme) => theme.zIndex.drawer + 1,
[`& .${drawerClasses.paper}`]: {
backgroundImage: 'none',
backgroundColor: 'background.paper',
},
}}
>
<Stack
sx={{
maxWidth: '70dvw',
height: '100%',
}}
>
<Stack direction="row" sx={{ p: 2, pb: 0, gap: 1 }}>
<Stack
direction="row"
sx={{ gap: 1, alignItems: 'center', flexGrow: 1, p: 1 }}
>
<Avatar
sizes="small"
alt="Riley Carter"
src="/static/images/avatar/7.jpg"
sx={{ width: 24, height: 24 }}
/>
<Typography component="p" variant="h6">
Riley Carter
</Typography>
</Stack>
<MenuButton showBadge>
<NotificationsRoundedIcon />
</MenuButton>
</Stack>
<Divider />
<Stack sx={{ flexGrow: 1 }}>
<MenuContent />
<Divider />
</Stack>
<CardAlert />
<Stack sx={{ p: 2 }}>
<Button variant="outlined" fullWidth startIcon={<LogoutRoundedIcon />}>
Logout
</Button>
</Stack>
</Stack>
</Drawer>
);
}

View File

@ -0,0 +1,129 @@
import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
import { areaElementClasses } from '@mui/x-charts/LineChart';
export type StatCardProps = {
title: string;
value: string;
interval: string;
trend: 'up' | 'down' | 'neutral';
data: number[];
};
function getDaysInMonth(month: number, year: number) {
const date = new Date(year, month, 0);
const monthName = date.toLocaleDateString('en-US', {
month: 'short',
});
const daysInMonth = date.getDate();
const days = [];
let i = 1;
while (days.length < daysInMonth) {
days.push(`${monthName} ${i}`);
i += 1;
}
return days;
}
function AreaGradient({ color, id }: { color: string; id: string }) {
return (
<defs>
<linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="0%" stopColor={color} stopOpacity={0.3} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
);
}
export default function StatCard({
title,
value,
interval,
trend,
data,
}: StatCardProps) {
const theme = useTheme();
const daysInWeek = getDaysInMonth(4, 2024);
const trendColors = {
up:
theme.palette.mode === 'light'
? theme.palette.success.main
: theme.palette.success.dark,
down:
theme.palette.mode === 'light'
? theme.palette.error.main
: theme.palette.error.dark,
neutral:
theme.palette.mode === 'light'
? theme.palette.grey[400]
: theme.palette.grey[700],
};
const labelColors = {
up: 'success' as const,
down: 'error' as const,
neutral: 'default' as const,
};
const color = labelColors[trend];
const chartColor = trendColors[trend];
const trendValues = { up: '+25%', down: '-25%', neutral: '+5%' };
return (
<Card variant="outlined" sx={{ height: '100%', flexGrow: 1 }}>
<CardContent>
<Typography component="h2" variant="subtitle2" gutterBottom>
{title}
</Typography>
<Stack
direction="column"
sx={{ justifyContent: 'space-between', flexGrow: '1', gap: 1 }}
>
<Stack sx={{ justifyContent: 'space-between' }}>
<Stack
direction="row"
sx={{ justifyContent: 'space-between', alignItems: 'center' }}
>
<Typography variant="h4" component="p">
{value}
</Typography>
<Chip size="small" color={color} label={trendValues[trend]} />
</Stack>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{interval}
</Typography>
</Stack>
<Box sx={{ width: '100%', height: 50 }}>
<SparkLineChart
colors={[chartColor]}
data={data}
area
showHighlight
showTooltip
xAxis={{
scaleType: 'band',
data: daysInWeek, // Use the correct property 'data' for xAxis
}}
sx={{
[`& .${areaElementClasses.root}`]: {
fill: `url(#area-gradient-${value})`,
},
}}
>
<AreaGradient color={chartColor} id={`area-gradient-${value}`} />
</SparkLineChart>
</Box>
</Stack>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,26 @@
import * as React from 'react';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
export default function Copyright(props: any) {
return (
<Typography
variant="body2"
align="center"
{...props}
sx={[
{
color: 'text.secondary',
},
...(Array.isArray(props.sx) ? props.sx : [props.sx]),
]}
>
{'Copyright © '}
<Link color="inherit" href="https://mui.com/">
Sitemark
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}

View File

@ -0,0 +1,326 @@
import * as React from 'react';
import SvgIcon from '@mui/material/SvgIcon';
export function SitemarkIcon() {
return (
<SvgIcon sx={{ height: 21, width: 100 }}>
<svg
width={86}
height={19}
viewBox="0 0 86 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#B4C0D3"
d="m.787 12.567 6.055-2.675 3.485 2.006.704 6.583-4.295-.035.634-4.577-.74-.422-3.625 2.817-2.218-3.697Z"
/>
<path
fill="#00D3AB"
d="m10.714 11.616 5.352 3.908 2.112-3.767-4.295-1.725v-.845l4.295-1.76-2.112-3.732-5.352 3.908v4.013Z"
/>
<path
fill="#4876EF"
d="m10.327 7.286.704-6.583-4.295.07.634 4.577-.74.422-3.66-2.816L.786 6.617l6.055 2.676 3.485-2.007Z"
/>
<path
fill="#4876EE"
d="M32.507 8.804v6.167h2.312v-7.86h-3.366v1.693h1.054ZM32.435 6.006c.212.22.535.33.968.33.434 0 .751-.11.953-.33.213-.23.318-.516.318-.86 0-.354-.105-.641-.318-.86-.202-.23-.52-.345-.953-.345-.433 0-.756.115-.968.344-.202.22-.303.507-.303.86 0 .345.101.632.303.861ZM24.46 14.799c.655.296 1.46.444 2.413.444.896 0 1.667-.139 2.312-.416.645-.277 1.141-.664 1.488-1.162.357-.506.535-1.094.535-1.764 0-.65-.169-1.2-.506-1.649-.328-.459-.785-.818-1.373-1.076-.587-.267-1.266-.435-2.037-.502l-.809-.071c-.481-.039-.828-.168-1.04-.388a1.08 1.08 0 0 1-.318-.774c0-.23.058-.44.173-.631.116-.201.29-.359.52-.474.241-.114.535-.172.882-.172.366 0 .67.067.91.201.053.029.104.059.15.09l.012.009.052.037c.146.111.263.243.35.395.125.21.188.444.188.703h2.311c0-.689-.159-1.286-.476-1.793-.318-.516-.776-.913-1.373-1.19-.588-.287-1.296-.43-2.124-.43-.79 0-1.474.133-2.052.4a3.131 3.131 0 0 0-1.358 1.12c-.318.487-.477 1.066-.477 1.735 0 .927.314 1.673.94 2.237.626.564 1.464.89 2.514.976l.794.071c.645.058 1.113.187 1.401.388a.899.899 0 0 1 .434.788 1.181 1.181 0 0 1-.231.717c-.154.201-.38.36-.68.474-.298.115-.669.172-1.112.172-.49 0-.89-.067-1.199-.2-.308-.144-.539-.33-.694-.56a1.375 1.375 0 0 1-.216-.746h-2.297c0 .679.168 1.281.505 1.807.337.517.834.928 1.489 1.234ZM39.977 15.07c-.8 0-1.445-.095-1.936-.286a2.03 2.03 0 0 1-1.084-.99c-.221-.469-.332-1.1-.332-1.893V8.789h-1.2V7.11h1.2V4.988h2.153V7.11h2.312V8.79h-2.312v3.198c0 .373.096.66.289.86.202.192.486.287.852.287h1.17v1.937h-1.112Z"
/>
<path
fill="#4876EE"
fillRule="evenodd"
d="M43.873 14.899c.52.23 1.117.344 1.791.344.665 0 1.252-.115 1.763-.344.51-.23.934-.55 1.271-.96.337-.412.564-.88.679-1.407h-2.124c-.096.24-.279.44-.549.603-.27.162-.616.244-1.04.244-.262 0-.497-.031-.704-.093a1.572 1.572 0 0 1-.423-.194 1.662 1.662 0 0 1-.636-.803 3.159 3.159 0 0 1-.163-.645h5.784v-.775a4.28 4.28 0 0 0-.463-1.98 3.686 3.686 0 0 0-1.343-1.477c-.578-.382-1.291-.574-2.139-.574-.645 0-1.223.115-1.733.345-.501.22-.92.52-1.257.903a4.178 4.178 0 0 0-.78 1.305c-.174.478-.26.98-.26 1.506v.287c0 .507.086 1.004.26 1.492.183.478.443.913.78 1.305.347.382.775.688 1.286.918Zm-.094-4.674.02-.09a2.507 2.507 0 0 1 .117-.356c.145-.354.356-.622.636-.804.104-.067.217-.123.339-.165.204-.071.433-.107.686-.107.395 0 .723.09.983.272.27.173.472.426.607.76a2.487 2.487 0 0 1 .16.603h-3.57c.006-.038.013-.076.022-.113Z"
clipRule="evenodd"
/>
<path
fill="#4876EE"
d="M50.476 14.97V7.112h1.835v1.98a4.54 4.54 0 0 1 .173-.603c.202-.536.506-.937.91-1.205.405-.277.9-.416 1.488-.416h.101c.598 0 1.094.139 1.489.416.404.268.707.67.91 1.205l.016.04.013.037.028-.077c.212-.536.52-.937.925-1.205.405-.277.901-.416 1.489-.416h.1c.598 0 1.098.139 1.503.416.414.268.727.67.94 1.205.211.535.317 1.205.317 2.008v4.475h-2.312v-4.604c0-.43-.115-.78-.346-1.047-.222-.268-.54-.402-.954-.402-.414 0-.742.139-.982.416-.241.268-.362.626-.362 1.076v4.56h-2.326v-4.603c0-.43-.115-.78-.346-1.047-.222-.268-.535-.402-.94-.402-.423 0-.756.139-.996.416-.241.268-.362.626-.362 1.076v4.56h-2.311Z"
/>
<path
fill="#4876EE"
fillRule="evenodd"
d="M68.888 13.456v1.515h1.834v-4.82c0-.726-.144-1.319-.433-1.778-.289-.468-.712-.817-1.271-1.047-.549-.23-1.228-.344-2.037-.344a27.76 27.76 0 0 0-.896.014c-.318.01-.626.024-.924.043l-.229.016a36.79 36.79 0 0 0-.552.042v1.936a81.998 81.998 0 0 1 1.733-.09 37.806 37.806 0 0 1 1.171-.025c.424 0 .732.1.925.301.193.201.289.502.289.904v.029h-1.43c-.704 0-1.325.09-1.864.272-.54.172-.959.445-1.257.818-.299.363-.448.832-.448 1.405 0 .526.12.98.361 1.363.24.373.573.66.997.86.433.201.934.302 1.502.302.55 0 1.012-.1 1.388-.302.385-.2.683-.487.895-.86a2.443 2.443 0 0 0 .228-.498l.018-.056Zm-.39-1.397v-.63h-1.445c-.405 0-.718.1-.939.3-.212.192-.318.455-.318.79 0 .157.026.3.08.429a.99.99 0 0 0 .238.345c.221.191.534.287.939.287a2.125 2.125 0 0 0 .394-.038c.106-.021.206-.052.3-.092.212-.095.385-.253.52-.473.135-.22.212-.526.23-.918Z"
clipRule="evenodd"
/>
<path
fill="#4876EE"
d="M72.106 14.97V7.11h1.835v2.595c.088-.74.31-1.338.665-1.791.481-.603 1.174-.904 2.08-.904h.303v1.98h-.578c-.635 0-1.127.172-1.473.516-.347.334-.52.822-.52 1.463v4.001h-2.312ZM79.92 11.298h.767l2.499 3.672h2.6l-3.169-4.51 2.606-3.35h-2.427l-2.875 3.737V4.5h-2.312v10.47h2.312v-3.672Z"
/>
</svg>
</SvgIcon>
);
}
export function IndiaFlag() {
return (
<SvgIcon>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none">
<g clipPath="url(#a)">
<mask
id="b"
maskUnits="userSpaceOnUse"
x="-4"
y="0"
width="32"
height="24"
>
<path d="M-4 0h32v24H-4V0Z" fill="#fff" />
</mask>
<g mask="url(#b)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 0v24h32V0H-4Z"
fill="#F7FCFF"
/>
<mask
id="c"
maskUnits="userSpaceOnUse"
x="-4"
y="0"
width="32"
height="24"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 0v24h32V0H-4Z"
fill="#fff"
/>
</mask>
<g mask="url(#c)" fillRule="evenodd" clipRule="evenodd">
<path d="M-4 0v8h32V0H-4Z" fill="#FF8C1A" />
<path d="M-4 16v8h32v-8H-4Z" fill="#5EAA22" />
<path
d="M8 12a4 4 0 1 0 8 0 4 4 0 0 0-8 0Zm7 0a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
fill="#3D58DB"
/>
<path
d="m12 12.9-.6 3 .4-3-1.5 2.8 1.2-3L9.4 15l2-2.4-2.8 1.6 2.6-1.8-3 .7 3-1H8l3.2-.2-3-1 3 .8-2.6-1.9 2.8 1.7-2-2.5 2.1 2.3-1.2-3 1.5 2.9-.4-3.2.6 3.2.6-3.2-.4 3.2 1.5-2.8-1.2 2.9L14.6 9l-2 2.5 2.8-1.7-2.6 1.9 3-.8-3 1 3.2.1-3.2.1 3 1-3-.7 2.6 1.8-2.8-1.6 2 2.4-2.1-2.3 1.2 3-1.5-2.9.4 3.2-.6-3.1Z"
fill="#3D58DB"
/>
</g>
</g>
</g>
<defs>
<clipPath id="a">
<rect width="24" height="24" rx="12" fill="#fff" />
</clipPath>
</defs>
</svg>
</SvgIcon>
);
}
export function UsaFlag() {
return (
<SvgIcon>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_983_1725)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 0H28V24H-4V0Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 14.6667V16.6667H28V14.6667H-4Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 18.3333V20.3333H28V18.3333H-4Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 7.33325V9.33325H28V7.33325H-4Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 22V24H28V22H-4Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 11V13H28V11H-4Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 0V2H28V0H-4Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 3.66675V5.66675H28V3.66675H-4Z"
fill="#E31D1C"
/>
<path d="M-4 0H16V13H-4V0Z" fill="#2E42A5" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-2.27876 2.93871L-3.00465 3.44759L-2.75958 2.54198L-3.4043 1.96807H-2.56221L-2.27978 1.229L-1.94861 1.96807H-1.23075L-1.79479 2.54198L-1.57643 3.44759L-2.27876 2.93871ZM1.72124 2.93871L0.995357 3.44759L1.24042 2.54198L0.595707 1.96807H1.43779L1.72022 1.229L2.05139 1.96807H2.76925L2.20521 2.54198L2.42357 3.44759L1.72124 2.93871ZM4.99536 3.44759L5.72124 2.93871L6.42357 3.44759L6.20517 2.54198L6.76927 1.96807H6.05137L5.72022 1.229L5.43779 1.96807H4.59571L5.24042 2.54198L4.99536 3.44759ZM9.72127 2.93871L8.99537 3.44759L9.24047 2.54198L8.59567 1.96807H9.43777L9.72027 1.229L10.0514 1.96807H10.7693L10.2052 2.54198L10.4236 3.44759L9.72127 2.93871ZM-3.00465 7.44759L-2.27876 6.93871L-1.57643 7.44759L-1.79479 6.54198L-1.23075 5.96807H-1.94861L-2.27978 5.229L-2.56221 5.96807H-3.4043L-2.75958 6.54198L-3.00465 7.44759ZM1.72124 6.93871L0.995357 7.44759L1.24042 6.54198L0.595707 5.96807H1.43779L1.72022 5.229L2.05139 5.96807H2.76925L2.20521 6.54198L2.42357 7.44759L1.72124 6.93871ZM4.99536 7.44759L5.72124 6.93871L6.42357 7.44759L6.20517 6.54198L6.76927 5.96807H6.05137L5.72022 5.229L5.43779 5.96807H4.59571L5.24042 6.54198L4.99536 7.44759ZM9.72127 6.93871L8.99537 7.44759L9.24047 6.54198L8.59567 5.96807H9.43777L9.72027 5.229L10.0514 5.96807H10.7693L10.2052 6.54198L10.4236 7.44759L9.72127 6.93871ZM-3.00465 11.4476L-2.27876 10.9387L-1.57643 11.4476L-1.79479 10.542L-1.23075 9.96807H-1.94861L-2.27978 9.229L-2.56221 9.96807H-3.4043L-2.75958 10.542L-3.00465 11.4476ZM1.72124 10.9387L0.995357 11.4476L1.24042 10.542L0.595707 9.96807H1.43779L1.72022 9.229L2.05139 9.96807H2.76925L2.20521 10.542L2.42357 11.4476L1.72124 10.9387ZM4.99536 11.4476L5.72124 10.9387L6.42357 11.4476L6.20517 10.542L6.76927 9.96807H6.05137L5.72022 9.229L5.43779 9.96807H4.59571L5.24042 10.542L4.99536 11.4476ZM9.72127 10.9387L8.99537 11.4476L9.24047 10.542L8.59567 9.96807H9.43777L9.72027 9.229L10.0514 9.96807H10.7693L10.2052 10.542L10.4236 11.4476L9.72127 10.9387ZM12.9954 3.44759L13.7213 2.93871L14.4236 3.44759L14.2052 2.54198L14.7693 1.96807H14.0514L13.7203 1.229L13.4378 1.96807H12.5957L13.2405 2.54198L12.9954 3.44759ZM13.7213 6.93871L12.9954 7.44759L13.2405 6.54198L12.5957 5.96807H13.4378L13.7203 5.229L14.0514 5.96807H14.7693L14.2052 6.54198L14.4236 7.44759L13.7213 6.93871ZM12.9954 11.4476L13.7213 10.9387L14.4236 11.4476L14.2052 10.542L14.7693 9.96807H14.0514L13.7203 9.229L13.4378 9.96807H12.5957L13.2405 10.542L12.9954 11.4476ZM-0.278763 4.93871L-1.00464 5.44759L-0.759583 4.54198L-1.40429 3.96807H-0.562213L-0.279783 3.229L0.0513873 3.96807H0.769247L0.205207 4.54198L0.423567 5.44759L-0.278763 4.93871ZM2.99536 5.44759L3.72124 4.93871L4.42357 5.44759L4.20521 4.54198L4.76925 3.96807H4.05139L3.72022 3.229L3.43779 3.96807H2.59571L3.24042 4.54198L2.99536 5.44759ZM7.72127 4.93871L6.99537 5.44759L7.24047 4.54198L6.59567 3.96807H7.43777L7.72027 3.229L8.05137 3.96807H8.76927L8.20517 4.54198L8.42357 5.44759L7.72127 4.93871ZM-1.00464 9.44759L-0.278763 8.93871L0.423567 9.44759L0.205207 8.54198L0.769247 7.96807H0.0513873L-0.279783 7.229L-0.562213 7.96807H-1.40429L-0.759583 8.54198L-1.00464 9.44759ZM3.72124 8.93871L2.99536 9.44759L3.24042 8.54198L2.59571 7.96807H3.43779L3.72022 7.229L4.05139 7.96807H4.76925L4.20521 8.54198L4.42357 9.44759L3.72124 8.93871ZM6.99537 9.44759L7.72127 8.93871L8.42357 9.44759L8.20517 8.54198L8.76927 7.96807H8.05137L7.72027 7.229L7.43777 7.96807H6.59567L7.24047 8.54198L6.99537 9.44759ZM11.7213 4.93871L10.9954 5.44759L11.2405 4.54198L10.5957 3.96807H11.4378L11.7203 3.229L12.0514 3.96807H12.7693L12.2052 4.54198L12.4236 5.44759L11.7213 4.93871ZM10.9954 9.44759L11.7213 8.93871L12.4236 9.44759L12.2052 8.54198L12.7693 7.96807H12.0514L11.7203 7.229L11.4378 7.96807H10.5957L11.2405 8.54198L10.9954 9.44759Z"
fill="#F7FCFF"
/>
</g>
<defs>
<clipPath id="clip0_983_1725">
<rect width="24" height="24" rx="12" fill="white" />
</clipPath>
</defs>
</svg>
</SvgIcon>
);
}
export function BrazilFlag() {
return (
<SvgIcon>
<svg
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_983_1741)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-4 0.5V24.5H28V0.5H-4Z"
fill="#009933"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.9265 4.20404L24.1283 12.7075L11.7605 20.6713L-0.191406 12.5427L11.9265 4.20404Z"
fill="#FFD221"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.9265 4.20404L24.1283 12.7075L11.7605 20.6713L-0.191406 12.5427L11.9265 4.20404Z"
fill="url(#paint0_linear_983_1741)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 17.7C14.7614 17.7 17 15.4614 17 12.7C17 9.93853 14.7614 7.69995 12 7.69995C9.2386 7.69995 7 9.93853 7 12.7C7 15.4614 9.2386 17.7 12 17.7Z"
fill="#2E42A5"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.379 15.07L10.1556 15.1874L10.1983 14.9387L10.0176 14.7626L10.2673 14.7263L10.379 14.5L10.4907 14.7263L10.7404 14.7626L10.5597 14.9387L10.6024 15.1874L10.379 15.07Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12.379 15.07L12.1556 15.1874L12.1983 14.9387L12.0176 14.7626L12.2673 14.7263L12.379 14.5L12.4907 14.7263L12.7404 14.7626L12.5597 14.9387L12.6024 15.1874L12.379 15.07Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12.379 16.27L12.1556 16.3874L12.1983 16.1387L12.0176 15.9625L12.2673 15.9262L12.379 15.7L12.4907 15.9262L12.7404 15.9625L12.5597 16.1387L12.6024 16.3874L12.379 16.27Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.379 12.07L11.1556 12.1874L11.1983 11.9387L11.0176 11.7626L11.2673 11.7263L11.379 11.5L11.4907 11.7263L11.7404 11.7626L11.5597 11.9387L11.6024 12.1874L11.379 12.07Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.379 14.07L11.1556 14.1874L11.1983 13.9387L11.0176 13.7626L11.2673 13.7263L11.379 13.5L11.4907 13.7263L11.7404 13.7626L11.5597 13.9387L11.6024 14.1874L11.379 14.07Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.97859 13.07L9.75519 13.1874L9.79789 12.9387L9.61719 12.7626L9.86689 12.7263L9.97859 12.5L10.0903 12.7263L10.34 12.7626L10.1593 12.9387L10.2019 13.1874L9.97859 13.07Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.5783 13.87L8.3549 13.9875L8.3976 13.7388L8.2168 13.5626L8.4666 13.5263L8.5783 13.3L8.6899 13.5263L8.9397 13.5626L8.759 13.7388L8.8016 13.9875L8.5783 13.87Z"
fill="#F7FCFF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.1798 10.47L12.9565 10.5875L12.9991 10.3387L12.8184 10.1626L13.0682 10.1263L13.1798 9.90002L13.2915 10.1263L13.5413 10.1626L13.3605 10.3387L13.4032 10.5875L13.1798 10.47Z"
fill="#F7FCFF"
/>
<path
d="M7 12L7.5 10C11.6854 10.2946 14.6201 11.2147 17 13.5L16.5 15C14.4373 13.0193 10.7839 12.2664 7 12Z"
fill="#F7FCFF"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_983_1741"
x1="27.9997"
y1="24.5"
x2="27.9997"
y2="0.5"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFC600" />
<stop offset="1" stopColor="#FFDE42" />
</linearGradient>
<clipPath id="clip0_983_1741">
<rect y="0.5" width="24" height="24" rx="12" fill="white" />
</clipPath>
</defs>
</svg>
</SvgIcon>
);
}
export function GlobeFlag() {
return (
<SvgIcon>
<svg
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_986_1789)">
<circle cx="12" cy="12.5" r="12" fill="#007FFF" />
<path
d="M12 0.5C5.376 0.5 0 5.876 0 12.5C0 19.124 5.376 24.5 12 24.5C18.624 24.5 24 19.124 24 12.5C24 5.876 18.624 0.5 12 0.5ZM10.8 22.016C6.06 21.428 2.4 17.396 2.4 12.5C2.4 11.756 2.496 11.048 2.652 10.352L8.4 16.1V17.3C8.4 18.62 9.48 19.7 10.8 19.7V22.016ZM19.08 18.968C18.768 17.996 17.88 17.3 16.8 17.3H15.6V13.7C15.6 13.04 15.06 12.5 14.4 12.5H7.2V10.1H9.6C10.26 10.1 10.8 9.56 10.8 8.9V6.5H13.2C14.52 6.5 15.6 5.42 15.6 4.1V3.608C19.116 5.036 21.6 8.48 21.6 12.5C21.6 14.996 20.64 17.264 19.08 18.968Z"
fill="#3EE07F"
/>
</g>
<defs>
<clipPath id="clip0_986_1789">
<rect width="24" height="24" fill="white" transform="translate(0 0.5)" />
</clipPath>
</defs>
</svg>
</SvgIcon>
);
}

View File

@ -0,0 +1,636 @@
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Chip from '@mui/material/Chip';
import { GridCellParams, GridRowsProp, GridColDef } from '@mui/x-data-grid';
import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
type SparkLineData = number[];
function getDaysInMonth(month: number, year: number) {
const date = new Date(year, month, 0);
const monthName = date.toLocaleDateString('en-US', {
month: 'short',
});
const daysInMonth = date.getDate();
const days = [];
let i = 1;
while (days.length < daysInMonth) {
days.push(`${monthName} ${i}`);
i += 1;
}
return days;
}
function renderSparklineCell(params: GridCellParams<SparkLineData, any>) {
const data = getDaysInMonth(4, 2024);
const { value, colDef } = params;
if (!value || value.length === 0) {
return null;
}
return (
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
<SparkLineChart
data={value}
width={colDef.computedWidth || 100}
height={32}
plotType="bar"
showHighlight
showTooltip
colors={['hsl(210, 98%, 42%)']}
xAxis={{
scaleType: 'band',
data,
}}
/>
</div>
);
}
function renderStatus(status: 'Online' | 'Offline') {
const colors: { [index: string]: 'success' | 'default' } = {
Online: 'success',
Offline: 'default',
};
return <Chip label={status} color={colors[status]} size="small" />;
}
export function renderAvatar(
params: GridCellParams<{ name: string; color: string }, any, any>,
) {
if (params.value == null) {
return '';
}
return (
<Avatar
sx={{
backgroundColor: params.value.color,
width: '24px',
height: '24px',
fontSize: '0.85rem',
}}
>
{params.value.name.toUpperCase().substring(0, 1)}
</Avatar>
);
}
export const columns: GridColDef[] = [
{ field: 'pageTitle', headerName: 'Page Title', flex: 1.5, minWidth: 200 },
{
field: 'status',
headerName: 'Status',
flex: 0.5,
minWidth: 80,
renderCell: (params) => renderStatus(params.value as any),
},
{
field: 'users',
headerName: 'Users',
headerAlign: 'right',
align: 'right',
flex: 1,
minWidth: 80,
},
{
field: 'eventCount',
headerName: 'Event Count',
headerAlign: 'right',
align: 'right',
flex: 1,
minWidth: 100,
},
{
field: 'viewsPerUser',
headerName: 'Views per User',
headerAlign: 'right',
align: 'right',
flex: 1,
minWidth: 120,
},
{
field: 'averageTime',
headerName: 'Average Time',
headerAlign: 'right',
align: 'right',
flex: 1,
minWidth: 100,
},
{
field: 'conversions',
headerName: 'Daily Conversions',
flex: 1,
minWidth: 150,
renderCell: renderSparklineCell,
},
];
export const rows: GridRowsProp = [
{
id: 1,
pageTitle: 'Homepage Overview',
status: 'Online',
eventCount: 8345,
users: 212423,
viewsPerUser: 18.5,
averageTime: '2m 15s',
conversions: [
469172, 488506, 592287, 617401, 640374, 632751, 668638, 807246, 749198, 944863,
911787, 844815, 992022, 1143838, 1446926, 1267886, 1362511, 1348746, 1560533,
1670690, 1695142, 1916613, 1823306, 1683646, 2025965, 2529989, 3263473,
3296541, 3041524, 2599497,
],
},
{
id: 2,
pageTitle: 'Product Details - Gadgets',
status: 'Online',
eventCount: 5653,
users: 172240,
viewsPerUser: 9.7,
averageTime: '2m 30s',
conversions: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
557488, 1341471, 2044561, 2206438,
],
},
{
id: 3,
pageTitle: 'Checkout Process - Step 1',
status: 'Offline',
eventCount: 3455,
users: 58240,
viewsPerUser: 15.2,
averageTime: '2m 10s',
conversions: [
166896, 190041, 248686, 226746, 261744, 271890, 332176, 381123, 396435, 495620,
520278, 460839, 704158, 559134, 681089, 712384, 765381, 771374, 851314, 907947,
903675, 1049642, 1003160, 881573, 1072283, 1139115, 1382701, 1395655, 1355040,
1381571,
],
},
{
id: 4,
pageTitle: 'User Profile Dashboard',
status: 'Online',
eventCount: 112543,
users: 96240,
viewsPerUser: 4.5,
averageTime: '2m 40s',
conversions: [
264651, 311845, 436558, 439385, 520413, 533380, 562363, 533793, 558029, 791126,
649082, 566792, 723451, 737827, 890859, 935554, 1044397, 1022973, 1129827,
1145309, 1195630, 1358925, 1373160, 1172679, 1340106, 1396974, 1623641,
1687545, 1581634, 1550291,
],
},
{
id: 5,
pageTitle: 'Article Listing - Tech News',
status: 'Offline',
eventCount: 3653,
users: 142240,
viewsPerUser: 3.1,
averageTime: '2m 55s',
conversions: [
251871, 262216, 402383, 396459, 378793, 406720, 447538, 451451, 457111, 589821,
640744, 504879, 626099, 662007, 754576, 768231, 833019, 851537, 972306,
1014831, 1027570, 1189068, 1119099, 987244, 1197954, 1310721, 1480816, 1577547,
1854053, 1791831,
],
},
{
id: 6,
pageTitle: 'FAQs - Customer Support',
status: 'Online',
eventCount: 106543,
users: 15240,
viewsPerUser: 7.2,
averageTime: '2m 20s',
conversions: [
13671, 16918, 27272, 34315, 42212, 56369, 64241, 77857, 70680, 91093, 108306,
94734, 132289, 133860, 147706, 158504, 192578, 207173, 220052, 233496, 250091,
285557, 268555, 259482, 274019, 321648, 359801, 399502, 447249, 497403,
],
},
{
id: 7,
pageTitle: 'Product Comparison - Laptops',
status: 'Offline',
eventCount: 7853,
users: 32240,
viewsPerUser: 6.5,
averageTime: '2m 50s',
conversions: [
93682, 107901, 144919, 151769, 170804, 183736, 201752, 219792, 227887, 295382,
309600, 278050, 331964, 356826, 404896, 428090, 470245, 485582, 539056, 582112,
594289, 671915, 649510, 574911, 713843, 754965, 853020, 916793, 960158, 984265,
],
},
{
id: 8,
pageTitle: 'Shopping Cart - Electronics',
status: 'Online',
eventCount: 8563,
users: 48240,
viewsPerUser: 4.3,
averageTime: '3m 10s',
conversions: [
52394, 63357, 82800, 105466, 128729, 144472, 172148, 197919, 212302, 278153,
290499, 249824, 317499, 333024, 388925, 410576, 462099, 488477, 533956, 572307,
591019, 681506, 653332, 581234, 719038, 783496, 911609, 973328, 1056071,
1112940,
],
},
{
id: 9,
pageTitle: 'Payment Confirmation - Bank Transfer',
status: 'Offline',
eventCount: 4563,
users: 18240,
viewsPerUser: 2.7,
averageTime: '3m 25s',
conversions: [
15372, 16901, 25489, 30148, 40857, 51136, 64627, 75804, 89633, 100407, 114908,
129957, 143568, 158509, 174822, 192488, 211512, 234702, 258812, 284328, 310431,
338186, 366582, 396749, 428788, 462880, 499125, 537723, 578884, 622825,
],
},
{
id: 10,
pageTitle: 'Product Reviews - Smartphones',
status: 'Online',
eventCount: 9863,
users: 28240,
viewsPerUser: 5.1,
averageTime: '3m 05s',
conversions: [
70211, 89234, 115676, 136021, 158744, 174682, 192890, 218073, 240926, 308190,
317552, 279834, 334072, 354955, 422153, 443911, 501486, 538091, 593724, 642882,
686539, 788615, 754813, 687955, 883645, 978347, 1142551, 1233074, 1278155,
1356724,
],
},
{
id: 11,
pageTitle: 'Subscription Management - Services',
status: 'Offline',
eventCount: 6563,
users: 24240,
viewsPerUser: 4.8,
averageTime: '3m 15s',
conversions: [
49662, 58971, 78547, 93486, 108722, 124901, 146422, 167883, 189295, 230090,
249837, 217828, 266494, 287537, 339586, 363299, 412855, 440900, 490111, 536729,
580591, 671635, 655812, 576431, 741632, 819296, 971762, 1052605, 1099234,
1173591,
],
},
{
id: 12,
pageTitle: 'Order Tracking - Shipments',
status: 'Online',
eventCount: 12353,
users: 38240,
viewsPerUser: 3.5,
averageTime: '3m 20s',
conversions: [
29589, 37965, 55800, 64672, 77995, 91126, 108203, 128900, 148232, 177159,
193489, 164471, 210765, 229977, 273802, 299381, 341092, 371567, 413812, 457693,
495920, 564785, 541022, 491680, 618096, 704926, 833365, 904313, 974622,
1036567,
],
},
{
id: 13,
pageTitle: 'Customer Feedback - Surveys',
status: 'Offline',
eventCount: 5863,
users: 13240,
viewsPerUser: 2.3,
averageTime: '3m 30s',
conversions: [
8472, 9637, 14892, 19276, 23489, 28510, 33845, 39602, 45867, 52605, 59189,
65731, 76021, 85579, 96876, 108515, 119572, 131826, 145328, 160192, 176528,
196662, 217929, 239731, 262920, 289258, 315691, 342199, 370752, 402319,
],
},
{
id: 14,
pageTitle: 'Account Settings - Preferences',
status: 'Online',
eventCount: 7853,
users: 18240,
viewsPerUser: 3.2,
averageTime: '3m 15s',
conversions: [
15792, 16948, 22728, 25491, 28412, 31268, 34241, 37857, 42068, 46893, 51098,
55734, 60780, 66421, 72680, 79584, 87233, 95711, 105285, 115814, 127509,
140260, 154086, 169495, 186445, 205109, 225580, 247983, 272484, 299280,
],
},
{
id: 15,
pageTitle: 'Login Page - Authentication',
status: 'Offline',
eventCount: 9563,
users: 24240,
viewsPerUser: 2.5,
averageTime: '3m 35s',
conversions: [
25638, 28355, 42089, 53021, 66074, 80620, 97989, 118202, 142103, 166890,
193869, 225467, 264089, 307721, 358059, 417835, 488732, 573924, 674878, 794657,
938542, 1111291, 1313329, 1543835, 1812156, 2123349, 2484926, 2907023, 3399566,
3973545,
],
},
{
id: 16,
pageTitle: 'Promotions - Seasonal Sales',
status: 'Online',
eventCount: 13423,
users: 54230,
viewsPerUser: 7.8,
averageTime: '2m 45s',
conversions: [
241732, 256384, 289465, 321423, 345672, 378294, 398472, 420364, 436278, 460192,
495374, 510283, 532489, 559672, 587312, 610982, 629385, 654732, 678925, 704362,
725182, 749384, 772361, 798234, 819472, 846291, 872183, 894673, 919283, 945672,
],
},
{
id: 17,
pageTitle: 'Tutorials - How to Guides',
status: 'Offline',
eventCount: 4234,
users: 19342,
viewsPerUser: 5.2,
averageTime: '3m 05s',
conversions: [
12345, 14567, 16789, 18901, 21023, 23145, 25267, 27389, 29501, 31623, 33745,
35867, 37989, 40101, 42223, 44345, 46467, 48589, 50701, 52823, 54945, 57067,
59189, 61301, 63423, 65545, 67667, 69789, 71901, 74023,
],
},
{
id: 18,
pageTitle: 'Blog Posts - Tech Insights',
status: 'Online',
eventCount: 8567,
users: 34234,
viewsPerUser: 6.3,
averageTime: '2m 50s',
conversions: [
23456, 25678, 27890, 30102, 32324, 34546, 36768, 38980, 41202, 43424, 45646,
47868, 50080, 52302, 54524, 56746, 58968, 61180, 63402, 65624, 67846, 70068,
72290, 74502, 76724, 78946, 81168, 83380, 85602, 87824,
],
},
{
id: 19,
pageTitle: 'Events - Webinars',
status: 'Offline',
eventCount: 3456,
users: 19234,
viewsPerUser: 4.5,
averageTime: '3m 20s',
conversions: [
123456, 145678, 167890, 190012, 212324, 234546, 256768, 278980, 301202, 323424,
345646, 367868, 390080, 412302, 434524, 456746, 478968, 501180, 523402, 545624,
567846, 590068, 612290, 634502, 656724, 678946, 701168, 723380, 745602, 767824,
],
},
{
id: 20,
pageTitle: 'Support - Contact Us',
status: 'Online',
eventCount: 6734,
users: 27645,
viewsPerUser: 3.9,
averageTime: '2m 55s',
conversions: [
234567, 256789, 278901, 301023, 323245, 345467, 367689, 389801, 412023, 434245,
456467, 478689, 500801, 523023, 545245, 567467, 589689, 611801, 634023, 656245,
678467, 700689, 722801, 745023, 767245, 789467, 811689, 833801, 856023, 878245,
],
},
{
id: 21,
pageTitle: 'Case Studies - Success Stories',
status: 'Offline',
eventCount: 4567,
users: 19345,
viewsPerUser: 6.1,
averageTime: '3m 10s',
conversions: [
34567, 36789, 38901, 41023, 43145, 45267, 47389, 49501, 51623, 53745, 55867,
57989, 60101, 62223, 64345, 66467, 68589, 70701, 72823, 74945, 77067, 79189,
81301, 83423, 85545, 87667, 89789, 91901, 94023, 96145,
],
},
{
id: 22,
pageTitle: 'News - Industry Updates',
status: 'Online',
eventCount: 7856,
users: 34567,
viewsPerUser: 5.7,
averageTime: '3m 05s',
conversions: [
45678, 47890, 50102, 52324, 54546, 56768, 58980, 61202, 63424, 65646, 67868,
70080, 72302, 74524, 76746, 78968, 81180, 83402, 85624, 87846, 90068, 92290,
94502, 96724, 98946, 101168, 103380, 105602, 107824, 110046,
],
},
{
id: 23,
pageTitle: 'Forum - User Discussions',
status: 'Offline',
eventCount: 5678,
users: 23456,
viewsPerUser: 4.2,
averageTime: '2m 40s',
conversions: [
56789, 58901, 61023, 63145, 65267, 67389, 69501, 71623, 73745, 75867, 77989,
80101, 82223, 84345, 86467, 88589, 90701, 92823, 94945, 97067, 99189, 101301,
103423, 105545, 107667, 109789, 111901, 114023, 116145, 118267,
],
},
{
id: 24,
pageTitle: 'Documentation - API Reference',
status: 'Online',
eventCount: 6789,
users: 27689,
viewsPerUser: 5.0,
averageTime: '3m 00s',
conversions: [
67890, 70102, 72324, 74546, 76768, 78980, 81202, 83424, 85646, 87868, 90080,
92302, 94524, 96746, 98968, 101180, 103402, 105624, 107846, 110068, 112290,
114502, 116724, 118946, 121168, 123380, 125602, 127824, 130046, 132268,
],
},
{
id: 25,
pageTitle: 'Services - Consulting',
status: 'Offline',
eventCount: 4563,
users: 19240,
viewsPerUser: 6.4,
averageTime: '3m 25s',
conversions: [
345678, 367890, 390012, 412324, 434546, 456768, 478980, 501202, 523424, 545646,
567868, 590080, 612302, 634524, 656746, 678968, 701180, 723402, 745624, 767846,
790068, 812290, 834502, 856724, 878946, 901168, 923380, 945602, 967824, 990046,
],
},
{
id: 26,
pageTitle: 'Feedback - User Reviews',
status: 'Online',
eventCount: 8564,
users: 34240,
viewsPerUser: 6.2,
averageTime: '3m 15s',
conversions: [
123478, 145690, 167912, 190134, 212356, 234578, 256790, 279012, 301234, 323456,
345678, 367890, 390012, 412234, 434456, 456678, 478890, 501012, 523234, 545456,
567678, 589890, 612012, 634234, 656456, 678678, 700890, 723012, 745234, 767456,
],
},
{
id: 27,
pageTitle: 'Profiles - Team Members',
status: 'Offline',
eventCount: 5634,
users: 23423,
viewsPerUser: 5.5,
averageTime: '2m 45s',
conversions: [
345123, 367345, 389567, 411789, 434012, 456234, 478456, 500678, 522901, 545123,
567345, 589567, 611789, 634012, 656234, 678456, 700678, 722901, 745123, 767345,
789567, 811789, 834012, 856234, 878456, 900678, 922901, 945123, 967345, 989567,
],
},
{
id: 28,
pageTitle: 'Notifications - Alerts',
status: 'Online',
eventCount: 6745,
users: 27654,
viewsPerUser: 4.9,
averageTime: '3m 10s',
conversions: [
456123, 478345, 500567, 522789, 545012, 567234, 589456, 611678, 633901, 656123,
678345, 700567, 722789, 745012, 767234, 789456, 811678, 833901, 856123, 878345,
900567, 922789, 945012, 967234, 989456, 1011678, 1033901, 1056123, 1078345,
1100567,
],
},
{
id: 29,
pageTitle: 'Dashboard - Metrics',
status: 'Offline',
eventCount: 5678,
users: 23456,
viewsPerUser: 6.3,
averageTime: '2m 50s',
conversions: [
567890, 590112, 612334, 634556, 656778, 678990, 701212, 723434, 745656, 767878,
790100, 812322, 834544, 856766, 878988, 901210, 923432, 945654, 967876, 990098,
1012320, 1034542, 1056764, 1078986, 1101208, 1123430, 1145652, 1167874,
1190096, 1212318,
],
},
{
id: 30,
pageTitle: 'Reports - Monthly Analysis',
status: 'Online',
eventCount: 7890,
users: 34567,
viewsPerUser: 5.9,
averageTime: '3m 20s',
conversions: [
678901, 701123, 723345, 745567, 767789, 790011, 812233, 834455, 856677, 878899,
901121, 923343, 945565, 967787, 990009, 1012231, 1034453, 1056675, 1078897,
1101119, 1123341, 1145563, 1167785, 1190007, 1212229, 1234451, 1256673,
1278895, 1301117, 1323339,
],
},
{
id: 31,
pageTitle: 'Training - Employee Onboarding',
status: 'Offline',
eventCount: 3456,
users: 19234,
viewsPerUser: 6.1,
averageTime: '3m 10s',
conversions: [
789012, 811234, 833456, 855678, 877890, 900112, 922334, 944556, 966778, 989000,
1011222, 1033444, 1055666, 1077888, 1100110, 1122332, 1144554, 1166776,
1188998, 1211220, 1233442, 1255664, 1277886, 1300108, 1322330, 1344552,
1366774, 1388996, 1411218, 1433440,
],
},
{
id: 32,
pageTitle: 'Resources - Knowledge Base',
status: 'Online',
eventCount: 5678,
users: 23456,
viewsPerUser: 4.7,
averageTime: '3m 25s',
conversions: [
890123, 912345, 934567, 956789, 979012, 1001234, 1023456, 1045678, 1067890,
1090123, 1112345, 1134567, 1156789, 1179012, 1201234, 1223456, 1245678,
1267890, 1290123, 1312345, 1334567, 1356789, 1379012, 1401234, 1423456,
1445678, 1467890, 1490123, 1512345, 1534567,
],
},
{
id: 33,
pageTitle: 'Settings - Privacy Controls',
status: 'Offline',
eventCount: 6789,
users: 27689,
viewsPerUser: 5.8,
averageTime: '3m 05s',
conversions: [
901234, 923456, 945678, 967890, 990112, 1012334, 1034556, 1056778, 1079000,
1101222, 1123444, 1145666, 1167888, 1190110, 1212332, 1234554, 1256776,
1278998, 1301220, 1323442, 1345664, 1367886, 1390108, 1412330, 1434552,
1456774, 1478996, 1501218, 1523440, 1545662,
],
},
{
id: 34,
pageTitle: 'Integrations - Third-Party Services',
status: 'Online',
eventCount: 4567,
users: 19345,
viewsPerUser: 4.4,
averageTime: '2m 50s',
conversions: [
123457, 145679, 167891, 190113, 212335, 234557, 256779, 279001, 301223, 323445,
345667, 367889, 390011, 412233, 434455, 456677, 478899, 501121, 523343, 545565,
567787, 590009, 612231, 634453, 656675, 678897, 701119, 723341, 745563, 767785,
],
},
{
id: 35,
pageTitle: 'Account - Billing Information',
status: 'Offline',
eventCount: 7890,
users: 34567,
viewsPerUser: 5.4,
averageTime: '3m 00s',
conversions: [
234568, 256790, 278912, 301134, 323356, 345578, 367790, 390012, 412234, 434456,
456678, 478890, 501112, 523334, 545556, 567778, 590000, 612222, 634444, 656666,
678888, 701110, 723332, 745554, 767776, 789998, 812220, 834442, 856664, 878886,
],
},
];

View File

@ -0,0 +1,76 @@
import { Theme } from '@mui/material/styles';
import { axisClasses, legendClasses, chartsGridClasses } from '@mui/x-charts';
import type { ChartsComponents } from '@mui/x-charts/themeAugmentation';
import { gray } from '../../../shared-theme/themePrimitives';
/* eslint-disable import/prefer-default-export */
export const chartsCustomizations: ChartsComponents<Theme> = {
MuiChartsAxis: {
styleOverrides: {
root: ({ theme }) => ({
[`& .${axisClasses.line}`]: {
stroke: gray[300],
},
[`& .${axisClasses.tick}`]: { stroke: gray[300] },
[`& .${axisClasses.tickLabel}`]: {
fill: gray[500],
fontWeight: 500,
},
...theme.applyStyles('dark', {
[`& .${axisClasses.line}`]: {
stroke: gray[700],
},
[`& .${axisClasses.tick}`]: { stroke: gray[700] },
[`& .${axisClasses.tickLabel}`]: {
fill: gray[300],
fontWeight: 500,
},
}),
}),
},
},
MuiChartsTooltip: {
styleOverrides: {
mark: ({ theme }) => ({
ry: 6,
boxShadow: 'none',
border: `1px solid ${(theme.vars || theme).palette.divider}`,
}),
table: ({ theme }) => ({
border: `1px solid ${(theme.vars || theme).palette.divider}`,
borderRadius: theme.shape.borderRadius,
background: 'hsl(0, 0%, 100%)',
...theme.applyStyles('dark', {
background: gray[900],
}),
}),
},
},
MuiChartsLegend: {
styleOverrides: {
root: {
[`& .${legendClasses.mark}`]: {
ry: 6,
},
},
},
},
MuiChartsGrid: {
styleOverrides: {
root: ({ theme }) => ({
[`& .${chartsGridClasses.line}`]: {
stroke: gray[200],
strokeDasharray: '4 2',
strokeWidth: 0.8,
},
...theme.applyStyles('dark', {
[`& .${chartsGridClasses.line}`]: {
stroke: gray[700],
strokeDasharray: '4 2',
strokeWidth: 0.8,
},
}),
}),
},
},
};

View File

@ -0,0 +1,132 @@
import { paperClasses } from '@mui/material/Paper';
import { alpha, Theme } from '@mui/material/styles';
import type { DataGridProComponents } from '@mui/x-data-grid-pro/themeAugmentation';
import { menuItemClasses } from '@mui/material/MenuItem';
import { listItemIconClasses } from '@mui/material/ListItemIcon';
import { iconButtonClasses } from '@mui/material/IconButton';
import { checkboxClasses } from '@mui/material/Checkbox';
import { listClasses } from '@mui/material/List';
import { gridClasses } from '@mui/x-data-grid';
import { tablePaginationClasses } from '@mui/material/TablePagination';
import { gray } from '../../../shared-theme/themePrimitives';
/* eslint-disable import/prefer-default-export */
export const dataGridCustomizations: DataGridProComponents<Theme> & DataGridProComponents<Theme> = {
MuiDataGrid: {
styleOverrides: {
root: ({ theme }) => ({
'--DataGrid-overlayHeight': '300px',
overflow: 'clip',
borderColor: (theme.vars || theme).palette.divider,
backgroundColor: (theme.vars || theme).palette.background.default,
[`& .${gridClasses.columnHeader}`]: {
backgroundColor: (theme.vars || theme).palette.background.paper,
},
[`& .${gridClasses.footerContainer}`]: {
backgroundColor: (theme.vars || theme).palette.background.paper,
},
[`& .${checkboxClasses.root}`]: {
padding: theme.spacing(0.5),
'& > svg': {
fontSize: '1rem',
},
},
[`& .${tablePaginationClasses.root}`]: {
marginRight: theme.spacing(1),
'& .MuiIconButton-root': {
maxHeight: 32,
maxWidth: 32,
'& > svg': {
fontSize: '1rem',
},
},
},
}),
cell: ({ theme }) => ({ borderTopColor: (theme.vars || theme).palette.divider }),
menu: ({ theme }) => ({
borderRadius: theme.shape.borderRadius,
backgroundImage: 'none',
[`& .${paperClasses.root}`]: {
border: `1px solid ${(theme.vars || theme).palette.divider}`,
},
[`& .${menuItemClasses.root}`]: {
margin: '0 4px',
},
[`& .${listItemIconClasses.root}`]: {
marginRight: 0,
},
[`& .${listClasses.root}`]: {
paddingLeft: 0,
paddingRight: 0,
},
}),
row: ({ theme }) => ({
'&:last-of-type': { borderBottom: `1px solid ${(theme.vars || theme).palette.divider}` },
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
'&.Mui-selected': {
background: (theme.vars || theme).palette.action.selected,
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
},
}),
iconButtonContainer: ({ theme }) => ({
[`& .${iconButtonClasses.root}`]: {
border: 'none',
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: alpha(theme.palette.action.selected, 0.3),
},
'&:active': {
backgroundColor: gray[200],
},
...theme.applyStyles('dark', {
color: gray[50],
'&:hover': {
backgroundColor: gray[800],
},
'&:active': {
backgroundColor: gray[900],
},
}),
},
}),
menuIconButton: ({ theme }) => ({
border: 'none',
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: gray[100],
},
'&:active': {
backgroundColor: gray[200],
},
...theme.applyStyles('dark', {
color: gray[50],
'&:hover': {
backgroundColor: gray[800],
},
'&:active': {
backgroundColor: gray[900],
},
}),
}),
filterForm: ({ theme }) => ({
gap: theme.spacing(1),
alignItems: 'flex-end',
}),
columnsManagementHeader: ({ theme }) => ({
paddingRight: theme.spacing(3),
paddingLeft: theme.spacing(3),
}),
columnHeaderTitleContainer: {
flexGrow: 1,
justifyContent: 'space-between',
},
columnHeaderDraggableContainer: { paddingRight: 2 },
},
},
};

View File

@ -0,0 +1,173 @@
import { alpha, Theme } from '@mui/material/styles';
import type { PickersProComponents } from '@mui/x-date-pickers-pro/themeAugmentation';
import type { PickerComponents } from '@mui/x-date-pickers/themeAugmentation';
import { pickersYearClasses, pickersMonthClasses, pickersDayClasses } from '@mui/x-date-pickers';
import { menuItemClasses } from '@mui/material/MenuItem';
import { gray, brand } from '../../../shared-theme/themePrimitives';
/* eslint-disable import/prefer-default-export */
export const datePickersCustomizations: PickersProComponents<Theme> & PickerComponents<Theme> = {
MuiPickersPopper: {
styleOverrides: {
paper: ({ theme }) => ({
marginTop: 4,
borderRadius: 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',
[`& .${menuItemClasses.root}`]: {
borderRadius: 6,
margin: '0 6px',
},
...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',
}),
}),
},
},
MuiPickersArrowSwitcher: {
styleOverrides: {
spacer: { width: 16 },
button: ({ theme }) => ({
backgroundColor: 'transparent',
color: (theme.vars || theme).palette.grey[500],
...theme.applyStyles('dark', {
color: (theme.vars || theme).palette.grey[400],
}),
}),
},
},
MuiPickersCalendarHeader: {
styleOverrides: {
switchViewButton: {
padding: 0,
border: 'none',
},
},
},
MuiPickersMonth: {
styleOverrides: {
monthButton: ({ theme }) => ({
fontSize: theme.typography.body1.fontSize,
color: (theme.vars || theme).palette.grey[600],
padding: theme.spacing(0.5),
borderRadius: theme.shape.borderRadius,
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
[`&.${pickersMonthClasses.selected}`]: {
backgroundColor: gray[700],
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
backgroundColor: 'transparent',
[`&.${pickersMonthClasses.selected}`]: { backgroundColor: gray[700] },
},
...theme.applyStyles('dark', {
color: (theme.vars || theme).palette.grey[300],
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
[`&.${pickersMonthClasses.selected}`]: {
color: (theme.vars || theme).palette.common.black,
fontWeight: theme.typography.fontWeightMedium,
backgroundColor: gray[300],
},
'&:focus': {
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
backgroundColor: 'transparent',
[`&.${pickersMonthClasses.selected}`]: { backgroundColor: gray[300] },
},
}),
}),
},
},
MuiPickersYear: {
styleOverrides: {
yearButton: ({ theme }) => ({
fontSize: theme.typography.body1.fontSize,
color: (theme.vars || theme).palette.grey[600],
padding: theme.spacing(0.5),
borderRadius: theme.shape.borderRadius,
height: 'fit-content',
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
[`&.${pickersYearClasses.selected}`]: {
backgroundColor: gray[700],
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
backgroundColor: 'transparent',
[`&.${pickersYearClasses.selected}`]: { backgroundColor: gray[700] },
},
...theme.applyStyles('dark', {
color: (theme.vars || theme).palette.grey[300],
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
[`&.${pickersYearClasses.selected}`]: {
color: (theme.vars || theme).palette.common.black,
fontWeight: theme.typography.fontWeightMedium,
backgroundColor: gray[300],
},
'&:focus': {
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
backgroundColor: 'transparent',
[`&.${pickersYearClasses.selected}`]: { backgroundColor: gray[300] },
},
}),
}),
},
},
MuiPickersDay: {
styleOverrides: {
root: ({ theme }) => ({
fontSize: theme.typography.body1.fontSize,
color: (theme.vars || theme).palette.grey[600],
padding: theme.spacing(0.5),
borderRadius: theme.shape.borderRadius,
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
[`&.${pickersDayClasses.selected}`]: {
backgroundColor: gray[700],
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
backgroundColor: 'transparent',
[`&.${pickersDayClasses.selected}`]: { backgroundColor: gray[700] },
},
...theme.applyStyles('dark', {
color: (theme.vars || theme).palette.grey[300],
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
},
[`&.${pickersDayClasses.selected}`]: {
color: (theme.vars || theme).palette.common.black,
fontWeight: theme.typography.fontWeightMedium,
backgroundColor: gray[300],
},
'&:focus': {
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
backgroundColor: 'transparent',
[`&.${pickersDayClasses.selected}`]: { backgroundColor: gray[300] },
},
}),
}),
},
},
};

View File

@ -0,0 +1,4 @@
export { chartsCustomizations } from './charts';
export { dataGridCustomizations } from './dataGrid';
export { datePickersCustomizations } from './datePickers';
export { treeViewCustomizations } from './treeView';

View File

@ -0,0 +1,62 @@
import { alpha, Theme } from '@mui/material/styles';
import type { TreeViewComponents } from '@mui/x-tree-view/themeAugmentation';
import { gray, brand } from '../../../shared-theme/themePrimitives';
/* eslint-disable import/prefer-default-export */
export const treeViewCustomizations: TreeViewComponents<Theme> = {
MuiTreeItem2: {
styleOverrides: {
root: ({ theme }) => ({
position: 'relative',
boxSizing: 'border-box',
padding: theme.spacing(0, 1),
'& .groupTransition': {
marginLeft: theme.spacing(2),
padding: theme.spacing(0),
borderLeft: '1px solid',
borderColor: (theme.vars || theme).palette.divider,
},
'&:focus-visible .focused': {
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
'&:hover': {
backgroundColor: alpha(gray[300], 0.2),
outline: `3px solid ${alpha(brand[500], 0.5)}`,
outlineOffset: '2px',
},
},
}),
content: ({ theme }) => ({
marginTop: theme.spacing(1),
padding: theme.spacing(0.5, 1),
overflow: 'clip',
'&:hover': {
backgroundColor: alpha(gray[300], 0.2),
},
'&.selected': {
backgroundColor: alpha(gray[300], 0.4),
'&:hover': {
backgroundColor: alpha(gray[300], 0.6),
},
},
...theme.applyStyles('dark', {
'&:hover': {
backgroundColor: alpha(gray[500], 0.2),
},
'&:focus-visible': {
'&:hover': {
backgroundColor: alpha(gray[500], 0.2),
},
},
'&.selected': {
backgroundColor: alpha(gray[500], 0.4),
'&:hover': {
backgroundColor: alpha(gray[500], 0.6),
},
},
}),
}),
},
},
};

13
src/index.tsx Normal file
View File

@ -0,0 +1,13 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import type {} from '@mui/material/themeCssVarsAugmentation';
import { StyledEngineProvider } from '@mui/material/styles';
import App from './dashboard/Dashboard';
ReactDOM.createRoot(document.querySelector("#root")!).render(
<React.StrictMode>
<StyledEngineProvider injectFirst>
<App />
</StyledEngineProvider>
</React.StrictMode>
);

View File

@ -0,0 +1,53 @@
import * as React from 'react';
import { ThemeProvider, 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, typography, shadows, shape } from './themePrimitives';
interface AppThemeProps {
children: React.ReactNode;
/**
* This is for the docs site. You can ignore it or remove it.
*/
disableCustomTheme?: boolean;
themeComponents?: ThemeOptions['components'];
}
export default function AppTheme(props: AppThemeProps) {
const { children, disableCustomTheme, themeComponents } = props;
const theme = React.useMemo(() => {
return disableCustomTheme
? {}
: createTheme({
// For more details about CSS variables configuration, see https://mui.com/material-ui/customization/css-theme-variables/configuration/
cssVariables: {
colorSchemeSelector: 'data-mui-color-scheme',
cssVarPrefix: 'template',
},
colorSchemes, // Recently added in v6 for building light & dark mode app, see https://mui.com/material-ui/customization/palette/#color-schemes
typography,
shadows,
shape,
components: {
...inputsCustomizations,
...dataDisplayCustomizations,
...feedbackCustomizations,
...navigationCustomizations,
...surfacesCustomizations,
...themeComponents,
},
});
}, [disableCustomTheme, themeComponents]);
if (disableCustomTheme) {
return <React.Fragment>{children}</React.Fragment>;
}
return (
<ThemeProvider theme={theme} disableTransitionOnChange>
{children}
</ThemeProvider>
);
}

View 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>
);
}

View 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>
);
}

View File

@ -0,0 +1,233 @@
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: {
padding: '8px',
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,
padding: '2px 8px',
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',
},
},
],
},
},
},
};

View 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],
}),
}),
},
},
};

View File

@ -0,0 +1,445 @@
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';
/* 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[500],
},
},
},
},
MuiOutlinedInput: {
styleOverrides: {
input: {
padding: 0,
},
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,
}),
},
},
};

View 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 }),
},
}),
},
},
};

View 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,
},
},
},
};

View File

@ -0,0 +1,403 @@
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: 'Inter, sans-serif',
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: 'Inter, sans-serif',
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;

20
src/theme.tsx Normal file
View File

@ -0,0 +1,20 @@
import { createTheme } from '@mui/material/styles';
import { red } from '@mui/material/colors';
// A custom theme for this app
const theme = createTheme({
cssVariables: true,
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
},
});
export default theme;

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

7
vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
});