Sfoglia il codice sorgente

Add example login page and MUI theme

tags/Baseline_30082024_FRONTEND_UAT
Wayne Lee 1 anno fa
parent
commit
150c6a6dc6
20 ha cambiato i file con 983 aggiunte e 19 eliminazioni
  1. +4
    -1
      .env.development
  2. +134
    -6
      package-lock.json
  3. +5
    -2
      package.json
  4. +6
    -0
      src/app/api/auth/[...nextauth]/route.ts
  5. +5
    -0
      src/app/dashboard/page.tsx
  6. +2
    -8
      src/app/layout.tsx
  7. +94
    -0
      src/app/login/LoginForm.tsx
  8. +28
    -0
      src/app/login/page.tsx
  9. +4
    -2
      src/app/page.tsx
  10. +2
    -0
      src/config/api.ts
  11. +42
    -0
      src/config/authConfig.ts
  12. +8
    -0
      src/middleware.ts
  13. +100
    -0
      src/theme/EmotionCache.tsx
  14. +22
    -0
      src/theme/ThemeRegistry.tsx
  15. +57
    -0
      src/theme/devias-material-kit/colors.ts
  16. +288
    -0
      src/theme/devias-material-kit/components.ts
  17. +26
    -0
      src/theme/devias-material-kit/index.ts
  18. +35
    -0
      src/theme/devias-material-kit/palette.ts
  19. +31
    -0
      src/theme/devias-material-kit/shadows.ts
  20. +90
    -0
      src/theme/devias-material-kit/typography.ts

+ 4
- 1
.env.development Vedi File

@@ -1 +1,4 @@
API_HOST=localhost
API_HOST=localhost
API_PORT=8090
API_PROTOCOL=http
NEXTAUTH_SECRET=secret

+ 134
- 6
package-lock.json Vedi File

