ソースを参照

Add example login page and MUI theme

tags/Baseline_30082024_FRONTEND_UAT
Wayne Lee 1年前
コミット
150c6a6dc6
20個のファイルの変更983行の追加19行の削除
  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 ファイルの表示

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

+ 134
- 6
package-lock.json ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

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

export default Dashboard;

+ 2
- 8
src/app/layout.tsx ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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;

読み込み中…
キャンセル
保存