theme.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. import { PaletteMode } from '@mui/material';
  2. import { Theme } from '@mui/material/styles';
  3. import { ButtonProps } from '@mui/material/Button';
  4. declare module '@mui/material/Typography' {
  5. interface TypographyPropsVariantOverrides {
  6. mono: true;
  7. }
  8. }
  9. declare module '@mui/material/styles' {
  10. interface TypeBackground {
  11. lightGrey?: string;
  12. grey?: string;
  13. light?: string;
  14. }
  15. interface Palette {
  16. background: TypeBackground;
  17. neutral: {
  18. 50: string;
  19. 100: string;
  20. 200: string;
  21. 300: string;
  22. 400: string;
  23. 500: string;
  24. 600: string;
  25. 700: string;
  26. 800: string;
  27. 900: string;
  28. };
  29. highlight: {
  30. light: string;
  31. dark: string;
  32. };
  33. }
  34. }
  35. const colors = {
  36. primary: {
  37. main: '#09B572',
  38. light: {
  39. light: '#f0fdf4',
  40. dark: '#1b4332',
  41. },
  42. dark: {
  43. light: '#07925E',
  44. dark: '#067A50',
  45. },
  46. },
  47. secondary: {
  48. main: '#1272CC',
  49. light: {
  50. light: '#E6F4FF',
  51. dark: '#003A8C',
  52. },
  53. dark: {
  54. light: '#0D5AA3',
  55. dark: '#0A4680',
  56. },
  57. },
  58. error: {
  59. main: '#D93C00',
  60. light: {
  61. light: '#FFEBE6',
  62. dark: '#7A1C00',
  63. },
  64. dark: {
  65. light: '#B33200',
  66. dark: '#8A2700',
  67. },
  68. },
  69. warning: {
  70. main: '#FF9800',
  71. light: {
  72. light: '#FFF4E5',
  73. dark: '#7A4A00',
  74. },
  75. dark: {
  76. light: '#E68A00',
  77. dark: '#B36B00',
  78. },
  79. },
  80. info: {
  81. main: '#2196F3',
  82. light: {
  83. light: '#E6F4FF',
  84. dark: '#0D3C61',
  85. },
  86. dark: {
  87. light: '#1A7BC9',
  88. dark: '#145FA0',
  89. },
  90. },
  91. success: {
  92. main: '#4CAF50',
  93. light: {
  94. light: '#E8F5E9',
  95. dark: '#1B5E20',
  96. },
  97. dark: {
  98. light: '#3D8B40',
  99. dark: '#2E7D32',
  100. },
  101. },
  102. neutral: {
  103. 50: '#F9FAFB',
  104. 100: '#F3F4F6',
  105. 200: '#E5E7EB',
  106. 300: '#D1D5DB',
  107. 400: '#9CA3AF',
  108. 500: '#6B7280',
  109. 600: '#4B5563',
  110. 700: '#374151',
  111. 800: '#1F2937',
  112. 900: '#111827',
  113. },
  114. background: {
  115. default: '#F3F4F6',
  116. paper: '#FFFFFF',
  117. },
  118. text: {
  119. primary: '#111827',
  120. secondary: '#4B5563',
  121. disabled: '#9CA3AF',
  122. },
  123. divider: '#E5E7EB',
  124. highlight: {
  125. light: '#FFE082',
  126. dark: '#003A8C',
  127. },
  128. };
  129. const spacing = (factor: number) => `${8 * factor}px`;
  130. const typography = {
  131. fontFamily: [
  132. 'Inter',
  133. '-apple-system',
  134. 'BlinkMacSystemFont',
  135. '"Segoe UI"',
  136. '"Helvetica Neue"',
  137. 'Arial',
  138. 'sans-serif',
  139. '"Apple Color Emoji"',
  140. '"Segoe UI Emoji"',
  141. '"Segoe UI Symbol"',
  142. ].join(','),
  143. h1: {
  144. fontSize: '36px',
  145. lineHeight: '42px',
  146. fontWeight: 'bold',
  147. letterSpacing: '-0.02em',
  148. },
  149. h2: {
  150. lineHeight: '24px',
  151. fontSize: '28px',
  152. fontWeight: 'bold',
  153. },
  154. h3: {
  155. lineHeight: '20px',
  156. fontSize: '24px',
  157. fontWeight: 'bold',
  158. },
  159. h4: {
  160. fontWeight: 500,
  161. lineHeight: '23px',
  162. fontSize: '20px',
  163. letterSpacing: '-0.02em',
  164. },
  165. h5: {
  166. fontWeight: 'bold',
  167. fontSize: '16px',
  168. lineHeight: '24px',
  169. },
  170. h6: {
  171. fontWeight: 'normal',
  172. fontSize: '16px',
  173. lineHeight: '24px',
  174. letterSpacing: '-0.01em',
  175. },
  176. body1: {
  177. fontSize: '14px',
  178. lineHeight: 1.5,
  179. },
  180. body2: {
  181. fontSize: '12px',
  182. lineHeight: '16px',
  183. },
  184. caption: {
  185. fontSize: '10px',
  186. lineHeight: '12px',
  187. },
  188. mono: {
  189. fontFamily: 'IBM Plex Mono, monospace',
  190. fontSize: 13,
  191. lineHeight: 1.8,
  192. },
  193. button: {
  194. textTransform: 'initial',
  195. lineHeight: '16px',
  196. fontWeight: '700',
  197. },
  198. };
  199. const getCommonThemes = (mode: PaletteMode) => ({
  200. typography,
  201. palette: {
  202. mode,
  203. primary: {
  204. main: colors.primary.main,
  205. light:
  206. mode === 'light'
  207. ? colors.primary.light.light
  208. : colors.primary.light.dark,
  209. dark:
  210. mode === 'light' ? colors.primary.dark.light : colors.primary.dark.dark,
  211. },
  212. secondary: {
  213. main: colors.secondary.main,
  214. light:
  215. mode === 'light'
  216. ? colors.secondary.light.light
  217. : colors.secondary.light.dark,
  218. dark:
  219. mode === 'light'
  220. ? colors.secondary.dark.light
  221. : colors.secondary.dark.dark,
  222. },
  223. error: {
  224. main: colors.error.main,
  225. light:
  226. mode === 'light' ? colors.error.light.light : colors.error.light.dark,
  227. dark: mode === 'light' ? colors.error.dark.light : colors.error.dark.dark,
  228. },
  229. warning: {
  230. main: colors.warning.main,
  231. light:
  232. mode === 'light'
  233. ? colors.warning.light.light
  234. : colors.warning.light.dark,
  235. dark:
  236. mode === 'light' ? colors.warning.dark.light : colors.warning.dark.dark,
  237. },
  238. info: {
  239. main: colors.info.main,
  240. light:
  241. mode === 'light' ? colors.info.light.light : colors.info.light.dark,
  242. dark: mode === 'light' ? colors.info.dark.light : colors.info.dark.dark,
  243. },
  244. success: {
  245. main: colors.success.main,
  246. light:
  247. mode === 'light'
  248. ? colors.success.light.light
  249. : colors.success.light.dark,
  250. dark:
  251. mode === 'light' ? colors.success.dark.light : colors.success.dark.dark,
  252. },
  253. neutral: colors.neutral,
  254. background: {
  255. default:
  256. mode === 'light' ? colors.background.default : colors.neutral[900],
  257. paper: mode === 'light' ? colors.background.paper : colors.neutral[800],
  258. grey: mode === 'light' ? colors.neutral[200] : colors.neutral[700],
  259. lightGrey: mode === 'light' ? colors.neutral[100] : colors.neutral[800],
  260. },
  261. text: {
  262. primary: mode === 'light' ? colors.text.primary : colors.neutral[50],
  263. secondary: mode === 'light' ? colors.text.secondary : colors.neutral[400],
  264. disabled: mode === 'light' ? colors.text.disabled : colors.neutral[600],
  265. },
  266. divider: mode === 'light' ? colors.divider : colors.neutral[700],
  267. highlight: {
  268. light: colors.highlight.light,
  269. dark: colors.highlight.dark,
  270. },
  271. },
  272. spacing,
  273. });
  274. export const getAttuTheme = (mode: PaletteMode) => {
  275. const commonThemes = getCommonThemes(mode);
  276. const isLight = mode === 'light';
  277. return {
  278. ...commonThemes,
  279. components: {
  280. MuiTypography: {
  281. styleOverrides: {
  282. ...typography,
  283. },
  284. },
  285. MuiButton: {
  286. defaultProps: {
  287. disableRipple: true,
  288. },
  289. styleOverrides: {
  290. root: ({
  291. theme,
  292. ownerState,
  293. }: {
  294. theme: Theme;
  295. ownerState: ButtonProps;
  296. }) => ({
  297. padding: theme.spacing(1, 3),
  298. textTransform: 'initial',
  299. fontWeight: 'bold',
  300. ...(ownerState.variant === 'text' && {
  301. padding: theme.spacing(1),
  302. color: theme.palette.primary.main,
  303. '&:hover': {
  304. backgroundColor: theme.palette.primary.main,
  305. color: theme.palette.background.paper,
  306. },
  307. }),
  308. ...(ownerState.variant === 'contained' && {
  309. boxShadow: 'none',
  310. '&:hover': {
  311. boxShadow: 'none',
  312. backgroundColor:
  313. ownerState.color === 'secondary'
  314. ? theme.palette.secondary.dark
  315. : theme.palette.primary.dark,
  316. },
  317. }),
  318. ...(ownerState.disabled && {
  319. pointerEvents: 'none',
  320. opacity: 0.6,
  321. }),
  322. }),
  323. },
  324. variants: [
  325. {
  326. props: { variant: 'contained', color: 'primary' },
  327. style: ({ theme }: { theme: Theme }) => ({
  328. backgroundColor: theme.palette.primary.main,
  329. color: theme.palette.background.paper,
  330. }),
  331. },
  332. {
  333. props: { variant: 'contained', color: 'secondary' },
  334. style: ({ theme }: { theme: Theme }) => ({
  335. backgroundColor: theme.palette.secondary.main,
  336. color: theme.palette.background.paper,
  337. }),
  338. },
  339. ],
  340. },
  341. MuiTab: {
  342. defaultProps: {
  343. disableRipple: true,
  344. },
  345. },
  346. MuiMenuItem: {
  347. defaultProps: {
  348. disableRipple: true,
  349. },
  350. styleOverrides: {
  351. root: {
  352. fontSize: '14px',
  353. padding: '8px 16px',
  354. minHeight: '36px',
  355. transition: 'background-color 0.2s ease',
  356. '&:hover': {
  357. backgroundColor: isLight
  358. ? 'rgba(10, 206, 130, 0.08)'
  359. : 'rgba(10, 206, 130, 0.16)',
  360. },
  361. '&.Mui-selected': {
  362. backgroundColor: isLight
  363. ? 'rgba(10, 206, 130, 0.16)'
  364. : 'rgba(10, 206, 130, 0.24)',
  365. '&:hover': {
  366. backgroundColor: isLight
  367. ? 'rgba(10, 206, 130, 0.24)'
  368. : 'rgba(10, 206, 130, 0.32)',
  369. },
  370. },
  371. '&.Mui-disabled': {
  372. opacity: 0.6,
  373. },
  374. '& .MuiListItemIcon-root': {
  375. color: isLight ? '#666' : '#aaa',
  376. minWidth: '36px',
  377. },
  378. },
  379. },
  380. },
  381. MuiDialog: {
  382. styleOverrides: {
  383. paper: {
  384. borderRadius: 8,
  385. boxShadow: '0px 5px 15px rgba(0, 0, 0, 0.15)',
  386. },
  387. },
  388. },
  389. MuiDialogActions: {
  390. styleOverrides: {
  391. spacing: {
  392. padding: spacing(4),
  393. },
  394. },
  395. },
  396. MuiDialogContent: {
  397. styleOverrides: {
  398. root: {
  399. padding: `${spacing(1)} ${spacing(4)}`,
  400. },
  401. },
  402. },
  403. MuiDialogTitle: {
  404. styleOverrides: {
  405. root: {
  406. padding: spacing(4),
  407. paddingBottom: spacing(1),
  408. },
  409. },
  410. },
  411. MuiFormHelperText: {
  412. styleOverrides: {
  413. contained: {
  414. marginLeft: 0,
  415. },
  416. },
  417. },
  418. MuiTextField: {
  419. styleOverrides: {
  420. root: {
  421. '& .MuiOutlinedInput-root': {
  422. '&:hover fieldset': {
  423. borderColor: isLight
  424. ? colors.primary.main
  425. : colors.primary.light.dark,
  426. },
  427. },
  428. },
  429. },
  430. },
  431. MuiFilledInput: {
  432. styleOverrides: {
  433. underline: {
  434. '&:before': {
  435. borderBottom: 'none',
  436. },
  437. borderWidth: 1,
  438. borderColor: 'transparent',
  439. },
  440. },
  441. },
  442. MuiInput: {
  443. styleOverrides: {
  444. underline: {
  445. '&:hover:not(.Mui-disabled):before': {
  446. borderWidth: 1,
  447. },
  448. borderWidth: 1,
  449. borderColor: 'transparent',
  450. },
  451. },
  452. },
  453. MuiChip: {
  454. styleOverrides: {
  455. root: {
  456. backgroundColor: isLight
  457. ? colors.primary.light.light
  458. : colors.primary.light.dark,
  459. color: isLight
  460. ? colors.primary.dark.light
  461. : colors.primary.light.light,
  462. '& .MuiChip-label': {
  463. fontWeight: 500,
  464. },
  465. },
  466. outlined: {
  467. borderColor: isLight
  468. ? colors.primary.main
  469. : colors.primary.light.dark,
  470. },
  471. clickable: {
  472. '&:hover': {
  473. backgroundColor: isLight
  474. ? colors.primary.light.light
  475. : colors.primary.dark.dark,
  476. },
  477. },
  478. deleteIcon: {
  479. color: isLight
  480. ? colors.primary.dark.light
  481. : colors.primary.light.light,
  482. '&:hover': {
  483. color: isLight
  484. ? colors.primary.dark.dark
  485. : colors.primary.light.dark,
  486. },
  487. },
  488. },
  489. },
  490. MuiTreeItem: {
  491. styleOverrides: {
  492. root: {
  493. fontSize: '15px',
  494. color: commonThemes.palette.primary.main,
  495. backgroundColor: commonThemes.palette.background.default,
  496. '& .MuiTreeItem-iconContainer': {
  497. width: 'auto',
  498. color: isLight ? '#666' : '#aaa',
  499. },
  500. '& .MuiTreeItem-group': {
  501. marginLeft: 0,
  502. '& .MuiTreeItem-content': {
  503. padding: '0 0 0 8px',
  504. },
  505. },
  506. '& .MuiTreeItem-label:hover': {
  507. backgroundColor: 'none',
  508. },
  509. '& .MuiTreeItem-content': {
  510. width: 'auto',
  511. padding: '0',
  512. '&.Mui-focused': {
  513. backgroundColor: isLight
  514. ? 'rgba(10, 206, 130, 0.08)'
  515. : 'rgba(10, 206, 130, 0.16)',
  516. },
  517. '&.Mui-selected': {
  518. backgroundColor: isLight
  519. ? 'rgba(10, 206, 130, 0.28)'
  520. : 'rgba(10, 206, 130, 0.36)',
  521. },
  522. '&.Mui-focused.Mui-selected': {
  523. backgroundColor: isLight
  524. ? 'rgba(10, 206, 130, 0.28) !important'
  525. : 'rgba(10, 206, 130, 0.36) !important',
  526. },
  527. '&:hover': {
  528. backgroundColor: isLight
  529. ? 'rgba(10, 206, 130, 0.08)'
  530. : 'rgba(10, 206, 130, 0.16)',
  531. },
  532. '& .MuiTreeItem-label': {
  533. background: 'none',
  534. },
  535. },
  536. },
  537. },
  538. },
  539. MuiMenu: {
  540. styleOverrides: {
  541. paper: {
  542. backgroundColor: commonThemes.palette.background.paper,
  543. boxShadow: '0px 5px 15px rgba(0, 0, 0, 0.15)',
  544. borderRadius: '8px',
  545. },
  546. list: {
  547. padding: '8px 0',
  548. },
  549. },
  550. },
  551. MuiSnackbar: {
  552. styleOverrides: {
  553. root: {
  554. '&.MuiSnackbar-anchorOriginTopCenter': {
  555. top: { xs: 56, md: 72 },
  556. },
  557. '&.MuiSnackbar-anchorOriginTopRight': {
  558. top: { xs: 56, md: 72 },
  559. right: (theme: Theme) => theme.spacing(1),
  560. },
  561. },
  562. },
  563. },
  564. MuiAlert: {
  565. styleOverrides: {
  566. root: {
  567. padding: '0 12px',
  568. borderRadius: '4px',
  569. display: 'flex',
  570. alignItems: 'center',
  571. color: '#fff',
  572. gap: '4px',
  573. '& .MuiAlert-action': {
  574. padding: '0',
  575. },
  576. '& .MuiAlert-icon': {
  577. marginRight: '4px',
  578. },
  579. '& svg': {
  580. fontSize: '16px',
  581. },
  582. },
  583. },
  584. },
  585. },
  586. };
  587. };
  588. export default getAttuTheme;