@@ -11,13 +11,16 @@
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
"@fontsource/inter": "^5.0.16",
"@fontsource/plus-jakarta-sans": "^5.0.18",
"@mui/icons-material": "^5.15.0",
"@mui/material": "^5.15.0",
"@mui/material-nextjs": "^5.15.0",
"next": "14.0.4",
"next-auth": "^4.24.5",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-hook-form": "^7.49.2"
},
"devDependencies": {
"@types/node": "^20",
@@ -285,10 +288,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@fontsource/roboto": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz",
"integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA=="
"node_modules/@fontsource/inter": {
"version": "5.0.16",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.0.16.tgz",
"integrity": "sha512-qF0aH5UiZvCmneX5orJbVRoc2VTyLTV3X/7laMp03Qt28L+B9tFlZODOGUL64wDWc69YVdi1LeJB0cIgd51lvw=="
},
"node_modules/@fontsource/plus-jakarta-sans": {
"version": "5.0.18",
"resolved": "https://registry.npmjs.org/@fontsource/plus-jakarta-sans/-/plus-jakarta-sans-5.0.18.tgz",
"integrity": "sha512-poMuIcQ8F7WGXF4mNUviDk49Ewdf0pU7wmCzWQNbWEtus+L46BSp+4OqbWy0LWJEmMLI9F5hUHaSo2maLJwrQw=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
@@ -662,6 +670,126 @@
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz",
"integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz",
"integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz",
"integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz",
"integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz",
"integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz",
"integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz",
"integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz",
"integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",


+ 5
- 2
package.json Vedi File

@@ -12,13 +12,16 @@
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
"@fontsource/inter": "^5.0.16",
"@fontsource/plus-jakarta-sans": "^5.0.18",
"@mui/icons-material": "^5.15.0",
"@mui/material": "^5.15.0",
"@mui/material-nextjs": "^5.15.0",
"next": "14.0.4",
"next-auth": "^4.24.5",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-hook-form": "^7.49.2"
},
"devDependencies": {
"@types/node": "^20",


+ 6
- 0
src/app/api/auth/[...nextauth]/route.ts Vedi File

@@ -0,0 +1,6 @@
import { authOptions } from "@/config/authConfig";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

+ 5
- 0
src/app/dashboard/page.tsx Vedi File

@@ -0,0 +1,5 @@
const Dashboard: React.FC = () => {
return "Dashboard (protected)";
};

export default Dashboard;

+ 2
- 8
src/app/layout.tsx Vedi File

@@ -1,11 +1,6 @@
import type { Metadata } from "next";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
import { CssBaseline } from "@mui/material";

import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import ThemeRegistry from "@/theme/ThemeRegistry";

export const metadata: Metadata = {
title: "Create Next App",
@@ -20,8 +15,7 @@ export default function RootLayout({
return (
<html lang="en">
<body>
<CssBaseline enableColorScheme />
<AppRouterCacheProvider>{children}</AppRouterCacheProvider>
<ThemeRegistry>{children}</ThemeRegistry>
</body>
</html>
);


+ 94
- 0
src/app/login/LoginForm.tsx Vedi File

@@ -0,0 +1,94 @@
"use client";

import { FormHelperText } from "@mui/material";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";

type LoginFields = {
username: string;
password: string;
};

// Error codes in https://next-auth.js.org/configuration/pages#sign-in-page
const getHumanFriendlyErrorMessage = (serverError: string): string => {
switch (serverError) {
case "CredentialsSignin":
return "Invalid username or password.";
case "Default":
default:
return "Something went wrong. Please try again later.";
}
};

const LoginForm: React.FC = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFields>();

const [serverError, setServerError] = useState<string>();

const router = useRouter();

const onSubmit: SubmitHandler<LoginFields> = async (data) => {
const res = await signIn("credentials", {
redirect: false,
...data,
});

if (res?.error) {
setServerError(res.error);
return;
}
const callbackUrl =
new URLSearchParams(window.location.search).get("callbackUrl") || "/";

router.push(callbackUrl);
};

return (
<Stack
spacing={3}
margin={5}
component="form"
onSubmit={handleSubmit(onSubmit)}
>
<Typography variant="h1">Sign In</Typography>
<TextField
label="Username"
{...register("username", { required: "Please enter a username" })}
error={Boolean(errors.username)}
helperText={errors.username?.message}
/>
<TextField
label="Password"
type="password"
{...register("password", { required: "Please enter a password" })}
error={Boolean(errors.password)}
helperText={errors.password?.message}
/>
{serverError && (
<FormHelperText error>
{getHumanFriendlyErrorMessage(serverError)}
</FormHelperText>
)}
<Button
variant="contained"
size="large"
type="submit"
disabled={isSubmitting}
>
Login
</Button>
</Stack>
);
};

export default LoginForm;

+ 28
- 0
src/app/login/page.tsx Vedi File

@@ -0,0 +1,28 @@
import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import LoginForm from "./LoginForm";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@/config/authConfig";

const Login: React.FC = async () => {
const session = await getServerSession(authOptions);
if (session?.user) {
redirect("/");
}

return (
<Grid container height="100vh">
<Grid item sm>
Hero
</Grid>
<Grid item xs={12} sm={8} lg={5}>
<Paper square sx={{ height: "100%" }}>
<LoginForm />
</Paper>
</Grid>
</Grid>
);
};

export default Login;

+ 4
- 2
src/app/page.tsx Vedi File

@@ -1,5 +1,7 @@
const Home: React.FC = () => {
return "home page";
import { permanentRedirect } from "next/navigation";

const Home: React.FC = async () => {
permanentRedirect("/dashboard");
};

export default Home;

+ 2
- 0
src/config/api.ts Vedi File

@@ -0,0 +1,2 @@
export const BASE_API_URL = `${process.env.API_PROTOCOL}://${process.env.API_HOST}:${process.env.API_PORT}`;
export const LOGIN_API_PATH = `${BASE_API_URL}/api/login`;

+ 42
- 0
src/config/authConfig.ts Vedi File

@@ -0,0 +1,42 @@
import { AuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { LOGIN_API_PATH } from "./api";

export const authOptions: AuthOptions = {
debug: process.env.NODE_ENV === "development",
providers: [
CredentialsProvider({
id: "credentials",
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const res = await fetch(LOGIN_API_PATH, {
method: "POST",
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" },
});

const user = await res.json();

if (res.ok && user) {
return user;
}
return null;
},
}),
],
pages: {
signIn: "/login",
},
callbacks: {
jwt(params) {
return params.token;
},
session(params) {
return params.session;
},
},
};

+ 8
- 0
src/middleware.ts Vedi File

@@ -0,0 +1,8 @@
import { withAuth } from "next-auth/middleware";
import { authOptions } from "@/config/authConfig";

export default withAuth({
pages: authOptions.pages,
});

export const config = { matcher: ["/dashboard"] };

+ 100
- 0
src/theme/EmotionCache.tsx Vedi File

@@ -0,0 +1,100 @@
"use client";
import * as React from "react";
import createCache from "@emotion/cache";
import { useServerInsertedHTML } from "next/navigation";
import { CacheProvider as DefaultCacheProvider } from "@emotion/react";
import type {
EmotionCache,
Options as OptionsOfCreateCache,
} from "@emotion/cache";

// Copied from https://github.com/mui/material-ui/blob/master/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/EmotionCache.tsx
export type NextAppDirEmotionCacheProviderProps = {
/** This is the options passed to createCache() from 'import createCache from "@emotion/cache"' */
options: Omit<OptionsOfCreateCache, "insertionPoint">;
/** By default <CacheProvider /> from 'import { CacheProvider } from "@emotion/react"' */
CacheProvider?: (props: {
value: EmotionCache;
children: React.ReactNode;
}) => React.JSX.Element | null;
children: React.ReactNode;
};

// Adapted from https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx
export default function NextAppDirEmotionCacheProvider(
props: NextAppDirEmotionCacheProviderProps,
) {
const { options, CacheProvider = DefaultCacheProvider, children } = props;

const [registry] = React.useState(() => {
const cache = createCache(options);
cache.compat = true;
const prevInsert = cache.insert;
let inserted: { name: string; isGlobal: boolean }[] = [];
cache.insert = (...args) => {
const [selector, serialized] = args;
if (cache.inserted[serialized.name] === undefined) {
inserted.push({
name: serialized.name,
isGlobal: !selector,
});
}
return prevInsert(...args);
};
const flush = () => {
const prevInserted = inserted;
inserted = [];
return prevInserted;
};
return { cache, flush };
});

useServerInsertedHTML(() => {
const inserted = registry.flush();
if (inserted.length === 0) {
return null;
}
let styles = "";
let dataEmotionAttribute = registry.cache.key;

const globals: {
name: string;
style: string;
}[] = [];

inserted.forEach(({ name, isGlobal }) => {
const style = registry.cache.inserted[name];

if (typeof style !== "boolean") {
if (isGlobal) {
globals.push({ name, style });
} else {
styles += style;
dataEmotionAttribute += ` ${name}`;
}
}
});

return (
<React.Fragment>
{globals.map(({ name, style }) => (
<style
key={name}
data-emotion={`${registry.cache.key}-global ${name}`}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style }}
/>
))}
{styles && (
<style
data-emotion={dataEmotionAttribute}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: styles }}
/>
)}
</React.Fragment>
);
});

return <CacheProvider value={registry.cache}>{children}</CacheProvider>;
}

+ 22
- 0
src/theme/ThemeRegistry.tsx Vedi File

@@ -0,0 +1,22 @@
"use client";
import * as React from "react";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import NextAppDirEmotionCacheProvider from "./EmotionCache";
import theme from "./devias-material-kit";

// Copied from https://github.com/mui/material-ui/blob/master/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/ThemeRegistry.tsx
export default function ThemeRegistry({
children,
}: {
children: React.ReactNode;
}) {
return (
<NextAppDirEmotionCacheProvider options={{ key: "mui" }}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</NextAppDirEmotionCacheProvider>
);
}

+ 57
- 0
src/theme/devias-material-kit/colors.ts Vedi File

@@ -0,0 +1,57 @@
export const neutral = {
50: "#F8F9FA",
100: "#F3F4F6",
200: "#E5E7EB",
300: "#D2D6DB",
400: "#9DA4AE",
500: "#6C737F",
600: "#4D5761",
700: "#2F3746",
800: "#1C2536",
900: "#111927",
};

export const indigo = {
lightest: "#F5F7FF",
light: "#EBEEFE",
main: "#6366F1",
dark: "#4338CA",
darkest: "#312E81",
contrastText: "#FFFFFF",
};

export const success = {
lightest: "#F0FDF9",
light: "#3FC79A",
main: "#10B981",
dark: "#0B815A",
darkest: "#134E48",
contrastText: "#FFFFFF",
};

export const info = {
lightest: "#ECFDFF",
light: "#CFF9FE",
main: "#06AED4",
dark: "#0E7090",
darkest: "#164C63",
contrastText: "#FFFFFF",
};

export const warning = {
lightest: "#FFFAEB",
light: "#FEF0C7",
main: "#F79009",
dark: "#B54708",
darkest: "#7A2E0E",
contrastText: "#FFFFFF",
};

export const error = {
lightest: "#FEF3F2",
light: "#FEE4E2",
main: "#F04438",
dark: "#B42318",
darkest: "#7A271A",
contrastText: "#FFFFFF",
};

+ 288
- 0
src/theme/devias-material-kit/components.ts Vedi File

@@ -0,0 +1,288 @@
import { ThemeOptions, createTheme } from "@mui/material";
import palette from "./palette";

// Used only to create transitions
const muiTheme = createTheme();

const components: ThemeOptions["components"] = {
MuiAvatar: {
styleOverrides: {
root: {
fontSize: 14,
fontWeight: 600,
letterSpacing: 0,
},
},
},
MuiButton: {
styleOverrides: {
root: {
borderRadius: "12px",
textTransform: "none",
},
sizeSmall: {
padding: "6px 16px",
},
sizeMedium: {
padding: "8px 20px",
},
sizeLarge: {
padding: "11px 24px",
},
textSizeSmall: {
padding: "7px 12px",
},
textSizeMedium: {
padding: "9px 16px",
},
textSizeLarge: {
padding: "12px 16px",
},
},
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: 20,
[`&.MuiPaper-elevation1`]: {
boxShadow:
"0px 5px 22px rgba(0, 0, 0, 0.04), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.03)",
},
},
},
},
MuiCardContent: {
styleOverrides: {
root: {
padding: "32px 24px",
"&:last-child": {
paddingBottom: "32px",
},
},
},
},
MuiCardHeader: {
defaultProps: {
titleTypographyProps: {
variant: "h6",
},
subheaderTypographyProps: {
variant: "body2",
},
},
styleOverrides: {
root: {
padding: "32px 24px 16px",
},
},
},
MuiCssBaseline: {
styleOverrides: {
"*": {
boxSizing: "border-box",
},
html: {
MozOsxFontSmoothing: "grayscale",
WebkitFontSmoothing: "antialiased",
display: "flex",
flexDirection: "column",
minHeight: "100%",
width: "100%",
},
body: {
display: "flex",
flex: "1 1 auto",
flexDirection: "column",
minHeight: "100%",
width: "100%",
},
"#__next": {
display: "flex",
flex: "1 1 auto",
flexDirection: "column",
height: "100%",
width: "100%",
},
"#nprogress": {
pointerEvents: "none",
},
"#nprogress .bar": {
backgroundColor: palette.primary.main,
height: 3,
left: 0,
position: "fixed",
top: 0,
width: "100%",
zIndex: 2000,
},
},
},
MuiInputBase: {
styleOverrides: {
input: {
"&::placeholder": {
opacity: 1,
},
},
},
},
MuiInput: {
styleOverrides: {
input: {
fontSize: 14,
fontWeight: 500,
lineHeight: "24px",
"&::placeholder": {
color: palette.text.secondary,
},
},
},
},
MuiFilledInput: {
styleOverrides: {
root: {
backgroundColor: "transparent",
borderRadius: 8,
borderStyle: "solid",
borderWidth: 1,
overflow: "hidden",
borderColor: palette.neutral[200],
transition: muiTheme.transitions.create(["border-color", "box-shadow"]),
"&:hover": {
backgroundColor: palette.action.hover,
},
"&:before": {
display: "none",
},
"&:after": {
display: "none",
},
[`&.Mui-disabled`]: {
backgroundColor: "transparent",
},
[`&.Mui-focused`]: {
backgroundColor: "transparent",
borderColor: palette.primary.main,
boxShadow: `${palette.primary.main} 0 0 0 2px`,
},
[`&.Mui-error`]: {
borderColor: palette.error.main,
boxShadow: `${palette.error.main} 0 0 0 2px`,
},
},
input: {
fontSize: 14,
fontWeight: 500,
lineHeight: "24px",
},
},
},
MuiOutlinedInput: {
styleOverrides: {
root: {
"&:hover": {
backgroundColor: palette.action.hover,
[`& .MuiOutlinedInput-notchedOutline`]: {
borderColor: palette.neutral[200],
},
},
[`&.Mui-focused`]: {
backgroundColor: "transparent",
[`& .MuiOutlinedInput-notchedOutline`]: {
borderColor: palette.primary.main,
boxShadow: `${palette.primary.main} 0 0 0 2px`,
},
},
[`&.Mui-error`]: {
[`& .MuiOutlinedInput-notchedOutline`]: {
borderColor: palette.error.main,
boxShadow: `${palette.error.main} 0 0 0 2px`,
},
},
},
input: {
fontSize: 14,
fontWeight: 500,
lineHeight: "24px",
},
notchedOutline: {
borderColor: palette.neutral[200],
transition: muiTheme.transitions.create(["border-color", "box-shadow"]),
},
},
},
MuiFormLabel: {
styleOverrides: {
root: {
fontSize: 14,
fontWeight: 500,
[`&.MuiInputLabel-filled`]: {
transform: "translate(12px, 18px) scale(1)",
},
[`&.MuiInputLabel-shrink`]: {
[`&.MuiInputLabel-standard`]: {
transform: "translate(0, -1.5px) scale(0.85)",
},
[`&.MuiInputLabel-filled`]: {
transform: "translate(12px, 6px) scale(0.85)",
},
[`&.MuiInputLabel-outlined`]: {
transform: "translate(14px, -9px) scale(0.85)",
},
},
},
},
},
MuiTab: {
styleOverrides: {
root: {
fontSize: 14,
fontWeight: 500,
lineHeight: 1.71,
minWidth: "auto",
paddingLeft: 0,
paddingRight: 0,
textTransform: "none",
"& + &": {
marginLeft: 24,
},
},
},
},
MuiTableCell: {
styleOverrides: {
root: {
borderBottomColor: palette.divider,
padding: "15px 16px",
},
},
},
MuiTableHead: {
styleOverrides: {
root: {
borderBottom: "none",
[`& .MuiTableCell-root`]: {
borderBottom: "none",
backgroundColor: palette.neutral[50],
color: palette.neutral[700],
fontSize: 12,
fontWeight: 600,
lineHeight: 1,
letterSpacing: 0.5,
textTransform: "uppercase",
},
[`& .MuiTableCell-paddingCheckbox`]: {
paddingTop: 4,
paddingBottom: 4,
},
},
},
},
MuiTextField: {
defaultProps: {
variant: "filled",
},
},
};

