├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── eslint.config.mjs ├── index.html ├── package-lock.json ├── package.json ├── prettier.config.mjs ├── public ├── assets │ ├── background │ │ ├── overlay.jpg │ │ └── shape-square.svg │ ├── icons │ │ ├── flags │ │ │ ├── ic-flag-de.svg │ │ │ ├── ic-flag-en.svg │ │ │ └── ic-flag-fr.svg │ │ ├── glass │ │ │ ├── ic-glass-bag.svg │ │ │ ├── ic-glass-buy.svg │ │ │ ├── ic-glass-message.svg │ │ │ └── ic-glass-users.svg │ │ ├── navbar │ │ │ ├── ic-analytics.svg │ │ │ ├── ic-blog.svg │ │ │ ├── ic-cart.svg │ │ │ ├── ic-disabled.svg │ │ │ ├── ic-lock.svg │ │ │ └── ic-user.svg │ │ ├── notification │ │ │ ├── ic-notification-chat.svg │ │ │ ├── ic-notification-mail.svg │ │ │ ├── ic-notification-package.svg │ │ │ └── ic-notification-shipping.svg │ │ ├── shape-avatar.svg │ │ └── workspaces │ │ │ ├── logo-1.webp │ │ │ ├── logo-2.webp │ │ │ └── logo-3.webp │ ├── illustrations │ │ ├── illustration-404.svg │ │ └── illustration-dashboard.webp │ └── images │ │ ├── avatar │ │ ├── avatar-1.webp │ │ ├── avatar-10.webp │ │ ├── avatar-11.webp │ │ ├── avatar-12.webp │ │ ├── avatar-13.webp │ │ ├── avatar-14.webp │ │ ├── avatar-15.webp │ │ ├── avatar-16.webp │ │ ├── avatar-17.webp │ │ ├── avatar-18.webp │ │ ├── avatar-19.webp │ │ ├── avatar-2.webp │ │ ├── avatar-20.webp │ │ ├── avatar-21.webp │ │ ├── avatar-22.webp │ │ ├── avatar-23.webp │ │ ├── avatar-24.webp │ │ ├── avatar-25.webp │ │ ├── avatar-3.webp │ │ ├── avatar-4.webp │ │ ├── avatar-5.webp │ │ ├── avatar-6.webp │ │ ├── avatar-7.webp │ │ ├── avatar-8.webp │ │ └── avatar-9.webp │ │ ├── cover │ │ ├── cover-1.webp │ │ ├── cover-10.webp │ │ ├── cover-11.webp │ │ ├── cover-12.webp │ │ ├── cover-13.webp │ │ ├── cover-14.webp │ │ ├── cover-15.webp │ │ ├── cover-16.webp │ │ ├── cover-17.webp │ │ ├── cover-18.webp │ │ ├── cover-19.webp │ │ ├── cover-2.webp │ │ ├── cover-20.webp │ │ ├── cover-21.webp │ │ ├── cover-22.webp │ │ ├── cover-23.webp │ │ ├── cover-24.webp │ │ ├── cover-3.webp │ │ ├── cover-4.webp │ │ ├── cover-5.webp │ │ ├── cover-6.webp │ │ ├── cover-7.webp │ │ ├── cover-8.webp │ │ └── cover-9.webp │ │ ├── minimal-free-preview.jpg │ │ └── product │ │ ├── product-1.webp │ │ ├── product-10.webp │ │ ├── product-11.webp │ │ ├── product-12.webp │ │ ├── product-13.webp │ │ ├── product-14.webp │ │ ├── product-15.webp │ │ ├── product-16.webp │ │ ├── product-17.webp │ │ ├── product-18.webp │ │ ├── product-19.webp │ │ ├── product-2.webp │ │ ├── product-20.webp │ │ ├── product-21.webp │ │ ├── product-22.webp │ │ ├── product-23.webp │ │ ├── product-24.webp │ │ ├── product-3.webp │ │ ├── product-4.webp │ │ ├── product-5.webp │ │ ├── product-6.webp │ │ ├── product-7.webp │ │ ├── product-8.webp │ │ └── product-9.webp └── favicon.ico ├── src ├── _mock │ ├── _data.ts │ ├── _mock.ts │ └── index.ts ├── app.tsx ├── components │ ├── chart │ │ ├── chart.tsx │ │ ├── classes.ts │ │ ├── components │ │ │ ├── chart-legends.tsx │ │ │ ├── chart-loading.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── styles.css │ │ ├── types.ts │ │ └── use-chart.ts │ ├── color-utils │ │ ├── classes.ts │ │ ├── color-picker.tsx │ │ ├── color-preview.tsx │ │ └── index.ts │ ├── iconify │ │ ├── classes.ts │ │ ├── icon-sets.ts │ │ ├── iconify.tsx │ │ ├── index.ts │ │ └── register-icons.ts │ ├── label │ │ ├── classes.ts │ │ ├── index.ts │ │ ├── label.tsx │ │ ├── styles.tsx │ │ └── types.ts │ ├── logo │ │ ├── classes.ts │ │ ├── index.ts │ │ └── logo.tsx │ ├── scrollbar │ │ ├── classes.ts │ │ ├── index.ts │ │ ├── scrollbar.tsx │ │ ├── styles.css │ │ └── types.ts │ └── svg-color │ │ ├── classes.ts │ │ ├── index.ts │ │ ├── svg-color.tsx │ │ └── types.ts ├── config-global.ts ├── global.css ├── layouts │ ├── auth │ │ ├── content.tsx │ │ ├── index.ts │ │ └── layout.tsx │ ├── components │ │ ├── account-popover.tsx │ │ ├── language-popover.tsx │ │ ├── menu-button.tsx │ │ ├── nav-upgrade.tsx │ │ ├── notifications-popover.tsx │ │ ├── searchbar.tsx │ │ └── workspaces-popover.tsx │ ├── core │ │ ├── classes.ts │ │ ├── css-vars.ts │ │ ├── header-section.tsx │ │ ├── index.ts │ │ ├── layout-section.tsx │ │ └── main-section.tsx │ ├── dashboard │ │ ├── content.tsx │ │ ├── css-vars.ts │ │ ├── index.ts │ │ ├── layout.tsx │ │ └── nav.tsx │ ├── nav-config-account.tsx │ ├── nav-config-dashboard.tsx │ └── nav-config-workspace.tsx ├── main.tsx ├── pages │ ├── blog.tsx │ ├── dashboard.tsx │ ├── page-not-found.tsx │ ├── products.tsx │ ├── sign-in.tsx │ └── user.tsx ├── routes │ ├── components │ │ ├── error-boundary.tsx │ │ ├── index.ts │ │ └── router-link.tsx │ ├── hooks │ │ ├── index.ts │ │ ├── use-pathname.ts │ │ └── use-router.ts │ └── sections.tsx ├── sections │ ├── auth │ │ ├── index.ts │ │ └── sign-in-view.tsx │ ├── blog │ │ ├── post-item.tsx │ │ ├── post-search.tsx │ │ ├── post-sort.tsx │ │ └── view │ │ │ ├── blog-view.tsx │ │ │ └── index.ts │ ├── error │ │ ├── index.ts │ │ └── not-found-view.tsx │ ├── overview │ │ ├── analytics-conversion-rates.tsx │ │ ├── analytics-current-subject.tsx │ │ ├── analytics-current-visits.tsx │ │ ├── analytics-news.tsx │ │ ├── analytics-order-timeline.tsx │ │ ├── analytics-tasks.tsx │ │ ├── analytics-traffic-by-site.tsx │ │ ├── analytics-website-visits.tsx │ │ ├── analytics-widget-summary.tsx │ │ └── view │ │ │ ├── index.ts │ │ │ └── overview-analytics-view.tsx │ ├── product │ │ ├── product-cart-widget.tsx │ │ ├── product-filters.tsx │ │ ├── product-item.tsx │ │ ├── product-sort.tsx │ │ └── view │ │ │ ├── index.ts │ │ │ └── products-view.tsx │ └── user │ │ ├── table-empty-rows.tsx │ │ ├── table-no-data.tsx │ │ ├── user-table-head.tsx │ │ ├── user-table-row.tsx │ │ ├── user-table-toolbar.tsx │ │ ├── utils.ts │ │ └── view │ │ ├── index.ts │ │ └── user-view.tsx ├── theme │ ├── core │ │ ├── components.tsx │ │ ├── custom-shadows.ts │ │ ├── index.ts │ │ ├── palette.ts │ │ ├── shadows.ts │ │ └── typography.ts │ ├── create-classes.ts │ ├── create-theme.ts │ ├── extend-theme-types.d.ts │ ├── index.ts │ ├── theme-config.ts │ ├── theme-provider.tsx │ └── types.ts ├── utils │ ├── format-number.ts │ └── format-time.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json ├── vite.config.ts └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # dependencies 9 | node_modules 10 | .pnp 11 | .pnp.js 12 | 13 | # testing 14 | coverage 15 | 16 | # production 17 | .next 18 | .swc 19 | _static 20 | out 21 | dist 22 | build 23 | 24 | # environment variables 25 | .env 26 | .env.local 27 | .env.development.local 28 | .env.test.local 29 | .env.production.local 30 | 31 | # misc 32 | .DS_Store 33 | .vercel 34 | .netlify 35 | .vscode 36 | tsconfig.tsbuildinfo 37 | .ncurc.js 38 | knip.jsonc 39 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build/* 3 | dist/* 4 | public/* 5 | **/out/* 6 | **/.next/* 7 | **/node_modules/* 8 | 9 | yarn.lock 10 | package-lock.json 11 | jsconfig.json 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v3.0.0 2 | 3 | ###### Apr 3, 2025 4 | 5 | - Support MUI v7. 6 | - Support React v19. 7 | - Support Eslint v9. 8 | - Upgrade and restructure the directory. 9 | - Upgrade some dependencies to the latest versions. 10 | 11 | --- 12 | 13 | ### v2.0.0 14 | 15 | ###### Aug 24, 2024 16 | 17 | - [New] Migrate to typescript. 18 | - Upgrade and restructure the directory. 19 | - Upgrade some dependencies to the latest versions. 20 | 21 | --- 22 | 23 | ### v1.8.0 24 | 25 | ###### Wed 11, 2023 26 | 27 | - [New] Migrate to vite.js. 28 | - Upgrade and restructure the directory. 29 | - Upgrade some dependencies to the latest versions 30 | 31 | --- 32 | 33 | ### v1.7.0 34 | 35 | ###### Feb 21, 2023 36 | 37 | - Upgrade some dependencies to the latest versions 38 | 39 | --- 40 | 41 | ### v1.6.0 42 | 43 | ###### Oct 17, 2022 44 | 45 | - Upgrade and restructure the directory. 46 | - Upgrade some dependencies to the latest versions 47 | 48 | --- 49 | 50 | ### v1.5.0 51 | 52 | ###### Jul 04, 2022 53 | 54 | - Support react 18. 55 | - Upgrade some dependencies to the latest versions 56 | 57 | --- 58 | 59 | ### v1.4.0 60 | 61 | ###### Apr 12, 2022 62 | 63 | - Update `src/components`. 64 | - Update `src/sections`. 65 | - Update `src/pages`. 66 | - Update `src/layouts`. 67 | - Update `src/theme`. 68 | - Upgrade some dependencies to the latest versions 69 | 70 | --- 71 | 72 | ### v1.3.0 73 | 74 | ###### Feb 21, 2022 75 | 76 | - Support react-script v5.0.0 77 | - Source code improvement 78 | - Upgrade some dependencies to the latest versions 79 | 80 | --- 81 | 82 | ### v1.2.0 83 | 84 | ###### Sep 18, 2021 85 | 86 | - Support MIU v5.0.0 official release 87 | - Upgrade some dependencies to the latest versions 88 | - Update `src/theme/typography.js` 89 | - Upgrade some dependencies to the latest versions 90 | 91 | --- 92 | 93 | ### v1.1.0 94 | 95 | ###### Jul 23, 2021 96 | 97 | - Support MUI v5.0.0-beta.1 98 | - Upgrade some dependencies to the latest versions 99 | 100 | --- 101 | 102 | ### v1.0.0 103 | 104 | ###### Jun 28, 2021 105 | 106 | Initial release. 107 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Minimal UI ([https://minimals.cc/](https://minimals.cc/)) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Minimal UI ([Free version](https://free.minimals.cc/)) 2 | 3 | ![license](https://img.shields.io/badge/license-MIT-blue.svg) 4 | 5 | ![preview](public/assets/images/minimal-free-preview.jpg) 6 | 7 | > Free React Admin Dashboard made with Material-UI components and React + Vite.js. 8 | 9 | ## Pages 10 | 11 | - [Dashboard](https://free.minimals.cc/) 12 | - [Users](https://free.minimals.cc/user) 13 | - [Products](https://free.minimals.cc/products) 14 | - [Blog](https://free.minimals.cc/blog) 15 | - [Sign in](https://free.minimals.cc/sign-in) 16 | - [Not found](https://free.minimals.cc/404) 17 | 18 | ## Quick start 19 | 20 | - Clone the repo: `git clone https://github.com/minimal-ui-kit/material-kit-react.git` 21 | - Recommended: `Node.js v20.x` 22 | - **Install:** `npm i` or `yarn install` 23 | - **Start:** `npm run dev` or `yarn dev` 24 | - **Build:** `npm run build` or `yarn build` 25 | - Open browser: `http://localhost:3039` 26 | 27 | ## Upgrade to PRO Version 28 | 29 | | Minimal Free | [Minimal Pro](https://material-ui.com/store/items/minimal-dashboard/) | 30 | | :-------------------------- | :------------------------------------------------------------------------------------------------------ | 31 | | **6** Pages | **70+** Pages | 32 | | **Partial** theme customize | **Fully** theme customize | 33 | | - | **Next.js** version | 34 | | - | **TypeScript** version (Standard Plus and Extended license) | 35 | | - | Design **Figma** file (Standard Plus and Extended license) | 36 | | - | Authentication with **Amplify**, **Auth0**, **JWT**, **Firebase** and **Supabase** | 37 | | - | Light/dark mode, right-to-left, form validation... ([+more components](https://minimals.cc/components)) | 38 | | - | Complete users flows | 39 | | - | 1 year of free updates / 6 months of technical support | 40 | | - | Learn more: [Package & license](https://docs.minimals.cc/package) | 41 | 42 | ## License 43 | 44 | Distributed under the [MIT](https://github.com/minimal-ui-kit/minimal.free/blob/main/LICENSE.md) license. 45 | 46 | ## Contact us 47 | 48 | Email: support@minimals.cc 49 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Minimal UI Kit 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@minimal/material-kit-react", 3 | "author": "minimals.cc", 4 | "licence": "MIT", 5 | "version": "3.0.0", 6 | "private": false, 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite", 10 | "start": "vite preview", 11 | "build": "tsc && vite build", 12 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"", 13 | "lint:fix": "eslint --fix \"src/**/*.{js,jsx,ts,tsx}\"", 14 | "lint:print": "npx eslint --print-config eslint.config.mjs > eslint-show-config.json", 15 | "fm:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", 16 | "fm:fix": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", 17 | "fix:all": "npm run lint:fix && npm run fm:fix", 18 | "clean": "rm -rf node_modules .next out dist build", 19 | "re:dev": "yarn clean && yarn install && yarn dev", 20 | "re:build": "yarn clean && yarn install && yarn build", 21 | "re:build-npm": "npm run clean && npm install && npm run build", 22 | "tsc:dev": "yarn dev & yarn tsc:watch", 23 | "tsc:watch": "tsc --noEmit --watch", 24 | "tsc:print": "npx tsc --showConfig" 25 | }, 26 | "engines": { 27 | "node": ">=20" 28 | }, 29 | "packageManager": "yarn@1.22.22", 30 | "dependencies": { 31 | "@emotion/cache": "^11.14.0", 32 | "@emotion/react": "^11.14.0", 33 | "@emotion/styled": "^11.14.0", 34 | "@fontsource-variable/dm-sans": "^5.2.5", 35 | "@fontsource/barlow": "^5.2.5", 36 | "@iconify/react": "^5.2.1", 37 | "@mui/lab": "^7.0.0-beta.10", 38 | "@mui/material": "^7.0.1", 39 | "apexcharts": "^4.5.0", 40 | "dayjs": "^1.11.13", 41 | "es-toolkit": "^1.34.1", 42 | "minimal-shared": "^1.0.7", 43 | "react": "^19.1.0", 44 | "react-apexcharts": "^1.7.0", 45 | "react-dom": "^19.1.0", 46 | "react-router-dom": "^7.4.1", 47 | "simplebar-react": "^3.3.0" 48 | }, 49 | "devDependencies": { 50 | "@eslint/js": "^9.23.0", 51 | "@types/node": "^22.14.0", 52 | "@types/react": "^19.1.0", 53 | "@types/react-dom": "^19.1.1", 54 | "@typescript-eslint/parser": "^8.29.0", 55 | "@vitejs/plugin-react-swc": "^3.8.1", 56 | "eslint": "^9.23.0", 57 | "eslint-import-resolver-typescript": "^4.3.1", 58 | "eslint-plugin-import": "^2.31.0", 59 | "eslint-plugin-perfectionist": "^4.11.0", 60 | "eslint-plugin-react": "^7.37.4", 61 | "eslint-plugin-react-hooks": "^5.2.0", 62 | "eslint-plugin-unused-imports": "^4.1.4", 63 | "globals": "^16.0.0", 64 | "prettier": "^3.5.3", 65 | "typescript": "^5.8.2", 66 | "typescript-eslint": "^8.29.0", 67 | "vite": "^6.2.5", 68 | "vite-plugin-checker": "^0.9.1" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import("prettier").Config} 3 | * Need to restart IDE when changing configuration 4 | * Open the command palette (Ctrl + Shift + P) and execute the command > Reload Window. 5 | */ 6 | const config = { 7 | semi: true, 8 | tabWidth: 2, 9 | endOfLine: 'lf', 10 | printWidth: 100, 11 | singleQuote: true, 12 | trailingComma: 'es5', 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /public/assets/background/overlay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/background/overlay.jpg -------------------------------------------------------------------------------- /public/assets/icons/flags/ic-flag-de.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/flags/ic-flag-en.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/flags/ic-flag-fr.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/glass/ic-glass-message.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/assets/icons/glass/ic-glass-users.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /public/assets/icons/navbar/ic-analytics.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/assets/icons/navbar/ic-blog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/icons/navbar/ic-cart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/assets/icons/navbar/ic-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/icons/navbar/ic-lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/icons/navbar/ic-user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/icons/notification/ic-notification-chat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/notification/ic-notification-mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/notification/ic-notification-package.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/notification/ic-notification-shipping.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/shape-avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icons/workspaces/logo-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/icons/workspaces/logo-1.webp -------------------------------------------------------------------------------- /public/assets/icons/workspaces/logo-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/icons/workspaces/logo-2.webp -------------------------------------------------------------------------------- /public/assets/icons/workspaces/logo-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/icons/workspaces/logo-3.webp -------------------------------------------------------------------------------- /public/assets/illustrations/illustration-404.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 14 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/assets/illustrations/illustration-dashboard.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/illustrations/illustration-dashboard.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-1.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-10.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-11.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-12.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-13.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-14.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-15.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-16.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-17.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-18.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-19.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-2.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-20.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-20.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-21.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-21.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-22.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-23.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-23.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-24.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-24.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-25.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-25.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-3.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-4.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-5.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-6.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-7.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-8.webp -------------------------------------------------------------------------------- /public/assets/images/avatar/avatar-9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/avatar/avatar-9.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-1.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-10.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-11.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-12.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-13.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-14.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-15.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-16.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-17.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-18.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-19.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-2.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-20.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-20.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-21.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-21.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-22.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-23.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-23.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-24.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-24.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-3.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-4.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-5.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-6.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-7.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-8.webp -------------------------------------------------------------------------------- /public/assets/images/cover/cover-9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/cover/cover-9.webp -------------------------------------------------------------------------------- /public/assets/images/minimal-free-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/minimal-free-preview.jpg -------------------------------------------------------------------------------- /public/assets/images/product/product-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-1.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-10.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-11.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-12.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-13.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-14.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-15.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-16.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-17.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-18.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-19.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-2.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-20.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-20.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-21.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-21.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-22.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-23.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-23.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-24.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-24.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-3.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-4.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-5.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-6.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-7.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-8.webp -------------------------------------------------------------------------------- /public/assets/images/product/product-9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/assets/images/product/product-9.webp -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minimal-ui-kit/material-kit-react/69e780a40c455d664b2e13c525faf1d492a072a7/public/favicon.ico -------------------------------------------------------------------------------- /src/_mock/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_mock'; 2 | export * from './_data'; 3 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import 'src/global.css'; 2 | 3 | import { useEffect } from 'react'; 4 | 5 | import Fab from '@mui/material/Fab'; 6 | 7 | import { usePathname } from 'src/routes/hooks'; 8 | 9 | import { ThemeProvider } from 'src/theme/theme-provider'; 10 | 11 | import { Iconify } from 'src/components/iconify'; 12 | 13 | // ---------------------------------------------------------------------- 14 | 15 | type AppProps = { 16 | children: React.ReactNode; 17 | }; 18 | 19 | export default function App({ children }: AppProps) { 20 | useScrollToTop(); 21 | 22 | const githubButton = () => ( 23 | 37 | 38 | 39 | ); 40 | 41 | return ( 42 | 43 | {children} 44 | {githubButton()} 45 | 46 | ); 47 | } 48 | 49 | // ---------------------------------------------------------------------- 50 | 51 | function useScrollToTop() { 52 | const pathname = usePathname(); 53 | 54 | useEffect(() => { 55 | window.scrollTo(0, 0); 56 | }, [pathname]); 57 | 58 | return null; 59 | } 60 | -------------------------------------------------------------------------------- /src/components/chart/chart.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, Suspense } from 'react'; 2 | import { useIsClient } from 'minimal-shared/hooks'; 3 | import { mergeClasses } from 'minimal-shared/utils'; 4 | 5 | import { styled } from '@mui/material/styles'; 6 | 7 | import { chartClasses } from './classes'; 8 | import { ChartLoading } from './components'; 9 | 10 | import type { ChartProps } from './types'; 11 | 12 | // ---------------------------------------------------------------------- 13 | 14 | const LazyChart = lazy(() => 15 | import('react-apexcharts').then((module) => ({ default: module.default })) 16 | ); 17 | 18 | export function Chart({ type, series, options, slotProps, className, sx, ...other }: ChartProps) { 19 | const isClient = useIsClient(); 20 | 21 | const renderFallback = () => ; 22 | 23 | return ( 24 | 30 | {isClient ? ( 31 | 32 | 33 | 34 | ) : ( 35 | renderFallback() 36 | )} 37 | 38 | ); 39 | } 40 | 41 | // ---------------------------------------------------------------------- 42 | 43 | const ChartRoot = styled('div')(({ theme }) => ({ 44 | width: '100%', 45 | flexShrink: 0, 46 | position: 'relative', 47 | borderRadius: theme.shape.borderRadius * 1.5, 48 | })); 49 | -------------------------------------------------------------------------------- /src/components/chart/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const chartClasses = { 6 | root: createClasses('chart__root'), 7 | loading: createClasses('chart__loading'), 8 | legends: { 9 | root: createClasses('chart__legends__root'), 10 | item: { 11 | wrap: createClasses('chart__legends__item__wrap'), 12 | root: createClasses('chart__legends__item__root'), 13 | dot: createClasses('chart__legends__item__dot'), 14 | icon: createClasses('chart__legends__item__icon'), 15 | label: createClasses('chart__legends__item__label'), 16 | value: createClasses('chart__legends__item__value'), 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/chart/components/chart-legends.tsx: -------------------------------------------------------------------------------- 1 | import { mergeClasses } from 'minimal-shared/utils'; 2 | 3 | import { styled } from '@mui/material/styles'; 4 | 5 | import { chartClasses } from '../classes'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | export type ChartLegendsProps = React.ComponentProps & { 10 | labels?: string[]; 11 | colors?: string[]; 12 | values?: string[]; 13 | sublabels?: string[]; 14 | icons?: React.ReactNode[]; 15 | slotProps?: { 16 | wrapper?: React.ComponentProps; 17 | root?: React.ComponentProps; 18 | dot?: React.ComponentProps; 19 | icon?: React.ComponentProps; 20 | value?: React.ComponentProps; 21 | label?: React.ComponentProps; 22 | }; 23 | }; 24 | 25 | export function ChartLegends({ 26 | sx, 27 | className, 28 | slotProps, 29 | icons = [], 30 | values = [], 31 | labels = [], 32 | colors = [], 33 | sublabels = [], 34 | ...other 35 | }: ChartLegendsProps) { 36 | return ( 37 | 38 | {labels.map((series, index) => ( 39 | 52 | 53 | {icons.length ? ( 54 | 55 | {icons[index]} 56 | 57 | ) : ( 58 | 59 | )} 60 | 61 | 62 | {series} 63 | {!!sublabels.length && <> {` (${sublabels[index]})`}} 64 | 65 | 66 | 67 | {values && ( 68 | 69 | {values[index]} 70 | 71 | )} 72 | 73 | ))} 74 | 75 | ); 76 | } 77 | 78 | // ---------------------------------------------------------------------- 79 | 80 | const ListRoot = styled('ul')(({ theme }) => ({ 81 | display: 'flex', 82 | flexWrap: 'wrap', 83 | gap: theme.spacing(2), 84 | })); 85 | 86 | const ItemWrap = styled('li')(() => ({ 87 | display: 'inline-flex', 88 | flexDirection: 'column', 89 | })); 90 | 91 | const ItemRoot = styled('div')(({ theme }) => ({ 92 | gap: 6, 93 | alignItems: 'center', 94 | display: 'inline-flex', 95 | justifyContent: 'flex-start', 96 | fontSize: theme.typography.pxToRem(13), 97 | fontWeight: theme.typography.fontWeightMedium, 98 | })); 99 | 100 | const ItemIcon = styled('span')({ 101 | display: 'inline-flex', 102 | color: 'var(--icon-color)', 103 | /** 104 | * As ':first-child' for ssr 105 | * https://github.com/emotion-js/emotion/issues/1105#issuecomment-1126025608 106 | */ 107 | '& > :first-of-type:not(style):not(:first-of-type ~ *), & > style + *': { width: 20, height: 20 }, 108 | }); 109 | 110 | const ItemDot = styled('span')({ 111 | width: 12, 112 | height: 12, 113 | flexShrink: 0, 114 | display: 'flex', 115 | borderRadius: '50%', 116 | position: 'relative', 117 | alignItems: 'center', 118 | justifyContent: 'center', 119 | color: 'var(--icon-color)', 120 | backgroundColor: 'currentColor', 121 | }); 122 | 123 | const ItemLabel = styled('span')({ flexShrink: 0 }); 124 | 125 | const ItemValue = styled('span')(({ theme }) => ({ 126 | ...theme.typography.h6, 127 | marginTop: theme.spacing(1), 128 | })); 129 | -------------------------------------------------------------------------------- /src/components/chart/components/chart-loading.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps } from '@mui/material/Box'; 2 | 3 | import { mergeClasses } from 'minimal-shared/utils'; 4 | 5 | import Box from '@mui/material/Box'; 6 | import Skeleton from '@mui/material/Skeleton'; 7 | 8 | import { chartClasses } from '../classes'; 9 | 10 | import type { ChartProps } from '../types'; 11 | 12 | // ---------------------------------------------------------------------- 13 | 14 | export type ChartLoadingProps = BoxProps & Pick; 15 | 16 | export function ChartLoading({ sx, className, type, ...other }: ChartLoadingProps) { 17 | const circularTypes: ChartProps['type'][] = ['donut', 'radialBar', 'pie', 'polarArea']; 18 | 19 | return ( 20 | ({ 24 | top: 0, 25 | left: 0, 26 | width: 1, 27 | zIndex: 9, 28 | height: 1, 29 | p: 'inherit', 30 | overflow: 'hidden', 31 | alignItems: 'center', 32 | position: 'absolute', 33 | borderRadius: 'inherit', 34 | justifyContent: 'center', 35 | }), 36 | ...(Array.isArray(sx) ? sx : [sx]), 37 | ]} 38 | {...other} 39 | > 40 | 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/components/chart/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './chart-legends'; 2 | 3 | export * from './chart-loading'; 4 | -------------------------------------------------------------------------------- /src/components/chart/index.ts: -------------------------------------------------------------------------------- 1 | export * from './chart'; 2 | 3 | export * from './use-chart'; 4 | 5 | export * from './components'; 6 | 7 | export type * from './types'; 8 | -------------------------------------------------------------------------------- /src/components/chart/styles.css: -------------------------------------------------------------------------------- 1 | .apexcharts-canvas { 2 | /** 3 | * Tooltip 4 | */ 5 | .apexcharts-tooltip { 6 | min-width: 80px; 7 | border-radius: 10px; 8 | backdrop-filter: blur(6px); 9 | color: var(--palette-text-primary); 10 | box-shadow: var(--customShadows-dropdown); 11 | background-color: rgba(var(--palette-background-defaultChannel) / 0.9); 12 | } 13 | .apexcharts-xaxistooltip { 14 | border-radius: 10px; 15 | border-color: transparent; 16 | backdrop-filter: blur(6px); 17 | color: var(--palette-text-primary); 18 | box-shadow: var(--customShadows-dropdown); 19 | background-color: rgba(var(--palette-background-defaultChannel) / 0.9); 20 | &::before { 21 | border-bottom-color: rgba(var(--palette-grey-500Channel) / 0.16); 22 | } 23 | &::after { 24 | border-bottom-color: rgba(var(--palette-background-defaultChannel) / 0.9); 25 | } 26 | } 27 | .apexcharts-tooltip-title { 28 | font-weight: 700; 29 | text-align: center; 30 | color: var(--palette-text-secondary); 31 | background-color: var(--palette-background-neutral); 32 | } 33 | /** 34 | * Tooltip: group 35 | */ 36 | .apexcharts-tooltip-series-group { 37 | padding: 4px 12px; 38 | } 39 | .apexcharts-tooltip-marker { 40 | margin-right: 8px; 41 | } 42 | /** 43 | * Legend 44 | */ 45 | .apexcharts-legend { 46 | padding: 0; 47 | } 48 | .apexcharts-legend-marker { 49 | margin-right: 6px; 50 | } 51 | .apexcharts-legend-text { 52 | margin-left: 0; 53 | padding-left: 0; 54 | line-height: 18px; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/chart/types.ts: -------------------------------------------------------------------------------- 1 | import type { Theme, SxProps } from '@mui/material/styles'; 2 | import type { Props as ApexProps } from 'react-apexcharts'; 3 | 4 | // ---------------------------------------------------------------------- 5 | 6 | export type ChartOptions = ApexProps['options']; 7 | 8 | export type ChartProps = React.ComponentProps<'div'> & 9 | Pick & { 10 | sx?: SxProps; 11 | slotProps?: { 12 | loading?: SxProps; 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/color-utils/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const colorPreviewClasses = { 6 | root: createClasses('color__preview__root'), 7 | item: createClasses('color__preview__item'), 8 | label: createClasses('color__preview__label'), 9 | }; 10 | 11 | export const colorPickerClasses = { 12 | root: createClasses('color__picker__root'), 13 | item: { 14 | root: createClasses('color__picker__item__root'), 15 | container: createClasses('color__picker__item__container'), 16 | icon: createClasses('color__picker__item__icon'), 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/color-utils/color-preview.tsx: -------------------------------------------------------------------------------- 1 | import { varAlpha, mergeClasses } from 'minimal-shared/utils'; 2 | 3 | import { styled } from '@mui/material/styles'; 4 | 5 | import { colorPreviewClasses } from './classes'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | export type ColorPreviewSlotProps = { 10 | item?: React.ComponentProps; 11 | label?: React.ComponentProps; 12 | }; 13 | 14 | export type ColorPreviewProps = React.ComponentProps & { 15 | limit?: number; 16 | size?: number; 17 | gap?: number; 18 | colors: string[]; 19 | slotProps?: ColorPreviewSlotProps; 20 | }; 21 | 22 | export function ColorPreview({ 23 | sx, 24 | colors, 25 | className, 26 | slotProps, 27 | gap = 6, 28 | limit = 3, 29 | size = 16, 30 | ...other 31 | }: ColorPreviewProps) { 32 | const colorsRange = colors.slice(0, limit); 33 | const remainingColorCount = colors.length - limit; 34 | 35 | return ( 36 | 41 | {colorsRange.map((color, index) => ( 42 | 57 | ))} 58 | 59 | {colors.length > limit && ( 60 | {`+${remainingColorCount}`} 64 | )} 65 | 66 | ); 67 | } 68 | 69 | // ---------------------------------------------------------------------- 70 | 71 | const ColorPreviewRoot = styled('ul')(() => ({ 72 | display: 'flex', 73 | flexDirection: 'row', 74 | alignItems: 'center', 75 | justifyContent: 'flex-end', 76 | })); 77 | 78 | const ItemRoot = styled('li')(({ theme }) => ({ 79 | borderRadius: '50%', 80 | width: 'var(--item-size)', 81 | height: 'var(--item-size)', 82 | marginLeft: 'var(--item-gap)', 83 | backgroundColor: 'var(--item-color)', 84 | border: `solid 2px ${theme.vars.palette.background.paper}`, 85 | boxShadow: `inset -1px 1px 2px ${varAlpha(theme.vars.palette.common.blackChannel, 0.24)}`, 86 | })); 87 | 88 | const ItemLabel = styled('li')(({ theme }) => ({ 89 | ...theme.typography.subtitle2, 90 | })); 91 | -------------------------------------------------------------------------------- /src/components/color-utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './classes'; 2 | 3 | export * from './color-picker'; 4 | 5 | export * from './color-preview'; 6 | -------------------------------------------------------------------------------- /src/components/iconify/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const iconifyClasses = { 6 | root: createClasses('iconify__root'), 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/iconify/iconify.tsx: -------------------------------------------------------------------------------- 1 | import type { IconProps } from '@iconify/react'; 2 | 3 | import { useId } from 'react'; 4 | import { Icon } from '@iconify/react'; 5 | import { mergeClasses } from 'minimal-shared/utils'; 6 | 7 | import { styled } from '@mui/material/styles'; 8 | 9 | import { iconifyClasses } from './classes'; 10 | import { allIconNames, registerIcons } from './register-icons'; 11 | 12 | import type { IconifyName } from './register-icons'; 13 | 14 | // ---------------------------------------------------------------------- 15 | 16 | export type IconifyProps = React.ComponentProps & 17 | Omit & { 18 | icon: IconifyName; 19 | }; 20 | 21 | export function Iconify({ className, icon, width = 20, height, sx, ...other }: IconifyProps) { 22 | const id = useId(); 23 | 24 | if (!allIconNames.includes(icon)) { 25 | console.warn( 26 | [ 27 | `Icon "${icon}" is currently loaded online, which may cause flickering effects.`, 28 | `To ensure a smoother experience, please register your icon collection for offline use.`, 29 | `More information is available at: https://docs.minimals.cc/icons/`, 30 | ].join('\n') 31 | ); 32 | } 33 | 34 | registerIcons(); 35 | 36 | return ( 37 | 53 | ); 54 | } 55 | 56 | // ---------------------------------------------------------------------- 57 | 58 | const IconRoot = styled(Icon)``; 59 | -------------------------------------------------------------------------------- /src/components/iconify/index.ts: -------------------------------------------------------------------------------- 1 | export * from './classes'; 2 | 3 | export * from './iconify'; 4 | 5 | export * from './register-icons'; 6 | -------------------------------------------------------------------------------- /src/components/iconify/register-icons.ts: -------------------------------------------------------------------------------- 1 | import type { IconifyJSON } from '@iconify/react'; 2 | 3 | import { addCollection } from '@iconify/react'; 4 | 5 | import allIcons from './icon-sets'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | export const iconSets = Object.entries(allIcons).reduce((acc, [key, value]) => { 10 | const [prefix, iconName] = key.split(':'); 11 | const existingPrefix = acc.find((item) => item.prefix === prefix); 12 | 13 | if (existingPrefix) { 14 | existingPrefix.icons[iconName] = value; 15 | } else { 16 | acc.push({ 17 | prefix, 18 | icons: { 19 | [iconName]: value, 20 | }, 21 | }); 22 | } 23 | 24 | return acc; 25 | }, [] as IconifyJSON[]); 26 | 27 | export const allIconNames = Object.keys(allIcons) as IconifyName[]; 28 | 29 | export type IconifyName = keyof typeof allIcons; 30 | 31 | // ---------------------------------------------------------------------- 32 | 33 | let areIconsRegistered = false; 34 | 35 | export function registerIcons() { 36 | if (areIconsRegistered) { 37 | return; 38 | } 39 | 40 | iconSets.forEach((iconSet) => { 41 | const iconSetConfig = { 42 | ...iconSet, 43 | width: (iconSet.prefix === 'carbon' && 32) || 24, 44 | height: (iconSet.prefix === 'carbon' && 32) || 24, 45 | }; 46 | 47 | addCollection(iconSetConfig); 48 | }); 49 | 50 | areIconsRegistered = true; 51 | } 52 | -------------------------------------------------------------------------------- /src/components/label/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const labelClasses = { 6 | root: createClasses('label__root'), 7 | icon: createClasses('label__icon'), 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/label/index.ts: -------------------------------------------------------------------------------- 1 | export * from './label'; 2 | 3 | export * from './styles'; 4 | 5 | export * from './classes'; 6 | 7 | export type * from './types'; 8 | -------------------------------------------------------------------------------- /src/components/label/label.tsx: -------------------------------------------------------------------------------- 1 | import { upperFirst } from 'es-toolkit'; 2 | import { mergeClasses } from 'minimal-shared/utils'; 3 | 4 | import { labelClasses } from './classes'; 5 | import { LabelRoot, LabelIcon } from './styles'; 6 | 7 | import type { LabelProps } from './types'; 8 | 9 | // ---------------------------------------------------------------------- 10 | 11 | export function Label({ 12 | sx, 13 | endIcon, 14 | children, 15 | startIcon, 16 | className, 17 | disabled, 18 | variant = 'soft', 19 | color = 'default', 20 | ...other 21 | }: LabelProps) { 22 | return ( 23 | 31 | {startIcon && {startIcon}} 32 | 33 | {typeof children === 'string' ? upperFirst(children) : children} 34 | 35 | {endIcon && {endIcon}} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/label/styles.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSObject } from '@mui/material/styles'; 2 | 3 | import { varAlpha } from 'minimal-shared/utils'; 4 | 5 | import { styled } from '@mui/material/styles'; 6 | 7 | import type { LabelProps } from './types'; 8 | 9 | // ---------------------------------------------------------------------- 10 | 11 | export const LabelRoot = styled('span', { 12 | shouldForwardProp: (prop: string) => !['color', 'variant', 'disabled', 'sx'].includes(prop), 13 | })(({ color, variant, disabled, theme }) => { 14 | const defaultStyles: CSSObject = { 15 | ...(color === 'default' && { 16 | /** 17 | * @variant filled 18 | */ 19 | ...(variant === 'filled' && { 20 | color: theme.vars.palette.common.white, 21 | backgroundColor: theme.vars.palette.text.primary, 22 | ...theme.applyStyles('dark', { 23 | color: theme.vars.palette.grey[800], 24 | }), 25 | }), 26 | /** 27 | * @variant outlined 28 | */ 29 | ...(variant === 'outlined' && { 30 | backgroundColor: 'transparent', 31 | color: theme.vars.palette.text.primary, 32 | border: `2px solid ${theme.vars.palette.text.primary}`, 33 | }), 34 | /** 35 | * @variant soft 36 | */ 37 | ...(variant === 'soft' && { 38 | color: theme.vars.palette.text.secondary, 39 | backgroundColor: varAlpha(theme.vars.palette.grey['500Channel'], 0.16), 40 | }), 41 | /** 42 | * @variant inverted 43 | */ 44 | ...(variant === 'inverted' && { 45 | color: theme.vars.palette.grey[800], 46 | backgroundColor: theme.vars.palette.grey[300], 47 | }), 48 | }), 49 | }; 50 | 51 | const colorStyles: CSSObject = { 52 | ...(color && 53 | color !== 'default' && { 54 | /** 55 | * @variant filled 56 | */ 57 | ...(variant === 'filled' && { 58 | color: theme.vars.palette[color].contrastText, 59 | backgroundColor: theme.vars.palette[color].main, 60 | }), 61 | /** 62 | * @variant outlined 63 | */ 64 | ...(variant === 'outlined' && { 65 | backgroundColor: 'transparent', 66 | color: theme.vars.palette[color].main, 67 | border: `2px solid ${theme.vars.palette[color].main}`, 68 | }), 69 | /** 70 | * @variant soft 71 | */ 72 | ...(variant === 'soft' && { 73 | color: theme.vars.palette[color].dark, 74 | backgroundColor: varAlpha(theme.vars.palette[color].mainChannel, 0.16), 75 | ...theme.applyStyles('dark', { 76 | color: theme.vars.palette[color].light, 77 | }), 78 | }), 79 | /** 80 | * @variant inverted 81 | */ 82 | ...(variant === 'inverted' && { 83 | color: theme.vars.palette[color].darker, 84 | backgroundColor: theme.vars.palette[color].lighter, 85 | }), 86 | }), 87 | }; 88 | 89 | return { 90 | height: 24, 91 | minWidth: 24, 92 | lineHeight: 0, 93 | flexShrink: 0, 94 | cursor: 'default', 95 | alignItems: 'center', 96 | whiteSpace: 'nowrap', 97 | display: 'inline-flex', 98 | gap: theme.spacing(0.75), 99 | justifyContent: 'center', 100 | padding: theme.spacing(0, 0.75), 101 | fontSize: theme.typography.pxToRem(12), 102 | fontWeight: theme.typography.fontWeightBold, 103 | borderRadius: theme.shape.borderRadius * 0.75, 104 | transition: theme.transitions.create(['all'], { duration: theme.transitions.duration.shorter }), 105 | ...defaultStyles, 106 | ...colorStyles, 107 | ...(disabled && { opacity: 0.48, pointerEvents: 'none' }), 108 | }; 109 | }); 110 | 111 | export const LabelIcon = styled('span')({ 112 | width: 16, 113 | height: 16, 114 | flexShrink: 0, 115 | '& svg, img': { width: '100%', height: '100%', objectFit: 'cover' }, 116 | }); 117 | -------------------------------------------------------------------------------- /src/components/label/types.ts: -------------------------------------------------------------------------------- 1 | import type { Theme, SxProps } from '@mui/material/styles'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export type LabelColor = 6 | | 'default' 7 | | 'primary' 8 | | 'secondary' 9 | | 'info' 10 | | 'success' 11 | | 'warning' 12 | | 'error'; 13 | 14 | export type LabelVariant = 'filled' | 'outlined' | 'soft' | 'inverted'; 15 | 16 | export interface LabelProps extends React.ComponentProps<'span'> { 17 | sx?: SxProps; 18 | disabled?: boolean; 19 | color?: LabelColor; 20 | variant?: LabelVariant; 21 | endIcon?: React.ReactNode; 22 | startIcon?: React.ReactNode; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/logo/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const logoClasses = { 6 | root: createClasses('logo__root'), 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/logo/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logo'; 2 | 3 | export * from './classes'; 4 | -------------------------------------------------------------------------------- /src/components/scrollbar/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const scrollbarClasses = { 6 | root: createClasses('scrollbar__root'), 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/scrollbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './classes'; 2 | 3 | export * from './scrollbar'; 4 | 5 | export type * from './types'; 6 | -------------------------------------------------------------------------------- /src/components/scrollbar/scrollbar.tsx: -------------------------------------------------------------------------------- 1 | import SimpleBar from 'simplebar-react'; 2 | import { mergeClasses } from 'minimal-shared/utils'; 3 | 4 | import { styled } from '@mui/material/styles'; 5 | 6 | import { scrollbarClasses } from './classes'; 7 | 8 | import type { ScrollbarProps } from './types'; 9 | 10 | // ---------------------------------------------------------------------- 11 | 12 | export function Scrollbar({ 13 | sx, 14 | ref, 15 | children, 16 | className, 17 | slotProps, 18 | fillContent = true, 19 | ...other 20 | }: ScrollbarProps) { 21 | return ( 22 | 37 | {children} 38 | 39 | ); 40 | } 41 | 42 | // ---------------------------------------------------------------------- 43 | 44 | const ScrollbarRoot = styled(SimpleBar, { 45 | shouldForwardProp: (prop: string) => !['fillContent', 'sx'].includes(prop), 46 | })>(({ fillContent }) => ({ 47 | minWidth: 0, 48 | minHeight: 0, 49 | flexGrow: 1, 50 | display: 'flex', 51 | flexDirection: 'column', 52 | ...(fillContent && { 53 | '& .simplebar-content': { 54 | display: 'flex', 55 | flex: '1 1 auto', 56 | minHeight: '100%', 57 | flexDirection: 'column', 58 | }, 59 | }), 60 | })); 61 | -------------------------------------------------------------------------------- /src/components/scrollbar/styles.css: -------------------------------------------------------------------------------- 1 | @import 'simplebar-react/dist/simplebar.min.css'; 2 | 3 | .simplebar-scrollbar:before { 4 | background-color: var(--palette-text-disabled); 5 | } 6 | .simplebar-scrollbar.simplebar-visible:before { 7 | opacity: 0.48; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/scrollbar/types.ts: -------------------------------------------------------------------------------- 1 | import type { Theme, SxProps } from '@mui/material/styles'; 2 | import type { Props as SimplebarProps } from 'simplebar-react'; 3 | 4 | // ---------------------------------------------------------------------- 5 | 6 | export type ScrollbarProps = SimplebarProps & 7 | React.ComponentProps<'div'> & { 8 | sx?: SxProps; 9 | fillContent?: boolean; 10 | slotProps?: { 11 | wrapperSx?: SxProps; 12 | contentSx?: SxProps; 13 | contentWrapperSx?: SxProps; 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/svg-color/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const svgColorClasses = { 6 | root: createClasses('svg__color__root'), 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/svg-color/index.ts: -------------------------------------------------------------------------------- 1 | export * from './classes'; 2 | 3 | export * from './svg-color'; 4 | 5 | export type * from './types'; 6 | -------------------------------------------------------------------------------- /src/components/svg-color/svg-color.tsx: -------------------------------------------------------------------------------- 1 | import { mergeClasses } from 'minimal-shared/utils'; 2 | 3 | import { styled } from '@mui/material/styles'; 4 | 5 | import { svgColorClasses } from './classes'; 6 | 7 | import type { SvgColorProps } from './types'; 8 | 9 | // ---------------------------------------------------------------------- 10 | 11 | export function SvgColor({ src, className, sx, ...other }: SvgColorProps) { 12 | return ( 13 | 24 | ); 25 | } 26 | 27 | // ---------------------------------------------------------------------- 28 | 29 | const SvgRoot = styled('span')(() => ({ 30 | width: 24, 31 | height: 24, 32 | flexShrink: 0, 33 | display: 'inline-flex', 34 | backgroundColor: 'currentColor', 35 | })); 36 | -------------------------------------------------------------------------------- /src/components/svg-color/types.ts: -------------------------------------------------------------------------------- 1 | import type { Theme, SxProps } from '@mui/material/styles'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export type SvgColorProps = React.ComponentProps<'span'> & { 6 | src: string; 7 | sx?: SxProps; 8 | }; 9 | -------------------------------------------------------------------------------- /src/config-global.ts: -------------------------------------------------------------------------------- 1 | import packageJson from '../package.json'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export type ConfigValue = { 6 | appName: string; 7 | appVersion: string; 8 | }; 9 | 10 | export const CONFIG: ConfigValue = { 11 | appName: 'Minimal UI', 12 | appVersion: packageJson.version, 13 | }; 14 | -------------------------------------------------------------------------------- /src/global.css: -------------------------------------------------------------------------------- 1 | /** ************************************** 2 | * Fonts: app 3 | *************************************** */ 4 | @import '@fontsource-variable/dm-sans'; 5 | 6 | @import '@fontsource/barlow/400.css'; 7 | @import '@fontsource/barlow/500.css'; 8 | @import '@fontsource/barlow/600.css'; 9 | @import '@fontsource/barlow/700.css'; 10 | @import '@fontsource/barlow/800.css'; 11 | 12 | /** ************************************** 13 | * Plugins 14 | *************************************** */ 15 | /* scrollbar */ 16 | @import './components/scrollbar/styles.css'; 17 | 18 | /* chart */ 19 | @import './components/chart/styles.css'; 20 | 21 | /** ************************************** 22 | * Baseline 23 | *************************************** */ 24 | html { 25 | height: 100%; 26 | -webkit-overflow-scrolling: touch; 27 | } 28 | body, 29 | #root, 30 | #root__layout { 31 | display: flex; 32 | flex: 1 1 auto; 33 | min-height: 100%; 34 | flex-direction: column; 35 | } 36 | img { 37 | max-width: 100%; 38 | vertical-align: middle; 39 | } 40 | ul { 41 | margin: 0; 42 | padding: 0; 43 | list-style-type: none; 44 | } 45 | input[type='number'] { 46 | -moz-appearance: textfield; 47 | appearance: none; 48 | } 49 | input[type='number']::-webkit-outer-spin-button { 50 | margin: 0; 51 | -webkit-appearance: none; 52 | } 53 | input[type='number']::-webkit-inner-spin-button { 54 | margin: 0; 55 | -webkit-appearance: none; 56 | } 57 | -------------------------------------------------------------------------------- /src/layouts/auth/content.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps } from '@mui/material/Box'; 2 | 3 | import { mergeClasses } from 'minimal-shared/utils'; 4 | 5 | import Box from '@mui/material/Box'; 6 | 7 | import { layoutClasses } from '../core/classes'; 8 | 9 | // ---------------------------------------------------------------------- 10 | 11 | export type AuthContentProps = BoxProps; 12 | 13 | export function AuthContent({ sx, children, className, ...other }: AuthContentProps) { 14 | return ( 15 | ({ 19 | py: 5, 20 | px: 3, 21 | width: 1, 22 | zIndex: 2, 23 | borderRadius: 2, 24 | display: 'flex', 25 | flexDirection: 'column', 26 | maxWidth: 'var(--layout-auth-content-width)', 27 | bgcolor: theme.vars.palette.background.default, 28 | }), 29 | ...(Array.isArray(sx) ? sx : [sx]), 30 | ]} 31 | {...other} 32 | > 33 | {children} 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/layouts/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout'; 2 | 3 | export * from './content'; 4 | -------------------------------------------------------------------------------- /src/layouts/components/language-popover.tsx: -------------------------------------------------------------------------------- 1 | import type { IconButtonProps } from '@mui/material/IconButton'; 2 | 3 | import { useState, useCallback } from 'react'; 4 | import { usePopover } from 'minimal-shared/hooks'; 5 | 6 | import Box from '@mui/material/Box'; 7 | import Popover from '@mui/material/Popover'; 8 | import MenuList from '@mui/material/MenuList'; 9 | import IconButton from '@mui/material/IconButton'; 10 | import MenuItem, { menuItemClasses } from '@mui/material/MenuItem'; 11 | 12 | // ---------------------------------------------------------------------- 13 | 14 | export type LanguagePopoverProps = IconButtonProps & { 15 | data?: { 16 | value: string; 17 | label: string; 18 | icon: string; 19 | }[]; 20 | }; 21 | 22 | export function LanguagePopover({ data = [], sx, ...other }: LanguagePopoverProps) { 23 | const { open, anchorEl, onClose, onOpen } = usePopover(); 24 | 25 | const [locale, setLocale] = useState(data[0].value); 26 | 27 | const handleChangeLang = useCallback( 28 | (newLang: string) => { 29 | setLocale(newLang); 30 | onClose(); 31 | }, 32 | [onClose] 33 | ); 34 | 35 | const currentLang = data.find((lang) => lang.value === locale); 36 | 37 | const renderFlag = (label?: string, icon?: string) => ( 38 | 44 | ); 45 | 46 | const renderMenuList = () => ( 47 | 54 | 73 | {data?.map((option) => ( 74 | handleChangeLang(option.value)} 78 | > 79 | {renderFlag(option.label, option.icon)} 80 | {option.label} 81 | 82 | ))} 83 | 84 | 85 | ); 86 | 87 | return ( 88 | <> 89 | ({ 94 | p: 0, 95 | width: 40, 96 | height: 40, 97 | ...(open && { bgcolor: theme.vars.palette.action.selected }), 98 | }), 99 | ...(Array.isArray(sx) ? sx : [sx]), 100 | ]} 101 | {...other} 102 | > 103 | {renderFlag(currentLang?.label, currentLang?.icon)} 104 | 105 | 106 | {renderMenuList()} 107 | 108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /src/layouts/components/menu-button.tsx: -------------------------------------------------------------------------------- 1 | import type { IconButtonProps } from '@mui/material/IconButton'; 2 | 3 | import IconButton from '@mui/material/IconButton'; 4 | 5 | import { Iconify } from 'src/components/iconify'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | export function MenuButton({ sx, ...other }: IconButtonProps) { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/layouts/components/nav-upgrade.tsx: -------------------------------------------------------------------------------- 1 | import type { StackProps } from '@mui/material/Stack'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import Button from '@mui/material/Button'; 5 | import Typography from '@mui/material/Typography'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | export function NavUpgrade({ sx, ...other }: StackProps) { 10 | return ( 11 | 24 | ({ 28 | background: `linear-gradient(to right, ${theme.vars.palette.secondary.main}, ${theme.vars.palette.warning.main})`, 29 | WebkitBackgroundClip: 'text', 30 | WebkitTextFillColor: 'transparent', 31 | backgroundClip: 'text', 32 | textFillColor: 'transparent', 33 | color: 'transparent', 34 | }), 35 | ]} 36 | > 37 | More features? 38 | 39 | 40 | 41 | {`From only `} 42 | 43 | $69 44 | 45 | 46 | 47 | 53 | 54 | 62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/layouts/components/searchbar.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps } from '@mui/material/Box'; 2 | 3 | import { useState, useCallback } from 'react'; 4 | import { varAlpha } from 'minimal-shared/utils'; 5 | 6 | import Box from '@mui/material/Box'; 7 | import Slide from '@mui/material/Slide'; 8 | import Input from '@mui/material/Input'; 9 | import Button from '@mui/material/Button'; 10 | import { useTheme } from '@mui/material/styles'; 11 | import IconButton from '@mui/material/IconButton'; 12 | import InputAdornment from '@mui/material/InputAdornment'; 13 | import ClickAwayListener from '@mui/material/ClickAwayListener'; 14 | 15 | import { Iconify } from 'src/components/iconify'; 16 | 17 | // ---------------------------------------------------------------------- 18 | 19 | export function Searchbar({ sx, ...other }: BoxProps) { 20 | const theme = useTheme(); 21 | 22 | const [open, setOpen] = useState(false); 23 | 24 | const handleOpen = useCallback(() => { 25 | setOpen((prev) => !prev); 26 | }, []); 27 | 28 | const handleClose = useCallback(() => { 29 | setOpen(false); 30 | }, []); 31 | 32 | return ( 33 | 34 |
35 | {!open && ( 36 | 37 | 38 | 39 | )} 40 | 41 | 42 | 64 | 71 | 72 | 73 | } 74 | sx={{ fontWeight: 'fontWeightBold' }} 75 | /> 76 | 79 | 80 | 81 |
82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/layouts/core/classes.ts: -------------------------------------------------------------------------------- 1 | import { createClasses } from 'src/theme/create-classes'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const layoutClasses = { 6 | root: createClasses('layout__root'), 7 | main: createClasses('layout__main'), 8 | header: createClasses('layout__header'), 9 | nav: { 10 | root: createClasses('layout__nav__root'), 11 | mobile: createClasses('layout__nav__mobile'), 12 | vertical: createClasses('layout__nav__vertical'), 13 | horizontal: createClasses('layout__nav__horizontal'), 14 | }, 15 | content: createClasses('layout__main__content'), 16 | sidebarContainer: createClasses('layout__sidebar__container'), 17 | }; 18 | -------------------------------------------------------------------------------- /src/layouts/core/css-vars.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from '@mui/material/styles'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export function layoutSectionVars(theme: Theme) { 6 | return { 7 | '--layout-nav-zIndex': theme.zIndex.drawer + 1, 8 | '--layout-nav-mobile-width': '288px', 9 | '--layout-header-blur': '8px', 10 | '--layout-header-zIndex': theme.zIndex.appBar + 1, 11 | '--layout-header-mobile-height': '64px', 12 | '--layout-header-desktop-height': '72px', 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/layouts/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './classes'; 2 | 3 | export * from './css-vars'; 4 | 5 | export * from './main-section'; 6 | 7 | export * from './layout-section'; 8 | 9 | export * from './header-section'; 10 | -------------------------------------------------------------------------------- /src/layouts/core/layout-section.tsx: -------------------------------------------------------------------------------- 1 | import type { Theme, SxProps, CSSObject } from '@mui/material/styles'; 2 | 3 | import { mergeClasses } from 'minimal-shared/utils'; 4 | 5 | import { styled } from '@mui/material/styles'; 6 | import GlobalStyles from '@mui/material/GlobalStyles'; 7 | 8 | import { layoutClasses } from './classes'; 9 | import { layoutSectionVars } from './css-vars'; 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | export type LayoutSectionProps = React.ComponentProps<'div'> & { 14 | sx?: SxProps; 15 | cssVars?: CSSObject; 16 | children?: React.ReactNode; 17 | footerSection?: React.ReactNode; 18 | headerSection?: React.ReactNode; 19 | sidebarSection?: React.ReactNode; 20 | }; 21 | 22 | export function LayoutSection({ 23 | sx, 24 | cssVars, 25 | children, 26 | footerSection, 27 | headerSection, 28 | sidebarSection, 29 | className, 30 | ...other 31 | }: LayoutSectionProps) { 32 | const inputGlobalStyles = ( 33 | ({ body: { ...layoutSectionVars(theme), ...cssVars } })} /> 34 | ); 35 | 36 | return ( 37 | <> 38 | {inputGlobalStyles} 39 | 40 | 46 | {sidebarSection ? ( 47 | <> 48 | {sidebarSection} 49 | 50 | {headerSection} 51 | {children} 52 | {footerSection} 53 | 54 | 55 | ) : ( 56 | <> 57 | {headerSection} 58 | {children} 59 | {footerSection} 60 | 61 | )} 62 | 63 | 64 | ); 65 | } 66 | 67 | // ---------------------------------------------------------------------- 68 | 69 | const LayoutRoot = styled('div')``; 70 | 71 | const LayoutSidebarContainer = styled('div')(() => ({ 72 | display: 'flex', 73 | flex: '1 1 auto', 74 | flexDirection: 'column', 75 | })); 76 | -------------------------------------------------------------------------------- /src/layouts/core/main-section.tsx: -------------------------------------------------------------------------------- 1 | import { mergeClasses } from 'minimal-shared/utils'; 2 | 3 | import { styled } from '@mui/material/styles'; 4 | 5 | import { layoutClasses } from './classes'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | export type MainSectionProps = React.ComponentProps; 10 | 11 | export function MainSection({ children, className, sx, ...other }: MainSectionProps) { 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | // ---------------------------------------------------------------------- 20 | 21 | const MainRoot = styled('main')({ 22 | display: 'flex', 23 | flex: '1 1 auto', 24 | flexDirection: 'column', 25 | }); 26 | -------------------------------------------------------------------------------- /src/layouts/dashboard/content.tsx: -------------------------------------------------------------------------------- 1 | import type { Breakpoint } from '@mui/material/styles'; 2 | import type { ContainerProps } from '@mui/material/Container'; 3 | 4 | import { mergeClasses } from 'minimal-shared/utils'; 5 | 6 | import Container from '@mui/material/Container'; 7 | 8 | import { layoutClasses } from '../core/classes'; 9 | 10 | // ---------------------------------------------------------------------- 11 | 12 | export type DashboardContentProps = ContainerProps & { 13 | layoutQuery?: Breakpoint; 14 | disablePadding?: boolean; 15 | }; 16 | 17 | export function DashboardContent({ 18 | sx, 19 | children, 20 | className, 21 | disablePadding, 22 | maxWidth = 'lg', 23 | layoutQuery = 'lg', 24 | ...other 25 | }: DashboardContentProps) { 26 | return ( 27 | ({ 32 | display: 'flex', 33 | flex: '1 1 auto', 34 | flexDirection: 'column', 35 | pt: 'var(--layout-dashboard-content-pt)', 36 | pb: 'var(--layout-dashboard-content-pb)', 37 | [theme.breakpoints.up(layoutQuery)]: { 38 | px: 'var(--layout-dashboard-content-px)', 39 | }, 40 | ...(disablePadding && { 41 | p: { 42 | xs: 0, 43 | sm: 0, 44 | md: 0, 45 | lg: 0, 46 | xl: 0, 47 | }, 48 | }), 49 | }), 50 | ...(Array.isArray(sx) ? sx : [sx]), 51 | ]} 52 | {...other} 53 | > 54 | {children} 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/layouts/dashboard/css-vars.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from '@mui/material/styles'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export function dashboardLayoutVars(theme: Theme) { 6 | return { 7 | '--layout-transition-easing': 'linear', 8 | '--layout-transition-duration': '120ms', 9 | '--layout-nav-vertical-width': '300px', 10 | '--layout-dashboard-content-pt': theme.spacing(1), 11 | '--layout-dashboard-content-pb': theme.spacing(8), 12 | '--layout-dashboard-content-px': theme.spacing(5), 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/layouts/dashboard/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout'; 2 | 3 | export * from './content'; 4 | -------------------------------------------------------------------------------- /src/layouts/nav-config-account.tsx: -------------------------------------------------------------------------------- 1 | import { Iconify } from 'src/components/iconify'; 2 | 3 | import type { AccountPopoverProps } from './components/account-popover'; 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | export const _account: AccountPopoverProps['data'] = [ 8 | { 9 | label: 'Home', 10 | href: '/', 11 | icon: , 12 | }, 13 | { 14 | label: 'Profile', 15 | href: '#', 16 | icon: , 17 | }, 18 | { 19 | label: 'Settings', 20 | href: '#', 21 | icon: , 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /src/layouts/nav-config-dashboard.tsx: -------------------------------------------------------------------------------- 1 | import { Label } from 'src/components/label'; 2 | import { SvgColor } from 'src/components/svg-color'; 3 | 4 | // ---------------------------------------------------------------------- 5 | 6 | const icon = (name: string) => ; 7 | 8 | export type NavItem = { 9 | title: string; 10 | path: string; 11 | icon: React.ReactNode; 12 | info?: React.ReactNode; 13 | }; 14 | 15 | export const navData = [ 16 | { 17 | title: 'Dashboard', 18 | path: '/', 19 | icon: icon('ic-analytics'), 20 | }, 21 | { 22 | title: 'User', 23 | path: '/user', 24 | icon: icon('ic-user'), 25 | }, 26 | { 27 | title: 'Product', 28 | path: '/products', 29 | icon: icon('ic-cart'), 30 | info: ( 31 | 34 | ), 35 | }, 36 | { 37 | title: 'Blog', 38 | path: '/blog', 39 | icon: icon('ic-blog'), 40 | }, 41 | { 42 | title: 'Sign in', 43 | path: '/sign-in', 44 | icon: icon('ic-lock'), 45 | }, 46 | { 47 | title: 'Not found', 48 | path: '/404', 49 | icon: icon('ic-disabled'), 50 | }, 51 | ]; 52 | -------------------------------------------------------------------------------- /src/layouts/nav-config-workspace.tsx: -------------------------------------------------------------------------------- 1 | import type { WorkspacesPopoverProps } from './components/workspaces-popover'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const _workspaces: WorkspacesPopoverProps['data'] = [ 6 | { 7 | id: 'team-1', 8 | name: 'Team 1', 9 | plan: 'Free', 10 | logo: '/assets/icons/workspaces/logo-1.webp', 11 | }, 12 | { 13 | id: 'team-2', 14 | name: 'Team 2', 15 | plan: 'Pro', 16 | logo: '/assets/icons/workspaces/logo-2.webp', 17 | }, 18 | { 19 | id: 'team-3', 20 | name: 'Team 3', 21 | plan: 'Pro', 22 | logo: '/assets/icons/workspaces/logo-3.webp', 23 | }, 24 | ]; 25 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { Outlet, RouterProvider, createBrowserRouter } from 'react-router'; 4 | 5 | import App from './app'; 6 | import { routesSection } from './routes/sections'; 7 | import { ErrorBoundary } from './routes/components'; 8 | 9 | // ---------------------------------------------------------------------- 10 | 11 | const router = createBrowserRouter([ 12 | { 13 | Component: () => ( 14 | 15 | 16 | 17 | ), 18 | errorElement: , 19 | children: routesSection, 20 | }, 21 | ]); 22 | 23 | const root = createRoot(document.getElementById('root')!); 24 | 25 | root.render( 26 | 27 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /src/pages/blog.tsx: -------------------------------------------------------------------------------- 1 | import { _posts } from 'src/_mock'; 2 | import { CONFIG } from 'src/config-global'; 3 | 4 | import { BlogView } from 'src/sections/blog/view'; 5 | 6 | // ---------------------------------------------------------------------- 7 | 8 | export default function Page() { 9 | return ( 10 | <> 11 | {`Blog - ${CONFIG.appName}`} 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import { CONFIG } from 'src/config-global'; 2 | 3 | import { OverviewAnalyticsView as DashboardView } from 'src/sections/overview/view'; 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | export default function Page() { 8 | return ( 9 | <> 10 | {`Dashboard - ${CONFIG.appName}`} 11 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/page-not-found.tsx: -------------------------------------------------------------------------------- 1 | import { CONFIG } from 'src/config-global'; 2 | 3 | import { NotFoundView } from 'src/sections/error'; 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | export default function Page() { 8 | return ( 9 | <> 10 | {`404 page not found! | Error - ${CONFIG.appName}`} 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/products.tsx: -------------------------------------------------------------------------------- 1 | import { CONFIG } from 'src/config-global'; 2 | 3 | import { ProductsView } from 'src/sections/product/view'; 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | export default function Page() { 8 | return ( 9 | <> 10 | {`Products - ${CONFIG.appName}`} 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/sign-in.tsx: -------------------------------------------------------------------------------- 1 | import { CONFIG } from 'src/config-global'; 2 | 3 | import { SignInView } from 'src/sections/auth'; 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | export default function Page() { 8 | return ( 9 | <> 10 | {`Sign in - ${CONFIG.appName}`} 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/user.tsx: -------------------------------------------------------------------------------- 1 | import { CONFIG } from 'src/config-global'; 2 | 3 | import { UserView } from 'src/sections/user/view'; 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | export default function Page() { 8 | return ( 9 | <> 10 | {`Users - ${CONFIG.appName}`} 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/routes/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './router-link'; 2 | 3 | export * from './error-boundary'; 4 | -------------------------------------------------------------------------------- /src/routes/components/router-link.tsx: -------------------------------------------------------------------------------- 1 | import type { LinkProps } from 'react-router'; 2 | 3 | import { Link } from 'react-router'; 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | interface RouterLinkProps extends Omit { 8 | href: string; 9 | ref?: React.RefObject; 10 | } 11 | 12 | export function RouterLink({ href, ref, ...other }: RouterLinkProps) { 13 | return ; 14 | } 15 | -------------------------------------------------------------------------------- /src/routes/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useRouter } from './use-router'; 2 | 3 | export { usePathname } from './use-pathname'; 4 | -------------------------------------------------------------------------------- /src/routes/hooks/use-pathname.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { useLocation } from 'react-router'; 3 | 4 | // ---------------------------------------------------------------------- 5 | 6 | export function usePathname() { 7 | const { pathname } = useLocation(); 8 | 9 | return useMemo(() => pathname, [pathname]); 10 | } 11 | -------------------------------------------------------------------------------- /src/routes/hooks/use-router.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { useNavigate } from 'react-router'; 3 | 4 | // ---------------------------------------------------------------------- 5 | 6 | export function useRouter() { 7 | const navigate = useNavigate(); 8 | 9 | const router = useMemo( 10 | () => ({ 11 | back: () => navigate(-1), 12 | forward: () => navigate(1), 13 | refresh: () => navigate(0), 14 | push: (href: string) => navigate(href), 15 | replace: (href: string) => navigate(href, { replace: true }), 16 | }), 17 | [navigate] 18 | ); 19 | 20 | return router; 21 | } 22 | -------------------------------------------------------------------------------- /src/routes/sections.tsx: -------------------------------------------------------------------------------- 1 | import type { RouteObject } from 'react-router'; 2 | 3 | import { lazy, Suspense } from 'react'; 4 | import { Outlet } from 'react-router-dom'; 5 | import { varAlpha } from 'minimal-shared/utils'; 6 | 7 | import Box from '@mui/material/Box'; 8 | import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress'; 9 | 10 | import { AuthLayout } from 'src/layouts/auth'; 11 | import { DashboardLayout } from 'src/layouts/dashboard'; 12 | 13 | // ---------------------------------------------------------------------- 14 | 15 | export const DashboardPage = lazy(() => import('src/pages/dashboard')); 16 | export const BlogPage = lazy(() => import('src/pages/blog')); 17 | export const UserPage = lazy(() => import('src/pages/user')); 18 | export const SignInPage = lazy(() => import('src/pages/sign-in')); 19 | export const ProductsPage = lazy(() => import('src/pages/products')); 20 | export const Page404 = lazy(() => import('src/pages/page-not-found')); 21 | 22 | const renderFallback = () => ( 23 | 31 | varAlpha(theme.vars.palette.text.primaryChannel, 0.16), 36 | [`& .${linearProgressClasses.bar}`]: { bgcolor: 'text.primary' }, 37 | }} 38 | /> 39 | 40 | ); 41 | 42 | export const routesSection: RouteObject[] = [ 43 | { 44 | element: ( 45 | 46 | 47 | 48 | 49 | 50 | ), 51 | children: [ 52 | { index: true, element: }, 53 | { path: 'user', element: }, 54 | { path: 'products', element: }, 55 | { path: 'blog', element: }, 56 | ], 57 | }, 58 | { 59 | path: 'sign-in', 60 | element: ( 61 | 62 | 63 | 64 | ), 65 | }, 66 | { 67 | path: '404', 68 | element: , 69 | }, 70 | { path: '*', element: }, 71 | ]; 72 | -------------------------------------------------------------------------------- /src/sections/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sign-in-view'; 2 | -------------------------------------------------------------------------------- /src/sections/auth/sign-in-view.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import Link from '@mui/material/Link'; 5 | import Button from '@mui/material/Button'; 6 | import Divider from '@mui/material/Divider'; 7 | import TextField from '@mui/material/TextField'; 8 | import IconButton from '@mui/material/IconButton'; 9 | import Typography from '@mui/material/Typography'; 10 | import InputAdornment from '@mui/material/InputAdornment'; 11 | 12 | import { useRouter } from 'src/routes/hooks'; 13 | 14 | import { Iconify } from 'src/components/iconify'; 15 | 16 | // ---------------------------------------------------------------------- 17 | 18 | export function SignInView() { 19 | const router = useRouter(); 20 | 21 | const [showPassword, setShowPassword] = useState(false); 22 | 23 | const handleSignIn = useCallback(() => { 24 | router.push('/'); 25 | }, [router]); 26 | 27 | const renderForm = ( 28 | 35 | 45 | 46 | 47 | Forgot password? 48 | 49 | 50 | 61 | setShowPassword(!showPassword)} edge="end"> 62 | 63 | 64 | 65 | ), 66 | }, 67 | }} 68 | sx={{ mb: 3 }} 69 | /> 70 | 71 | 81 | 82 | ); 83 | 84 | return ( 85 | <> 86 | 95 | Sign in 96 | 102 | Don’t have an account? 103 | 104 | Get started 105 | 106 | 107 | 108 | {renderForm} 109 | 110 | 114 | OR 115 | 116 | 117 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /src/sections/blog/post-search.tsx: -------------------------------------------------------------------------------- 1 | import type { Theme, SxProps } from '@mui/material/styles'; 2 | 3 | import TextField from '@mui/material/TextField'; 4 | import InputAdornment from '@mui/material/InputAdornment'; 5 | import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'; 6 | 7 | import { Iconify } from 'src/components/iconify'; 8 | 9 | import type { IPostItem } from './post-item'; 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | type PostSearchProps = { 14 | posts: IPostItem[]; 15 | sx?: SxProps; 16 | }; 17 | 18 | export function PostSearch({ posts, sx }: PostSearchProps) { 19 | return ( 20 | post.title} 37 | isOptionEqualToValue={(option, value) => option.id === value.id} 38 | renderInput={(params) => ( 39 | 47 | 51 | 52 | ), 53 | }, 54 | }} 55 | /> 56 | )} 57 | /> 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/sections/blog/post-sort.tsx: -------------------------------------------------------------------------------- 1 | import type { ButtonProps } from '@mui/material/Button'; 2 | 3 | import { useState, useCallback } from 'react'; 4 | import { varAlpha } from 'minimal-shared/utils'; 5 | 6 | import Button from '@mui/material/Button'; 7 | import Popover from '@mui/material/Popover'; 8 | import MenuList from '@mui/material/MenuList'; 9 | import MenuItem, { menuItemClasses } from '@mui/material/MenuItem'; 10 | 11 | import { Iconify } from 'src/components/iconify'; 12 | 13 | // ---------------------------------------------------------------------- 14 | 15 | type PostSortProps = ButtonProps & { 16 | sortBy: string; 17 | onSort: (newSort: string) => void; 18 | options: { value: string; label: string }[]; 19 | }; 20 | 21 | export function PostSort({ options, sortBy, onSort, sx, ...other }: PostSortProps) { 22 | const [openPopover, setOpenPopover] = useState(null); 23 | 24 | const handleOpenPopover = useCallback((event: React.MouseEvent) => { 25 | setOpenPopover(event.currentTarget); 26 | }, []); 27 | 28 | const handleClosePopover = useCallback(() => { 29 | setOpenPopover(null); 30 | }, []); 31 | 32 | return ( 33 | <> 34 | 56 | 57 | 64 | 80 | {options.map((option) => ( 81 | { 85 | onSort(option.value); 86 | handleClosePopover(); 87 | }} 88 | > 89 | {option.label} 90 | 91 | ))} 92 | 93 | 94 | 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /src/sections/blog/view/blog-view.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import Grid from '@mui/material/Grid'; 5 | import Button from '@mui/material/Button'; 6 | import Typography from '@mui/material/Typography'; 7 | import Pagination from '@mui/material/Pagination'; 8 | 9 | import { DashboardContent } from 'src/layouts/dashboard'; 10 | 11 | import { Iconify } from 'src/components/iconify'; 12 | 13 | import { PostItem } from '../post-item'; 14 | import { PostSort } from '../post-sort'; 15 | import { PostSearch } from '../post-search'; 16 | 17 | import type { IPostItem } from '../post-item'; 18 | 19 | // ---------------------------------------------------------------------- 20 | 21 | type Props = { 22 | posts: IPostItem[]; 23 | }; 24 | 25 | export function BlogView({ posts }: Props) { 26 | const [sortBy, setSortBy] = useState('latest'); 27 | 28 | const handleSort = useCallback((newSort: string) => { 29 | setSortBy(newSort); 30 | }, []); 31 | 32 | return ( 33 | 34 | 41 | 42 | Blog 43 | 44 | 51 | 52 | 53 | 61 | 62 | 71 | 72 | 73 | 74 | {posts.map((post, index) => { 75 | const latestPostLarge = index === 0; 76 | const latestPost = index === 1 || index === 2; 77 | 78 | return ( 79 | 87 | 88 | 89 | ); 90 | })} 91 | 92 | 93 | 94 | 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /src/sections/blog/view/index.ts: -------------------------------------------------------------------------------- 1 | export * from './blog-view'; 2 | -------------------------------------------------------------------------------- /src/sections/error/index.ts: -------------------------------------------------------------------------------- 1 | export * from './not-found-view'; 2 | -------------------------------------------------------------------------------- /src/sections/error/not-found-view.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import Button from '@mui/material/Button'; 3 | import Container from '@mui/material/Container'; 4 | import Typography from '@mui/material/Typography'; 5 | 6 | import { RouterLink } from 'src/routes/components'; 7 | 8 | import { Logo } from 'src/components/logo'; 9 | 10 | // ---------------------------------------------------------------------- 11 | 12 | export function NotFoundView() { 13 | return ( 14 | <> 15 | 16 | 17 | 27 | 28 | Sorry, page not found! 29 | 30 | 31 | 32 | Sorry, we couldn’t find the page you’re looking for. Perhaps you’ve mistyped the URL? Be 33 | sure to check your spelling. 34 | 35 | 36 | 45 | 46 | 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-conversion-rates.tsx: -------------------------------------------------------------------------------- 1 | import type { CardProps } from '@mui/material/Card'; 2 | import type { ChartOptions } from 'src/components/chart'; 3 | 4 | import Card from '@mui/material/Card'; 5 | import CardHeader from '@mui/material/CardHeader'; 6 | import { useTheme, alpha as hexAlpha } from '@mui/material/styles'; 7 | 8 | import { fNumber } from 'src/utils/format-number'; 9 | 10 | import { Chart, useChart } from 'src/components/chart'; 11 | 12 | // ---------------------------------------------------------------------- 13 | 14 | type Props = CardProps & { 15 | title?: string; 16 | subheader?: string; 17 | chart: { 18 | colors?: string[]; 19 | categories?: string[]; 20 | series: { 21 | name: string; 22 | data: number[]; 23 | }[]; 24 | options?: ChartOptions; 25 | }; 26 | }; 27 | 28 | export function AnalyticsConversionRates({ title, subheader, chart, sx, ...other }: Props) { 29 | const theme = useTheme(); 30 | 31 | const chartColors = chart.colors ?? [ 32 | theme.palette.primary.dark, 33 | hexAlpha(theme.palette.primary.dark, 0.24), 34 | ]; 35 | 36 | const chartOptions = useChart({ 37 | colors: chartColors, 38 | stroke: { width: 2, colors: ['transparent'] }, 39 | tooltip: { 40 | shared: true, 41 | intersect: false, 42 | y: { 43 | formatter: (value: number) => fNumber(value), 44 | title: { formatter: (seriesName: string) => `${seriesName}: ` }, 45 | }, 46 | }, 47 | xaxis: { categories: chart.categories }, 48 | dataLabels: { 49 | enabled: true, 50 | offsetX: -6, 51 | style: { fontSize: '10px', colors: ['#FFFFFF', theme.palette.text.primary] }, 52 | }, 53 | plotOptions: { 54 | bar: { 55 | horizontal: true, 56 | borderRadius: 2, 57 | barHeight: '48%', 58 | dataLabels: { position: 'top' }, 59 | }, 60 | }, 61 | ...chart.options, 62 | }); 63 | 64 | return ( 65 | 66 | 67 | 68 | 80 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-current-subject.tsx: -------------------------------------------------------------------------------- 1 | import type { CardProps } from '@mui/material/Card'; 2 | import type { ChartOptions } from 'src/components/chart'; 3 | 4 | import Card from '@mui/material/Card'; 5 | import Divider from '@mui/material/Divider'; 6 | import { useTheme } from '@mui/material/styles'; 7 | import CardHeader from '@mui/material/CardHeader'; 8 | 9 | import { Chart, useChart, ChartLegends } from 'src/components/chart'; 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | type Props = CardProps & { 14 | title?: string; 15 | subheader?: string; 16 | chart: { 17 | colors?: string[]; 18 | categories: string[]; 19 | series: { 20 | name: string; 21 | data: number[]; 22 | }[]; 23 | options?: ChartOptions; 24 | }; 25 | }; 26 | 27 | export function AnalyticsCurrentSubject({ title, subheader, chart, sx, ...other }: Props) { 28 | const theme = useTheme(); 29 | 30 | const chartColors = chart.colors ?? [ 31 | theme.palette.primary.main, 32 | theme.palette.warning.main, 33 | theme.palette.info.main, 34 | ]; 35 | 36 | const chartOptions = useChart({ 37 | colors: chartColors, 38 | stroke: { width: 2 }, 39 | fill: { opacity: 0.48 }, 40 | xaxis: { 41 | categories: chart.categories, 42 | labels: { style: { colors: Array.from({ length: 6 }, () => theme.palette.text.secondary) } }, 43 | }, 44 | ...chart.options, 45 | }); 46 | 47 | return ( 48 | 49 | 50 | 51 | 63 | 64 | 65 | 66 | item.name)} 68 | colors={chartOptions?.colors} 69 | sx={{ p: 3, justifyContent: 'center' }} 70 | /> 71 | 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-current-visits.tsx: -------------------------------------------------------------------------------- 1 | import type { CardProps } from '@mui/material/Card'; 2 | import type { ChartOptions } from 'src/components/chart'; 3 | 4 | import Card from '@mui/material/Card'; 5 | import Divider from '@mui/material/Divider'; 6 | import { useTheme } from '@mui/material/styles'; 7 | import CardHeader from '@mui/material/CardHeader'; 8 | 9 | import { fNumber } from 'src/utils/format-number'; 10 | 11 | import { Chart, useChart, ChartLegends } from 'src/components/chart'; 12 | 13 | // ---------------------------------------------------------------------- 14 | 15 | type Props = CardProps & { 16 | title?: string; 17 | subheader?: string; 18 | chart: { 19 | colors?: string[]; 20 | series: { 21 | label: string; 22 | value: number; 23 | }[]; 24 | options?: ChartOptions; 25 | }; 26 | }; 27 | 28 | export function AnalyticsCurrentVisits({ title, subheader, chart, sx, ...other }: Props) { 29 | const theme = useTheme(); 30 | 31 | const chartSeries = chart.series.map((item) => item.value); 32 | 33 | const chartColors = chart.colors ?? [ 34 | theme.palette.primary.main, 35 | theme.palette.warning.light, 36 | theme.palette.info.dark, 37 | theme.palette.error.main, 38 | ]; 39 | 40 | const chartOptions = useChart({ 41 | chart: { sparkline: { enabled: true } }, 42 | colors: chartColors, 43 | labels: chart.series.map((item) => item.label), 44 | stroke: { width: 0 }, 45 | dataLabels: { enabled: true, dropShadow: { enabled: false } }, 46 | tooltip: { 47 | y: { 48 | formatter: (value: number) => fNumber(value), 49 | title: { formatter: (seriesName: string) => `${seriesName}` }, 50 | }, 51 | }, 52 | plotOptions: { pie: { donut: { labels: { show: false } } } }, 53 | ...chart.options, 54 | }); 55 | 56 | return ( 57 | 58 | 59 | 60 | 71 | 72 | 73 | 74 | 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-news.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps } from '@mui/material/Box'; 2 | import type { CardProps } from '@mui/material/Card'; 3 | 4 | import Box from '@mui/material/Box'; 5 | import Link from '@mui/material/Link'; 6 | import Card from '@mui/material/Card'; 7 | import Button from '@mui/material/Button'; 8 | import Avatar from '@mui/material/Avatar'; 9 | import CardHeader from '@mui/material/CardHeader'; 10 | import ListItemText from '@mui/material/ListItemText'; 11 | 12 | import { fToNow } from 'src/utils/format-time'; 13 | 14 | import { Iconify } from 'src/components/iconify'; 15 | import { Scrollbar } from 'src/components/scrollbar'; 16 | 17 | // ---------------------------------------------------------------------- 18 | 19 | type Props = CardProps & { 20 | title?: string; 21 | subheader?: string; 22 | list: { 23 | id: string; 24 | title: string; 25 | coverUrl: string; 26 | description: string; 27 | postedAt: string | number | null; 28 | }[]; 29 | }; 30 | 31 | export function AnalyticsNews({ title, subheader, list, sx, ...other }: Props) { 32 | return ( 33 | 34 | 35 | 36 | 37 | 38 | {list.map((item) => ( 39 | 40 | ))} 41 | 42 | 43 | 44 | 45 | 52 | 53 | 54 | ); 55 | } 56 | 57 | // ---------------------------------------------------------------------- 58 | 59 | type ItemProps = BoxProps & { 60 | item: Props['list'][number]; 61 | }; 62 | 63 | function Item({ item, sx, ...other }: ItemProps) { 64 | return ( 65 | ({ 68 | py: 2, 69 | px: 3, 70 | gap: 2, 71 | display: 'flex', 72 | alignItems: 'center', 73 | borderBottom: `dashed 1px ${theme.vars.palette.divider}`, 74 | }), 75 | ...(Array.isArray(sx) ? sx : [sx]), 76 | ]} 77 | {...other} 78 | > 79 | 85 | 86 | {item.title}} 88 | secondary={item.description} 89 | slotProps={{ 90 | primary: { noWrap: true }, 91 | secondary: { 92 | noWrap: true, 93 | sx: { mt: 0.5 }, 94 | }, 95 | }} 96 | /> 97 | 98 | 99 | {fToNow(item.postedAt)} 100 | 101 | 102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-order-timeline.tsx: -------------------------------------------------------------------------------- 1 | import type { CardProps } from '@mui/material/Card'; 2 | import type { TimelineItemProps } from '@mui/lab/TimelineItem'; 3 | 4 | import Card from '@mui/material/Card'; 5 | import Timeline from '@mui/lab/Timeline'; 6 | import TimelineDot from '@mui/lab/TimelineDot'; 7 | import Typography from '@mui/material/Typography'; 8 | import CardHeader from '@mui/material/CardHeader'; 9 | import TimelineContent from '@mui/lab/TimelineContent'; 10 | import TimelineSeparator from '@mui/lab/TimelineSeparator'; 11 | import TimelineConnector from '@mui/lab/TimelineConnector'; 12 | import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; 13 | 14 | import { fDateTime } from 'src/utils/format-time'; 15 | 16 | // ---------------------------------------------------------------------- 17 | 18 | type Props = CardProps & { 19 | title?: string; 20 | subheader?: string; 21 | list: { 22 | id: string; 23 | type: string; 24 | title: string; 25 | time: string | number | null; 26 | }[]; 27 | }; 28 | 29 | export function AnalyticsOrderTimeline({ title, subheader, list, sx, ...other }: Props) { 30 | return ( 31 | 32 | 33 | 34 | 37 | {list.map((item, index) => ( 38 | 39 | ))} 40 | 41 | 42 | ); 43 | } 44 | 45 | // ---------------------------------------------------------------------- 46 | 47 | type ItemProps = TimelineItemProps & { 48 | lastItem: boolean; 49 | item: Props['list'][number]; 50 | }; 51 | 52 | function Item({ item, lastItem, ...other }: ItemProps) { 53 | return ( 54 | 55 | 56 | 65 | {lastItem ? null : } 66 | 67 | 68 | 69 | {item.title} 70 | 71 | 72 | {fDateTime(item.time)} 73 | 74 | 75 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-traffic-by-site.tsx: -------------------------------------------------------------------------------- 1 | import type { CardProps } from '@mui/material/Card'; 2 | 3 | import { varAlpha } from 'minimal-shared/utils'; 4 | 5 | import Box from '@mui/material/Box'; 6 | import Card from '@mui/material/Card'; 7 | import CardHeader from '@mui/material/CardHeader'; 8 | import Typography from '@mui/material/Typography'; 9 | 10 | import { fShortenNumber } from 'src/utils/format-number'; 11 | 12 | import { Iconify } from 'src/components/iconify'; 13 | 14 | // ---------------------------------------------------------------------- 15 | 16 | type Props = CardProps & { 17 | title?: string; 18 | subheader?: string; 19 | list: { value: string; label: string; total: number }[]; 20 | }; 21 | 22 | export function AnalyticsTrafficBySite({ title, subheader, list, sx, ...other }: Props) { 23 | return ( 24 | 25 | 26 | 34 | {list.map((site) => ( 35 | ({ 38 | py: 2.5, 39 | display: 'flex', 40 | borderRadius: 1.5, 41 | textAlign: 'center', 42 | alignItems: 'center', 43 | flexDirection: 'column', 44 | border: `solid 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.12)}`, 45 | })} 46 | > 47 | {site.value === 'twitter' && } 48 | {site.value === 'facebook' && } 49 | {site.value === 'google' && } 50 | {site.value === 'linkedin' && } 51 | 52 | 53 | {fShortenNumber(site.total)} 54 | 55 | 56 | 57 | {site.label} 58 | 59 | 60 | ))} 61 | 62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-website-visits.tsx: -------------------------------------------------------------------------------- 1 | import type { CardProps } from '@mui/material/Card'; 2 | import type { ChartOptions } from 'src/components/chart'; 3 | 4 | import Card from '@mui/material/Card'; 5 | import CardHeader from '@mui/material/CardHeader'; 6 | import { useTheme, alpha as hexAlpha } from '@mui/material/styles'; 7 | 8 | import { Chart, useChart } from 'src/components/chart'; 9 | 10 | // ---------------------------------------------------------------------- 11 | 12 | type Props = CardProps & { 13 | title?: string; 14 | subheader?: string; 15 | chart: { 16 | colors?: string[]; 17 | categories?: string[]; 18 | series: { 19 | name: string; 20 | data: number[]; 21 | }[]; 22 | options?: ChartOptions; 23 | }; 24 | }; 25 | 26 | export function AnalyticsWebsiteVisits({ title, subheader, chart, sx, ...other }: Props) { 27 | const theme = useTheme(); 28 | 29 | const chartColors = chart.colors ?? [ 30 | hexAlpha(theme.palette.primary.dark, 0.8), 31 | hexAlpha(theme.palette.warning.main, 0.8), 32 | ]; 33 | 34 | const chartOptions = useChart({ 35 | colors: chartColors, 36 | stroke: { width: 2, colors: ['transparent'] }, 37 | xaxis: { categories: chart.categories }, 38 | legend: { show: true }, 39 | tooltip: { y: { formatter: (value: number) => `${value} visits` } }, 40 | ...chart.options, 41 | }); 42 | 43 | return ( 44 | 45 | 46 | 47 | 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/sections/overview/analytics-widget-summary.tsx: -------------------------------------------------------------------------------- 1 | import type { CardProps } from '@mui/material/Card'; 2 | import type { PaletteColorKey } from 'src/theme/core'; 3 | import type { ChartOptions } from 'src/components/chart'; 4 | 5 | import { varAlpha } from 'minimal-shared/utils'; 6 | 7 | import Box from '@mui/material/Box'; 8 | import Card from '@mui/material/Card'; 9 | import { useTheme } from '@mui/material/styles'; 10 | 11 | import { fNumber, fPercent, fShortenNumber } from 'src/utils/format-number'; 12 | 13 | import { Iconify } from 'src/components/iconify'; 14 | import { SvgColor } from 'src/components/svg-color'; 15 | import { Chart, useChart } from 'src/components/chart'; 16 | 17 | // ---------------------------------------------------------------------- 18 | 19 | type Props = CardProps & { 20 | title: string; 21 | total: number; 22 | percent: number; 23 | color?: PaletteColorKey; 24 | icon: React.ReactNode; 25 | chart: { 26 | series: number[]; 27 | categories: string[]; 28 | options?: ChartOptions; 29 | }; 30 | }; 31 | 32 | export function AnalyticsWidgetSummary({ 33 | sx, 34 | icon, 35 | title, 36 | total, 37 | chart, 38 | percent, 39 | color = 'primary', 40 | ...other 41 | }: Props) { 42 | const theme = useTheme(); 43 | 44 | const chartColors = [theme.palette[color].dark]; 45 | 46 | const chartOptions = useChart({ 47 | chart: { sparkline: { enabled: true } }, 48 | colors: chartColors, 49 | xaxis: { categories: chart.categories }, 50 | grid: { 51 | padding: { 52 | top: 6, 53 | left: 6, 54 | right: 6, 55 | bottom: 6, 56 | }, 57 | }, 58 | tooltip: { 59 | y: { formatter: (value: number) => fNumber(value), title: { formatter: () => '' } }, 60 | }, 61 | markers: { 62 | strokeWidth: 0, 63 | }, 64 | ...chart.options, 65 | }); 66 | 67 | const renderTrending = () => ( 68 | 78 | 79 | 80 | {percent > 0 && '+'} 81 | {fPercent(percent)} 82 | 83 | 84 | ); 85 | 86 | return ( 87 | ({ 90 | p: 3, 91 | boxShadow: 'none', 92 | position: 'relative', 93 | color: `${color}.darker`, 94 | backgroundColor: 'common.white', 95 | backgroundImage: `linear-gradient(135deg, ${varAlpha(theme.vars.palette[color].lighterChannel, 0.48)}, ${varAlpha(theme.vars.palette[color].lightChannel, 0.48)})`, 96 | }), 97 | ...(Array.isArray(sx) ? sx : [sx]), 98 | ]} 99 | {...other} 100 | > 101 | {icon} 102 | 103 | {renderTrending()} 104 | 105 | 113 | 114 | {title} 115 | 116 | {fShortenNumber(total)} 117 | 118 | 119 | 125 | 126 | 127 | 140 | 141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /src/sections/overview/view/index.ts: -------------------------------------------------------------------------------- 1 | export * from './overview-analytics-view'; 2 | -------------------------------------------------------------------------------- /src/sections/product/product-cart-widget.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps } from '@mui/material/Box'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import Badge from '@mui/material/Badge'; 5 | 6 | import { RouterLink } from 'src/routes/components'; 7 | 8 | import { Iconify } from 'src/components/iconify'; 9 | 10 | // ---------------------------------------------------------------------- 11 | 12 | type CartIconProps = BoxProps & { 13 | totalItems: number; 14 | }; 15 | 16 | export function CartIcon({ totalItems, sx, ...other }: CartIconProps) { 17 | return ( 18 | ({ 23 | right: 0, 24 | top: 112, 25 | zIndex: 999, 26 | display: 'flex', 27 | cursor: 'pointer', 28 | position: 'fixed', 29 | color: 'text.primary', 30 | borderTopLeftRadius: 16, 31 | borderBottomLeftRadius: 16, 32 | bgcolor: 'background.paper', 33 | padding: theme.spacing(1, 3, 1, 2), 34 | boxShadow: theme.vars.customShadows.dropdown, 35 | transition: theme.transitions.create(['opacity']), 36 | '&:hover': { opacity: 0.72 }, 37 | }), 38 | ...(Array.isArray(sx) ? sx : [sx]), 39 | ]} 40 | {...other} 41 | > 42 | 43 | 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/sections/product/product-item.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import Link from '@mui/material/Link'; 3 | import Card from '@mui/material/Card'; 4 | import Stack from '@mui/material/Stack'; 5 | import Typography from '@mui/material/Typography'; 6 | 7 | import { fCurrency } from 'src/utils/format-number'; 8 | 9 | import { Label } from 'src/components/label'; 10 | import { ColorPreview } from 'src/components/color-utils'; 11 | 12 | // ---------------------------------------------------------------------- 13 | 14 | export type ProductItemProps = { 15 | id: string; 16 | name: string; 17 | price: number; 18 | status: string; 19 | coverUrl: string; 20 | colors: string[]; 21 | priceSale: number | null; 22 | }; 23 | 24 | export function ProductItem({ product }: { product: ProductItemProps }) { 25 | const renderStatus = ( 26 | 39 | ); 40 | 41 | const renderImg = ( 42 | 54 | ); 55 | 56 | const renderPrice = ( 57 | 58 | 66 | {product.priceSale && fCurrency(product.priceSale)} 67 | 68 |   69 | {fCurrency(product.price)} 70 | 71 | ); 72 | 73 | return ( 74 | 75 | 76 | {product.status && renderStatus} 77 | {renderImg} 78 | 79 | 80 | 81 | 82 | {product.name} 83 | 84 | 85 | 92 | 93 | {renderPrice} 94 | 95 | 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/sections/product/product-sort.tsx: -------------------------------------------------------------------------------- 1 | import type { ButtonProps } from '@mui/material/Button'; 2 | 3 | import { useState, useCallback } from 'react'; 4 | 5 | import Button from '@mui/material/Button'; 6 | import Popover from '@mui/material/Popover'; 7 | import MenuList from '@mui/material/MenuList'; 8 | import Typography from '@mui/material/Typography'; 9 | import MenuItem, { menuItemClasses } from '@mui/material/MenuItem'; 10 | 11 | import { Iconify } from 'src/components/iconify'; 12 | 13 | // ---------------------------------------------------------------------- 14 | 15 | type ProductSortProps = ButtonProps & { 16 | sortBy: string; 17 | onSort: (newSort: string) => void; 18 | options: { value: string; label: string }[]; 19 | }; 20 | 21 | export function ProductSort({ options, sortBy, onSort, sx, ...other }: ProductSortProps) { 22 | const [openPopover, setOpenPopover] = useState(null); 23 | 24 | const handleOpenPopover = useCallback((event: React.MouseEvent) => { 25 | setOpenPopover(event.currentTarget); 26 | }, []); 27 | 28 | const handleClosePopover = useCallback(() => { 29 | setOpenPopover(null); 30 | }, []); 31 | 32 | return ( 33 | <> 34 | 51 | 52 | 59 | 75 | {options.map((option) => ( 76 | { 80 | onSort(option.value); 81 | handleClosePopover(); 82 | }} 83 | > 84 | {option.label} 85 | 86 | ))} 87 | 88 | 89 | 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/sections/product/view/index.ts: -------------------------------------------------------------------------------- 1 | export * from './products-view'; 2 | -------------------------------------------------------------------------------- /src/sections/user/table-empty-rows.tsx: -------------------------------------------------------------------------------- 1 | import type { TableRowProps } from '@mui/material/TableRow'; 2 | 3 | import TableRow from '@mui/material/TableRow'; 4 | import TableCell from '@mui/material/TableCell'; 5 | 6 | // ---------------------------------------------------------------------- 7 | 8 | type TableEmptyRowsProps = TableRowProps & { 9 | emptyRows: number; 10 | height?: number; 11 | }; 12 | 13 | export function TableEmptyRows({ emptyRows, height, sx, ...other }: TableEmptyRowsProps) { 14 | if (!emptyRows) { 15 | return null; 16 | } 17 | 18 | return ( 19 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/sections/user/table-no-data.tsx: -------------------------------------------------------------------------------- 1 | import type { TableRowProps } from '@mui/material/TableRow'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import TableRow from '@mui/material/TableRow'; 5 | import TableCell from '@mui/material/TableCell'; 6 | import Typography from '@mui/material/Typography'; 7 | 8 | // ---------------------------------------------------------------------- 9 | 10 | type TableNoDataProps = TableRowProps & { 11 | searchQuery: string; 12 | }; 13 | 14 | export function TableNoData({ searchQuery, ...other }: TableNoDataProps) { 15 | return ( 16 | 17 | 18 | 19 | 20 | Not found 21 | 22 | 23 | 24 | No results found for   25 | "{searchQuery}". 26 |
Try checking for typos or using complete words. 27 |
28 |
29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/sections/user/user-table-head.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import TableRow from '@mui/material/TableRow'; 3 | import Checkbox from '@mui/material/Checkbox'; 4 | import TableHead from '@mui/material/TableHead'; 5 | import TableCell from '@mui/material/TableCell'; 6 | import TableSortLabel from '@mui/material/TableSortLabel'; 7 | 8 | import { visuallyHidden } from './utils'; 9 | 10 | // ---------------------------------------------------------------------- 11 | 12 | type UserTableHeadProps = { 13 | orderBy: string; 14 | rowCount: number; 15 | numSelected: number; 16 | order: 'asc' | 'desc'; 17 | onSort: (id: string) => void; 18 | headLabel: Record[]; 19 | onSelectAllRows: (checked: boolean) => void; 20 | }; 21 | 22 | export function UserTableHead({ 23 | order, 24 | onSort, 25 | orderBy, 26 | rowCount, 27 | headLabel, 28 | numSelected, 29 | onSelectAllRows, 30 | }: UserTableHeadProps) { 31 | return ( 32 | 33 | 34 | 35 | 0 && numSelected < rowCount} 37 | checked={rowCount > 0 && numSelected === rowCount} 38 | onChange={(event: React.ChangeEvent) => 39 | onSelectAllRows(event.target.checked) 40 | } 41 | /> 42 | 43 | 44 | {headLabel.map((headCell) => ( 45 | 51 | onSort(headCell.id)} 56 | > 57 | {headCell.label} 58 | {orderBy === headCell.id ? ( 59 | 60 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 61 | 62 | ) : null} 63 | 64 | 65 | ))} 66 | 67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/sections/user/user-table-row.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | import Box from '@mui/material/Box'; 4 | import Avatar from '@mui/material/Avatar'; 5 | import Popover from '@mui/material/Popover'; 6 | import TableRow from '@mui/material/TableRow'; 7 | import Checkbox from '@mui/material/Checkbox'; 8 | import MenuList from '@mui/material/MenuList'; 9 | import TableCell from '@mui/material/TableCell'; 10 | import IconButton from '@mui/material/IconButton'; 11 | import MenuItem, { menuItemClasses } from '@mui/material/MenuItem'; 12 | 13 | import { Label } from 'src/components/label'; 14 | import { Iconify } from 'src/components/iconify'; 15 | 16 | // ---------------------------------------------------------------------- 17 | 18 | export type UserProps = { 19 | id: string; 20 | name: string; 21 | role: string; 22 | status: string; 23 | company: string; 24 | avatarUrl: string; 25 | isVerified: boolean; 26 | }; 27 | 28 | type UserTableRowProps = { 29 | row: UserProps; 30 | selected: boolean; 31 | onSelectRow: () => void; 32 | }; 33 | 34 | export function UserTableRow({ row, selected, onSelectRow }: UserTableRowProps) { 35 | const [openPopover, setOpenPopover] = useState(null); 36 | 37 | const handleOpenPopover = useCallback((event: React.MouseEvent) => { 38 | setOpenPopover(event.currentTarget); 39 | }, []); 40 | 41 | const handleClosePopover = useCallback(() => { 42 | setOpenPopover(null); 43 | }, []); 44 | 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | 61 | {row.name} 62 | 63 | 64 | 65 | {row.company} 66 | 67 | {row.role} 68 | 69 | 70 | {row.isVerified ? ( 71 | 72 | ) : ( 73 | '-' 74 | )} 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 95 | 111 | 112 | 113 | Edit 114 | 115 | 116 | 117 | 118 | Delete 119 | 120 | 121 | 122 | 123 | ); 124 | } 125 | -------------------------------------------------------------------------------- /src/sections/user/user-table-toolbar.tsx: -------------------------------------------------------------------------------- 1 | import Tooltip from '@mui/material/Tooltip'; 2 | import Toolbar from '@mui/material/Toolbar'; 3 | import Typography from '@mui/material/Typography'; 4 | import IconButton from '@mui/material/IconButton'; 5 | import OutlinedInput from '@mui/material/OutlinedInput'; 6 | import InputAdornment from '@mui/material/InputAdornment'; 7 | 8 | import { Iconify } from 'src/components/iconify'; 9 | 10 | // ---------------------------------------------------------------------- 11 | 12 | type UserTableToolbarProps = { 13 | numSelected: number; 14 | filterName: string; 15 | onFilterName: (event: React.ChangeEvent) => void; 16 | }; 17 | 18 | export function UserTableToolbar({ numSelected, filterName, onFilterName }: UserTableToolbarProps) { 19 | return ( 20 | theme.spacing(0, 1, 0, 3), 26 | ...(numSelected > 0 && { 27 | color: 'primary.main', 28 | bgcolor: 'primary.lighter', 29 | }), 30 | }} 31 | > 32 | {numSelected > 0 ? ( 33 | 34 | {numSelected} selected 35 | 36 | ) : ( 37 | 44 | 45 | 46 | } 47 | sx={{ maxWidth: 320 }} 48 | /> 49 | )} 50 | 51 | {numSelected > 0 ? ( 52 | 53 | 54 | 55 | 56 | 57 | ) : ( 58 | 59 | 60 | 61 | 62 | 63 | )} 64 | 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/sections/user/utils.ts: -------------------------------------------------------------------------------- 1 | import type { UserProps } from './user-table-row'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export const visuallyHidden = { 6 | border: 0, 7 | margin: -1, 8 | padding: 0, 9 | width: '1px', 10 | height: '1px', 11 | overflow: 'hidden', 12 | position: 'absolute', 13 | whiteSpace: 'nowrap', 14 | clip: 'rect(0 0 0 0)', 15 | } as const; 16 | 17 | // ---------------------------------------------------------------------- 18 | 19 | export function emptyRows(page: number, rowsPerPage: number, arrayLength: number) { 20 | return page ? Math.max(0, (1 + page) * rowsPerPage - arrayLength) : 0; 21 | } 22 | 23 | // ---------------------------------------------------------------------- 24 | 25 | function descendingComparator(a: T, b: T, orderBy: keyof T) { 26 | if (b[orderBy] < a[orderBy]) { 27 | return -1; 28 | } 29 | if (b[orderBy] > a[orderBy]) { 30 | return 1; 31 | } 32 | return 0; 33 | } 34 | 35 | // ---------------------------------------------------------------------- 36 | 37 | export function getComparator( 38 | order: 'asc' | 'desc', 39 | orderBy: Key 40 | ): ( 41 | a: { 42 | [key in Key]: number | string; 43 | }, 44 | b: { 45 | [key in Key]: number | string; 46 | } 47 | ) => number { 48 | return order === 'desc' 49 | ? (a, b) => descendingComparator(a, b, orderBy) 50 | : (a, b) => -descendingComparator(a, b, orderBy); 51 | } 52 | 53 | // ---------------------------------------------------------------------- 54 | 55 | type ApplyFilterProps = { 56 | inputData: UserProps[]; 57 | filterName: string; 58 | comparator: (a: any, b: any) => number; 59 | }; 60 | 61 | export function applyFilter({ inputData, comparator, filterName }: ApplyFilterProps) { 62 | const stabilizedThis = inputData.map((el, index) => [el, index] as const); 63 | 64 | stabilizedThis.sort((a, b) => { 65 | const order = comparator(a[0], b[0]); 66 | if (order !== 0) return order; 67 | return a[1] - b[1]; 68 | }); 69 | 70 | inputData = stabilizedThis.map((el) => el[0]); 71 | 72 | if (filterName) { 73 | inputData = inputData.filter( 74 | (user) => user.name.toLowerCase().indexOf(filterName.toLowerCase()) !== -1 75 | ); 76 | } 77 | 78 | return inputData; 79 | } 80 | -------------------------------------------------------------------------------- /src/sections/user/view/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user-view'; 2 | -------------------------------------------------------------------------------- /src/theme/core/custom-shadows.ts: -------------------------------------------------------------------------------- 1 | import { varAlpha } from 'minimal-shared/utils'; 2 | 3 | import { grey, info, error, common, primary, success, warning, secondary } from './palette'; 4 | 5 | import type { ThemeColorScheme } from '../types'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | /** 10 | * TypeScript (type definition and extension) 11 | * @to {@link file://./../extend-theme-types.d.ts} 12 | */ 13 | 14 | export interface CustomShadows { 15 | z1?: string; 16 | z4?: string; 17 | z8?: string; 18 | z12?: string; 19 | z16?: string; 20 | z20?: string; 21 | z24?: string; 22 | primary?: string; 23 | secondary?: string; 24 | info?: string; 25 | success?: string; 26 | warning?: string; 27 | error?: string; 28 | card?: string; 29 | dialog?: string; 30 | dropdown?: string; 31 | } 32 | 33 | // ---------------------------------------------------------------------- 34 | 35 | export function createShadowColor(colorChannel: string): string { 36 | return `0 8px 16px 0 ${varAlpha(colorChannel, 0.24)}`; 37 | } 38 | 39 | function createCustomShadows(colorChannel: string): CustomShadows { 40 | return { 41 | z1: `0 1px 2px 0 ${varAlpha(colorChannel, 0.16)}`, 42 | z4: `0 4px 8px 0 ${varAlpha(colorChannel, 0.16)}`, 43 | z8: `0 8px 16px 0 ${varAlpha(colorChannel, 0.16)}`, 44 | z12: `0 12px 24px -4px ${varAlpha(colorChannel, 0.16)}`, 45 | z16: `0 16px 32px -4px ${varAlpha(colorChannel, 0.16)}`, 46 | z20: `0 20px 40px -4px ${varAlpha(colorChannel, 0.16)}`, 47 | z24: `0 24px 48px 0 ${varAlpha(colorChannel, 0.16)}`, 48 | /********/ 49 | dialog: `-40px 40px 80px -8px ${varAlpha(common.blackChannel, 0.24)}`, 50 | card: `0 0 2px 0 ${varAlpha(colorChannel, 0.2)}, 0 12px 24px -4px ${varAlpha(colorChannel, 0.12)}`, 51 | dropdown: `0 0 2px 0 ${varAlpha(colorChannel, 0.24)}, -20px 20px 40px -4px ${varAlpha(colorChannel, 0.24)}`, 52 | /********/ 53 | primary: createShadowColor(primary.mainChannel), 54 | secondary: createShadowColor(secondary.mainChannel), 55 | info: createShadowColor(info.mainChannel), 56 | success: createShadowColor(success.mainChannel), 57 | warning: createShadowColor(warning.mainChannel), 58 | error: createShadowColor(error.mainChannel), 59 | }; 60 | } 61 | 62 | export const customShadows: Partial> = { 63 | light: createCustomShadows(grey['500Channel']), 64 | }; 65 | -------------------------------------------------------------------------------- /src/theme/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shadows'; 2 | 3 | export * from './palette'; 4 | 5 | export * from './typography'; 6 | 7 | export * from './components'; 8 | 9 | export * from './custom-shadows'; 10 | -------------------------------------------------------------------------------- /src/theme/core/shadows.ts: -------------------------------------------------------------------------------- 1 | import type { Shadows } from '@mui/material/styles'; 2 | 3 | import { varAlpha } from 'minimal-shared/utils'; 4 | 5 | import { grey } from './palette'; 6 | 7 | import type { ThemeColorScheme } from '../types'; 8 | 9 | // ---------------------------------------------------------------------- 10 | 11 | function createShadows(colorChannel: string): Shadows { 12 | const color1 = varAlpha(colorChannel, 0.2); 13 | const color2 = varAlpha(colorChannel, 0.14); 14 | const color3 = varAlpha(colorChannel, 0.12); 15 | 16 | return [ 17 | 'none', 18 | `0px 2px 1px -1px ${color1},0px 1px 1px 0px ${color2},0px 1px 3px 0px ${color3}`, 19 | `0px 3px 1px -2px ${color1},0px 2px 2px 0px ${color2},0px 1px 5px 0px ${color3}`, 20 | `0px 3px 3px -2px ${color1},0px 3px 4px 0px ${color2},0px 1px 8px 0px ${color3}`, 21 | `0px 2px 4px -1px ${color1},0px 4px 5px 0px ${color2},0px 1px 10px 0px ${color3}`, 22 | `0px 3px 5px -1px ${color1},0px 5px 8px 0px ${color2},0px 1px 14px 0px ${color3}`, 23 | `0px 3px 5px -1px ${color1},0px 6px 10px 0px ${color2},0px 1px 18px 0px ${color3}`, 24 | `0px 4px 5px -2px ${color1},0px 7px 10px 1px ${color2},0px 2px 16px 1px ${color3}`, 25 | `0px 5px 5px -3px ${color1},0px 8px 10px 1px ${color2},0px 3px 14px 2px ${color3}`, 26 | `0px 5px 6px -3px ${color1},0px 9px 12px 1px ${color2},0px 3px 16px 2px ${color3}`, 27 | `0px 6px 6px -3px ${color1},0px 10px 14px 1px ${color2},0px 4px 18px 3px ${color3}`, 28 | `0px 6px 7px -4px ${color1},0px 11px 15px 1px ${color2},0px 4px 20px 3px ${color3}`, 29 | `0px 7px 8px -4px ${color1},0px 12px 17px 2px ${color2},0px 5px 22px 4px ${color3}`, 30 | `0px 7px 8px -4px ${color1},0px 13px 19px 2px ${color2},0px 5px 24px 4px ${color3}`, 31 | `0px 7px 9px -4px ${color1},0px 14px 21px 2px ${color2},0px 5px 26px 4px ${color3}`, 32 | `0px 8px 9px -5px ${color1},0px 15px 22px 2px ${color2},0px 6px 28px 5px ${color3}`, 33 | `0px 8px 10px -5px ${color1},0px 16px 24px 2px ${color2},0px 6px 30px 5px ${color3}`, 34 | `0px 8px 11px -5px ${color1},0px 17px 26px 2px ${color2},0px 6px 32px 5px ${color3}`, 35 | `0px 9px 11px -5px ${color1},0px 18px 28px 2px ${color2},0px 7px 34px 6px ${color3}`, 36 | `0px 9px 12px -6px ${color1},0px 19px 29px 2px ${color2},0px 7px 36px 6px ${color3}`, 37 | `0px 10px 13px -6px ${color1},0px 20px 31px 3px ${color2},0px 8px 38px 7px ${color3}`, 38 | `0px 10px 13px -6px ${color1},0px 21px 33px 3px ${color2},0px 8px 40px 7px ${color3}`, 39 | `0px 10px 14px -6px ${color1},0px 22px 35px 3px ${color2},0px 8px 42px 7px ${color3}`, 40 | `0px 11px 14px -7px ${color1},0px 23px 36px 3px ${color2},0px 9px 44px 8px ${color3}`, 41 | `0px 11px 15px -7px ${color1},0px 24px 38px 3px ${color2},0px 9px 46px 8px ${color3}`, 42 | ]; 43 | } 44 | 45 | export const shadows: Partial> = { 46 | light: createShadows(grey['500Channel']), 47 | }; 48 | -------------------------------------------------------------------------------- /src/theme/core/typography.ts: -------------------------------------------------------------------------------- 1 | import type { CSSObject, Breakpoint, TypographyVariantsOptions } from '@mui/material/styles'; 2 | 3 | import { pxToRem, setFont } from 'minimal-shared/utils'; 4 | 5 | import { createTheme as getTheme } from '@mui/material/styles'; 6 | 7 | import { themeConfig } from '../theme-config'; 8 | 9 | // ---------------------------------------------------------------------- 10 | 11 | /** 12 | * TypeScript (type definition and extension) 13 | * @to {@link file://./../extend-theme-types.d.ts} 14 | */ 15 | export type FontStyleExtend = { 16 | fontWeightSemiBold: CSSObject['fontWeight']; 17 | fontSecondaryFamily: CSSObject['fontFamily']; 18 | }; 19 | 20 | export type ResponsiveFontSizesInput = Partial>; 21 | export type ResponsiveFontSizesResult = Record; 22 | 23 | const defaultMuiTheme = getTheme(); 24 | 25 | function responsiveFontSizes(obj: ResponsiveFontSizesInput): ResponsiveFontSizesResult { 26 | const breakpoints: Breakpoint[] = defaultMuiTheme.breakpoints.keys; 27 | 28 | return breakpoints.reduce((acc, breakpoint) => { 29 | const value = obj[breakpoint]; 30 | 31 | if (value !== undefined && value >= 0) { 32 | acc[defaultMuiTheme.breakpoints.up(breakpoint)] = { 33 | fontSize: pxToRem(value), 34 | }; 35 | } 36 | 37 | return acc; 38 | }, {} as ResponsiveFontSizesResult); 39 | } 40 | 41 | // ---------------------------------------------------------------------- 42 | 43 | const primaryFont = setFont(themeConfig.fontFamily.primary); 44 | const secondaryFont = setFont(themeConfig.fontFamily.secondary); 45 | 46 | export const typography: TypographyVariantsOptions = { 47 | fontFamily: primaryFont, 48 | fontSecondaryFamily: secondaryFont, 49 | fontWeightLight: '300', 50 | fontWeightRegular: '400', 51 | fontWeightMedium: '500', 52 | fontWeightSemiBold: '600', 53 | fontWeightBold: '700', 54 | h1: { 55 | fontFamily: secondaryFont, 56 | fontWeight: 800, 57 | lineHeight: 80 / 64, 58 | fontSize: pxToRem(40), 59 | ...responsiveFontSizes({ sm: 52, md: 58, lg: 64 }), 60 | }, 61 | h2: { 62 | fontFamily: secondaryFont, 63 | fontWeight: 800, 64 | lineHeight: 64 / 48, 65 | fontSize: pxToRem(32), 66 | ...responsiveFontSizes({ sm: 40, md: 44, lg: 48 }), 67 | }, 68 | h3: { 69 | fontFamily: secondaryFont, 70 | fontWeight: 700, 71 | lineHeight: 1.5, 72 | fontSize: pxToRem(24), 73 | ...responsiveFontSizes({ sm: 26, md: 30, lg: 32 }), 74 | }, 75 | h4: { 76 | fontWeight: 700, 77 | lineHeight: 1.5, 78 | fontSize: pxToRem(20), 79 | ...responsiveFontSizes({ md: 24 }), 80 | }, 81 | h5: { 82 | fontWeight: 700, 83 | lineHeight: 1.5, 84 | fontSize: pxToRem(18), 85 | ...responsiveFontSizes({ sm: 19 }), 86 | }, 87 | h6: { 88 | fontWeight: 600, 89 | lineHeight: 28 / 18, 90 | fontSize: pxToRem(17), 91 | ...responsiveFontSizes({ sm: 18 }), 92 | }, 93 | subtitle1: { 94 | fontWeight: 600, 95 | lineHeight: 1.5, 96 | fontSize: pxToRem(16), 97 | }, 98 | subtitle2: { 99 | fontWeight: 600, 100 | lineHeight: 22 / 14, 101 | fontSize: pxToRem(14), 102 | }, 103 | body1: { 104 | lineHeight: 1.5, 105 | fontSize: pxToRem(16), 106 | }, 107 | body2: { 108 | lineHeight: 22 / 14, 109 | fontSize: pxToRem(14), 110 | }, 111 | caption: { 112 | lineHeight: 1.5, 113 | fontSize: pxToRem(12), 114 | }, 115 | overline: { 116 | fontWeight: 700, 117 | lineHeight: 1.5, 118 | fontSize: pxToRem(12), 119 | textTransform: 'uppercase', 120 | }, 121 | button: { 122 | fontWeight: 700, 123 | lineHeight: 24 / 14, 124 | fontSize: pxToRem(14), 125 | textTransform: 'unset', 126 | }, 127 | }; 128 | -------------------------------------------------------------------------------- /src/theme/create-classes.ts: -------------------------------------------------------------------------------- 1 | import { themeConfig } from './theme-config'; 2 | 3 | // ---------------------------------------------------------------------- 4 | 5 | export function createClasses(className: string): string { 6 | return `${themeConfig.classesPrefix}__${className}`; 7 | } 8 | -------------------------------------------------------------------------------- /src/theme/create-theme.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from '@mui/material/styles'; 2 | 3 | import { createTheme as createMuiTheme } from '@mui/material/styles'; 4 | 5 | import { shadows } from './core/shadows'; 6 | import { palette } from './core/palette'; 7 | import { themeConfig } from './theme-config'; 8 | import { components } from './core/components'; 9 | import { typography } from './core/typography'; 10 | import { customShadows } from './core/custom-shadows'; 11 | 12 | import type { ThemeOptions } from './types'; 13 | 14 | // ---------------------------------------------------------------------- 15 | 16 | export const baseTheme: ThemeOptions = { 17 | colorSchemes: { 18 | light: { 19 | palette: palette.light, 20 | shadows: shadows.light, 21 | customShadows: customShadows.light, 22 | }, 23 | }, 24 | components, 25 | typography, 26 | shape: { borderRadius: 8 }, 27 | cssVariables: themeConfig.cssVariables, 28 | }; 29 | 30 | // ---------------------------------------------------------------------- 31 | 32 | type CreateThemeProps = { 33 | themeOverrides?: ThemeOptions; 34 | }; 35 | 36 | export function createTheme({ themeOverrides = {} }: CreateThemeProps = {}): Theme { 37 | const theme = createMuiTheme(baseTheme, themeOverrides); 38 | 39 | return theme; 40 | } 41 | -------------------------------------------------------------------------------- /src/theme/extend-theme-types.d.ts: -------------------------------------------------------------------------------- 1 | import type {} from '@mui/lab/themeAugmentation'; 2 | import type {} from '@mui/material/themeCssVarsAugmentation'; 3 | 4 | import type { FontStyleExtend } from './core/typography'; 5 | import type { CustomShadows } from './core/custom-shadows'; 6 | import type { 7 | GreyExtend, 8 | TypeTextExtend, 9 | CommonColorsExtend, 10 | PaletteColorExtend, 11 | TypeBackgroundExtend, 12 | } from './core/palette'; 13 | 14 | // ---------------------------------------------------------------------- 15 | 16 | /** ************************************** 17 | * EXTEND CORE 18 | * Palette, typography, shadows... 19 | *************************************** */ 20 | 21 | /** 22 | * Palette 23 | * https://mui.com/customization/palette/ 24 | * @from {@link file://./core/palette.ts} 25 | */ 26 | declare module '@mui/material/styles' { 27 | // grey 28 | interface Color extends GreyExtend {} 29 | // text 30 | interface TypeText extends TypeTextExtend {} 31 | // black & white 32 | interface CommonColors extends CommonColorsExtend {} 33 | // background 34 | interface TypeBackground extends TypeBackgroundExtend {} 35 | // primary, secondary, info, success, warning, error 36 | interface PaletteColor extends PaletteColorExtend {} 37 | interface SimplePaletteColorOptions extends Partial {} 38 | } 39 | 40 | /** 41 | * Typography 42 | * https://mui.com/customization/typography/ 43 | * @from {@link file://./core/typography.ts} 44 | */ 45 | declare module '@mui/material/styles' { 46 | interface TypographyVariants extends FontStyleExtend {} 47 | interface TypographyVariantsOptions extends Partial {} 48 | } 49 | 50 | declare module '@mui/material/styles' { 51 | /** 52 | * Custom shadows 53 | * @from {@link file://./core/custom-shadows.ts} 54 | */ 55 | interface Theme { 56 | customShadows: CustomShadows; 57 | } 58 | interface ThemeOptions { 59 | customShadows?: CustomShadows; 60 | } 61 | interface ThemeVars { 62 | customShadows: CustomShadows; 63 | typography: Theme['typography']; 64 | transitions: Theme['transitions']; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/theme/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core'; 2 | 3 | export * from './types'; 4 | 5 | export * from './theme-config'; 6 | 7 | export * from './theme-provider'; 8 | -------------------------------------------------------------------------------- /src/theme/theme-config.ts: -------------------------------------------------------------------------------- 1 | import type { CommonColors } from '@mui/material/styles'; 2 | 3 | import type { ThemeCssVariables } from './types'; 4 | import type { PaletteColorNoChannels } from './core/palette'; 5 | 6 | // ---------------------------------------------------------------------- 7 | 8 | type ThemeConfig = { 9 | classesPrefix: string; 10 | cssVariables: ThemeCssVariables; 11 | fontFamily: Record<'primary' | 'secondary', string>; 12 | palette: Record< 13 | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error', 14 | PaletteColorNoChannels 15 | > & { 16 | common: Pick; 17 | grey: Record< 18 | '50' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900', 19 | string 20 | >; 21 | }; 22 | }; 23 | 24 | export const themeConfig: ThemeConfig = { 25 | /** ************************************** 26 | * Base 27 | *************************************** */ 28 | classesPrefix: 'minimal', 29 | /** ************************************** 30 | * Typography 31 | *************************************** */ 32 | fontFamily: { 33 | primary: 'DM Sans Variable', 34 | secondary: 'Barlow', 35 | }, 36 | /** ************************************** 37 | * Palette 38 | *************************************** */ 39 | palette: { 40 | primary: { 41 | lighter: '#D0ECFE', 42 | light: '#73BAFB', 43 | main: '#1877F2', 44 | dark: '#0C44AE', 45 | darker: '#042174', 46 | contrastText: '#FFFFFF', 47 | }, 48 | secondary: { 49 | lighter: '#EFD6FF', 50 | light: '#C684FF', 51 | main: '#8E33FF', 52 | dark: '#5119B7', 53 | darker: '#27097A', 54 | contrastText: '#FFFFFF', 55 | }, 56 | info: { 57 | lighter: '#CAFDF5', 58 | light: '#61F3F3', 59 | main: '#00B8D9', 60 | dark: '#006C9C', 61 | darker: '#003768', 62 | contrastText: '#FFFFFF', 63 | }, 64 | success: { 65 | lighter: '#D3FCD2', 66 | light: '#77ED8B', 67 | main: '#22C55E', 68 | dark: '#118D57', 69 | darker: '#065E49', 70 | contrastText: '#ffffff', 71 | }, 72 | warning: { 73 | lighter: '#FFF5CC', 74 | light: '#FFD666', 75 | main: '#FFAB00', 76 | dark: '#B76E00', 77 | darker: '#7A4100', 78 | contrastText: '#1C252E', 79 | }, 80 | error: { 81 | lighter: '#FFE9D5', 82 | light: '#FFAC82', 83 | main: '#FF5630', 84 | dark: '#B71D18', 85 | darker: '#7A0916', 86 | contrastText: '#FFFFFF', 87 | }, 88 | grey: { 89 | '50': '#FCFDFD', 90 | '100': '#F9FAFB', 91 | '200': '#F4F6F8', 92 | '300': '#DFE3E8', 93 | '400': '#C4CDD5', 94 | '500': '#919EAB', 95 | '600': '#637381', 96 | '700': '#454F5B', 97 | '800': '#1C252E', 98 | '900': '#141A21', 99 | }, 100 | common: { black: '#000000', white: '#FFFFFF' }, 101 | }, 102 | /** ************************************** 103 | * Css variables 104 | *************************************** */ 105 | cssVariables: { 106 | cssVarPrefix: '', 107 | colorSchemeSelector: 'data-color-scheme', 108 | }, 109 | }; 110 | -------------------------------------------------------------------------------- /src/theme/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | import type { ThemeProviderProps as MuiThemeProviderProps } from '@mui/material/styles'; 2 | 3 | import CssBaseline from '@mui/material/CssBaseline'; 4 | import { ThemeProvider as ThemeVarsProvider } from '@mui/material/styles'; 5 | 6 | import { createTheme } from './create-theme'; 7 | 8 | import type {} from './extend-theme-types'; 9 | import type { ThemeOptions } from './types'; 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | export type ThemeProviderProps = Partial & { 14 | themeOverrides?: ThemeOptions; 15 | }; 16 | 17 | export function ThemeProvider({ themeOverrides, children, ...other }: ThemeProviderProps) { 18 | const theme = createTheme({ 19 | themeOverrides, 20 | }); 21 | 22 | return ( 23 | 24 | 25 | {children} 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/theme/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Shadows, 3 | ColorSystemOptions, 4 | CssVarsThemeOptions, 5 | SupportedColorScheme, 6 | ThemeOptions as MuiThemeOptions, 7 | } from '@mui/material/styles'; 8 | 9 | import type { CustomShadows } from './core/custom-shadows'; 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | /** 14 | * Theme options 15 | * Extended type that includes additional properties for color schemes and CSS variables. 16 | * 17 | * @see https://github.com/mui/material-ui/blob/master/packages/mui-material/src/styles/createTheme.ts 18 | */ 19 | 20 | export type ThemeColorScheme = SupportedColorScheme; 21 | export type ThemeCssVariables = Pick< 22 | CssVarsThemeOptions, 23 | 'colorSchemeSelector' | 'disableCssColorScheme' | 'cssVarPrefix' | 'shouldSkipGeneratingVar' 24 | >; 25 | 26 | type ColorSchemeOptionsExtended = ColorSystemOptions & { 27 | shadows?: Shadows; 28 | customShadows?: CustomShadows; 29 | }; 30 | 31 | export type ThemeOptions = Omit & 32 | Pick & { 33 | colorSchemes?: Partial>; 34 | cssVariables?: ThemeCssVariables; 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/format-number.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Locales code 3 | * https://gist.github.com/raushankrjha/d1c7e35cf87e69aa8b4208a8171a8416 4 | */ 5 | 6 | export type InputNumberValue = string | number | null | undefined; 7 | 8 | type Options = Intl.NumberFormatOptions; 9 | 10 | const DEFAULT_LOCALE = { code: 'en-US', currency: 'USD' }; 11 | 12 | function processInput(inputValue: InputNumberValue): number | null { 13 | if (inputValue == null || Number.isNaN(inputValue)) return null; 14 | return Number(inputValue); 15 | } 16 | 17 | // ---------------------------------------------------------------------- 18 | 19 | export function fNumber(inputValue: InputNumberValue, options?: Options) { 20 | const locale = DEFAULT_LOCALE; 21 | 22 | const number = processInput(inputValue); 23 | if (number === null) return ''; 24 | 25 | const fm = new Intl.NumberFormat(locale.code, { 26 | minimumFractionDigits: 0, 27 | maximumFractionDigits: 2, 28 | ...options, 29 | }).format(number); 30 | 31 | return fm; 32 | } 33 | 34 | // ---------------------------------------------------------------------- 35 | 36 | export function fCurrency(inputValue: InputNumberValue, options?: Options) { 37 | const locale = DEFAULT_LOCALE; 38 | 39 | const number = processInput(inputValue); 40 | if (number === null) return ''; 41 | 42 | const fm = new Intl.NumberFormat(locale.code, { 43 | style: 'currency', 44 | currency: locale.currency, 45 | minimumFractionDigits: 0, 46 | maximumFractionDigits: 2, 47 | ...options, 48 | }).format(number); 49 | 50 | return fm; 51 | } 52 | 53 | // ---------------------------------------------------------------------- 54 | 55 | export function fPercent(inputValue: InputNumberValue, options?: Options) { 56 | const locale = DEFAULT_LOCALE; 57 | 58 | const number = processInput(inputValue); 59 | if (number === null) return ''; 60 | 61 | const fm = new Intl.NumberFormat(locale.code, { 62 | style: 'percent', 63 | minimumFractionDigits: 0, 64 | maximumFractionDigits: 1, 65 | ...options, 66 | }).format(number / 100); 67 | 68 | return fm; 69 | } 70 | 71 | // ---------------------------------------------------------------------- 72 | 73 | export function fShortenNumber(inputValue: InputNumberValue, options?: Options) { 74 | const locale = DEFAULT_LOCALE; 75 | 76 | const number = processInput(inputValue); 77 | if (number === null) return ''; 78 | 79 | const fm = new Intl.NumberFormat(locale.code, { 80 | notation: 'compact', 81 | maximumFractionDigits: 2, 82 | ...options, 83 | }).format(number); 84 | 85 | return fm.replace(/[A-Z]/g, (match) => match.toLowerCase()); 86 | } 87 | -------------------------------------------------------------------------------- /src/utils/format-time.ts: -------------------------------------------------------------------------------- 1 | import type { Dayjs } from 'dayjs'; 2 | 3 | import dayjs from 'dayjs'; 4 | import duration from 'dayjs/plugin/duration'; 5 | import relativeTime from 'dayjs/plugin/relativeTime'; 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | /** 10 | * @Docs 11 | * https://day.js.org/docs/en/display/format 12 | */ 13 | 14 | /** 15 | * Default timezones 16 | * https://day.js.org/docs/en/timezone/set-default-timezone#docsNav 17 | * 18 | */ 19 | 20 | /** 21 | * UTC 22 | * https://day.js.org/docs/en/plugin/utc 23 | * @install 24 | * import utc from 'dayjs/plugin/utc'; 25 | * dayjs.extend(utc); 26 | * @usage 27 | * dayjs().utc().format() 28 | * 29 | */ 30 | 31 | dayjs.extend(duration); 32 | dayjs.extend(relativeTime); 33 | 34 | // ---------------------------------------------------------------------- 35 | 36 | export type DatePickerFormat = Dayjs | Date | string | number | null | undefined; 37 | 38 | export const formatPatterns = { 39 | dateTime: 'DD MMM YYYY h:mm a', // 17 Apr 2022 12:00 am 40 | date: 'DD MMM YYYY', // 17 Apr 2022 41 | time: 'h:mm a', // 12:00 am 42 | split: { 43 | dateTime: 'DD/MM/YYYY h:mm a', // 17/04/2022 12:00 am 44 | date: 'DD/MM/YYYY', // 17/04/2022 45 | }, 46 | paramCase: { 47 | dateTime: 'DD-MM-YYYY h:mm a', // 17-04-2022 12:00 am 48 | date: 'DD-MM-YYYY', // 17-04-2022 49 | }, 50 | }; 51 | 52 | const isValidDate = (date: DatePickerFormat) => 53 | date !== null && date !== undefined && dayjs(date).isValid(); 54 | 55 | // ---------------------------------------------------------------------- 56 | 57 | /** 58 | * @output 17 Apr 2022 12:00 am 59 | */ 60 | export function fDateTime(date: DatePickerFormat, template?: string): string { 61 | if (!isValidDate(date)) { 62 | return 'Invalid date'; 63 | } 64 | 65 | return dayjs(date).format(template ?? formatPatterns.dateTime); 66 | } 67 | 68 | // ---------------------------------------------------------------------- 69 | 70 | /** 71 | * @output 17 Apr 2022 72 | */ 73 | export function fDate(date: DatePickerFormat, template?: string): string { 74 | if (!isValidDate(date)) { 75 | return 'Invalid date'; 76 | } 77 | 78 | return dayjs(date).format(template ?? formatPatterns.date); 79 | } 80 | 81 | // ---------------------------------------------------------------------- 82 | 83 | /** 84 | * @output a few seconds, 2 years 85 | */ 86 | export function fToNow(date: DatePickerFormat): string { 87 | if (!isValidDate(date)) { 88 | return 'Invalid date'; 89 | } 90 | 91 | return dayjs(date).toNow(true); 92 | } 93 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Bundler */ 4 | "baseUrl": ".", 5 | "module": "ESNext", 6 | "jsx": "react-jsx", 7 | "allowJs": true, 8 | "resolveJsonModule": true, 9 | 10 | /* Build */ 11 | "target": "ES2020", 12 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 13 | "moduleResolution": "bundler", 14 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 15 | "incremental": true, 16 | "skipLibCheck": true, 17 | "esModuleInterop": true, 18 | "isolatedModules": true, 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noEmit": true, 23 | "strictNullChecks": true 24 | }, 25 | "include": ["src"], 26 | "exclude": ["node_modules"], 27 | "references": [ 28 | { 29 | "path": "./tsconfig.node.json" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/(.*)", 5 | "destination": "/" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import checker from 'vite-plugin-checker'; 3 | import { defineConfig } from 'vite'; 4 | import react from '@vitejs/plugin-react-swc'; 5 | 6 | // ---------------------------------------------------------------------- 7 | 8 | const PORT = 3039; 9 | 10 | export default defineConfig({ 11 | plugins: [ 12 | react(), 13 | checker({ 14 | typescript: true, 15 | eslint: { 16 | useFlatConfig: true, 17 | lintCommand: 'eslint "./src/**/*.{js,jsx,ts,tsx}"', 18 | dev: { logLevel: ['error'] }, 19 | }, 20 | overlay: { 21 | position: 'tl', 22 | initialIsOpen: false, 23 | }, 24 | }), 25 | ], 26 | resolve: { 27 | alias: [ 28 | { 29 | find: /^src(.+)/, 30 | replacement: path.resolve(process.cwd(), 'src/$1'), 31 | }, 32 | ], 33 | }, 34 | server: { port: PORT, host: true }, 35 | preview: { port: PORT, host: true }, 36 | }); 37 | --------------------------------------------------------------------------------