export default components;

+ 26
- 0
src/theme/devias-material-kit/index.ts Vedi File

@@ -0,0 +1,26 @@
import { createTheme } from "@mui/material";
import { paletteOptions as palette } from "./palette";
import components from "./components";
import shadows from "./shadows";
import typography from "./typography";

const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 900,
lg: 1200,
xl: 1440,
},
},
components,
palette,
shadows,
shape: {
borderRadius: 8,
},
typography,
});

export default theme;

+ 35
- 0
src/theme/devias-material-kit/palette.ts Vedi File

@@ -0,0 +1,35 @@
import { common } from "@mui/material/colors";
import { PaletteOptions } from "@mui/material/styles";
import { error, indigo, info, neutral, success, warning } from "./colors";

const palette = {
action: {
active: neutral[500],
disabled: "rgba(17,25,39,0.38)",
disabledBackground: "rgba(17,25,39,0.12)",
focus: "rgba(17,25,39,0.16)",
hover: "rgba(17,25,39,0.04)",
selected: "rgba(17,25,39,0.12)",
},
background: {
default: common.white,
paper: common.white,
},
divider: "#F2F4F7",
error,
info,
mode: "light",
primary: indigo,
success,
text: {
primary: neutral[900],
secondary: neutral[500],
disabled: "rgba(17,25,39,0.38)",
},
warning,
neutral,
};

export const paletteOptions: PaletteOptions = { ...palette, mode: "light" };

export default palette;

+ 31
- 0
src/theme/devias-material-kit/shadows.ts Vedi File

@@ -0,0 +1,31 @@
import { Shadows } from "@mui/material";

const shadows: Shadows = [
"none",
"0px 1px 2px rgba(0, 0, 0, 0.08)",
"0px 1px 5px rgba(0, 0, 0, 0.08)",
"0px 1px 8px rgba(0, 0, 0, 0.08)",
"0px 1px 10px rgba(0, 0, 0, 0.08)",
"0px 1px 14px rgba(0, 0, 0, 0.08)",
"0px 1px 18px rgba(0, 0, 0, 0.08)",
"0px 2px 16px rgba(0, 0, 0, 0.08)",
"0px 3px 14px rgba(0, 0, 0, 0.08)",
"0px 3px 16px rgba(0, 0, 0, 0.08)",
"0px 4px 18px rgba(0, 0, 0, 0.08)",
"0px 4px 20px rgba(0, 0, 0, 0.08)",
"0px 5px 22px rgba(0, 0, 0, 0.08)",
"0px 5px 24px rgba(0, 0, 0, 0.08)",
"0px 5px 26px rgba(0, 0, 0, 0.08)",
"0px 6px 28px rgba(0, 0, 0, 0.08)",
"0px 6px 30px rgba(0, 0, 0, 0.08)",
"0px 6px 32px rgba(0, 0, 0, 0.08)",
"0px 7px 34px rgba(0, 0, 0, 0.08)",
"0px 7px 36px rgba(0, 0, 0, 0.08)",
"0px 8px 38px rgba(0, 0, 0, 0.08)",
"0px 8px 40px rgba(0, 0, 0, 0.08)",
"0px 8px 42px rgba(0, 0, 0, 0.08)",
"0px 9px 44px rgba(0, 0, 0, 0.08)",
"0px 9px 46px rgba(0, 0, 0, 0.08)",
];

export default shadows;

+ 90
- 0
src/theme/devias-material-kit/typography.ts Vedi File

@@ -0,0 +1,90 @@
import { TypographyOptions } from "@mui/material/styles/createTypography";

import "@fontsource/inter/400.css";
import "@fontsource/inter/500.css";
import "@fontsource/inter/600.css";
import "@fontsource/plus-jakarta-sans/700.css";

const typography: TypographyOptions = {
fontFamily:
'"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
body1: {
fontSize: "1rem",
fontWeight: 400,
lineHeight: 1.5,
},
body2: {
fontSize: "0.875rem",
fontWeight: 400,
lineHeight: 1.57,
},
button: {
fontWeight: 600,
},
caption: {
fontSize: "0.75rem",
fontWeight: 500,
lineHeight: 1.66,
},
subtitle1: {
fontSize: "1rem",
fontWeight: 500,
lineHeight: 1.57,
},
subtitle2: {
fontSize: "0.875rem",
fontWeight: 500,
lineHeight: 1.57,
},
overline: {
fontSize: "0.75rem",
fontWeight: 600,
letterSpacing: "0.5px",
lineHeight: 2.5,
textTransform: "uppercase",
},
h1: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
fontWeight: 700,
fontSize: "3.5rem",
lineHeight: 1.2,
},
h2: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
fontWeight: 700,
fontSize: "3rem",
lineHeight: 1.2,
},
h3: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
fontWeight: 700,
fontSize: "2.25rem",
lineHeight: 1.2,
},
h4: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
fontWeight: 700,
fontSize: "2rem",
lineHeight: 1.2,
},
h5: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
fontWeight: 700,
fontSize: "1.5rem",
lineHeight: 1.2,
},
h6: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
fontWeight: 700,
fontSize: "1.125rem",
lineHeight: 1.2,
},
fontSize: 0,
fontWeightLight: undefined,
fontWeightRegular: undefined,
fontWeightMedium: undefined,
fontWeightBold: undefined,
htmlFontSize: 0,
};

export default typography;

Caricamento…
Annulla
Salva