├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── README.md ├── babel.config.js ├── gh-pages.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── preview-1.png ├── preview-2.png ├── public ├── _redirects └── favicon.ico ├── src ├── App.tsx ├── components │ ├── Brand.tsx │ ├── Copyright.tsx │ ├── ErrorPage │ │ ├── ErrorPage.tsx │ │ ├── index.ts │ │ └── styles.less │ ├── Frame │ │ ├── Frame.tsx │ │ ├── NavToggle.tsx │ │ ├── index.ts │ │ └── styles.less │ ├── Header │ │ ├── Header.tsx │ │ ├── index.ts │ │ └── styles.less │ ├── Logo │ │ ├── Logo.tsx │ │ ├── index.tsx │ │ └── styles.less │ ├── NavLink.tsx │ ├── PageContent.tsx │ ├── PageToolbar.tsx │ ├── RadioTile │ │ ├── RadioTile.tsx │ │ ├── index.tsx │ │ └── styles.less │ ├── Textarea.tsx │ └── index.ts ├── config.tsx ├── constants │ └── index.ts ├── data │ └── mock.ts ├── images │ ├── charts │ │ ├── index.ts │ │ ├── pv.svg │ │ ├── uv.svg │ │ └── vv.svg │ └── errors │ │ ├── 403.svg │ │ ├── 404.svg │ │ ├── 500.svg │ │ ├── 503.svg │ │ └── index.ts ├── index.html ├── index.tsx ├── locales │ ├── en-US │ │ └── index.ts │ ├── index.ts │ └── zh-CN │ │ └── index.ts ├── pages │ ├── authentication │ │ ├── 403 │ │ │ ├── Error403.tsx │ │ │ └── index.ts │ │ ├── 404 │ │ │ ├── Error404.tsx │ │ │ └── index.ts │ │ ├── 500 │ │ │ ├── Error500.tsx │ │ │ └── index.ts │ │ ├── 503 │ │ │ ├── Error503.tsx │ │ │ └── index.ts │ │ ├── sign-in │ │ │ ├── SignIn.tsx │ │ │ └── index.ts │ │ └── sign-up │ │ │ ├── SignUp.tsx │ │ │ └── index.ts │ ├── calendar │ │ ├── Calendar.tsx │ │ ├── EventModal.tsx │ │ ├── event-utils.tsx │ │ ├── index.tsx │ │ └── styles.less │ ├── dashboard │ │ ├── BarChart.tsx │ │ ├── ColorfulChart.tsx │ │ ├── Dashboard.tsx │ │ ├── DataTable.tsx │ │ ├── PieChart.tsx │ │ ├── index.tsx │ │ └── styles.less │ ├── forms │ │ ├── basic │ │ │ ├── BasicForm.tsx │ │ │ ├── index.tsx │ │ │ └── styles.less │ │ └── wizard │ │ │ ├── BusinessDetailForm.tsx │ │ │ ├── Completed.tsx │ │ │ ├── FormHeader.tsx │ │ │ ├── ProjectInfoForm.tsx │ │ │ ├── ProjectTypeForm.tsx │ │ │ ├── TeamSettingsForm.tsx │ │ │ ├── WizardForm.tsx │ │ │ ├── index.tsx │ │ │ └── styles.less │ └── tables │ │ ├── members │ │ ├── Cells.tsx │ │ ├── DataTable.tsx │ │ ├── DrawerView.tsx │ │ ├── index.tsx │ │ ├── styles.less │ │ └── users.ts │ │ └── virtualized │ │ ├── VirtualizedTable.tsx │ │ └── index.tsx ├── styles │ └── index.less └── utils │ ├── formatValue.ts │ ├── highlightValue.tsx │ ├── index.ts │ └── toThousands.ts ├── tsconfig.json ├── vercel.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const OFF = 0; 2 | const WARNING = 1; 3 | const ERROR = 2; 4 | 5 | module.exports = { 6 | env: { 7 | browser: true, 8 | es6: true 9 | }, 10 | parser: '@typescript-eslint/parser', 11 | extends: [ 12 | 'plugin:@typescript-eslint/recommended', 13 | 'plugin:react/recommended', 14 | 'plugin:react-hooks/recommended', 15 | 'prettier' 16 | ], 17 | parserOptions: {}, 18 | plugins: ['@typescript-eslint', 'react'], 19 | rules: { 20 | semi: [ERROR, 'always'], 21 | 'space-infix-ops': ERROR, 22 | 'prefer-spread': ERROR, 23 | 'no-multi-spaces': ERROR, 24 | 'class-methods-use-this': WARNING, 25 | 'arrow-parens': [ERROR, 'as-needed'], 26 | '@typescript-eslint/no-unused-vars': ERROR, 27 | '@typescript-eslint/no-explicit-any': OFF, 28 | '@typescript-eslint/explicit-function-return-type': OFF, 29 | '@typescript-eslint/explicit-member-accessibility': OFF, 30 | '@typescript-eslint/no-namespace': OFF, 31 | '@typescript-eslint/explicit-module-boundary-types': OFF, 32 | 'react/display-name': OFF, 33 | 'react/prop-types': OFF 34 | }, 35 | settings: { 36 | react: { 37 | version: 'detect' 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | karma-* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | node_modules 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional REPL history 34 | .node_repl_history 35 | 36 | # custom 37 | 38 | .vscode 39 | .DS_Store 40 | .idea 41 | scripts 42 | build 43 | lib 44 | dist 45 | assets 46 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | singleQuote: true, 5 | arrowParens: 'avoid', 6 | trailingComma: 'none' 7 | }; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Admin Dashboard Template 2 | 3 | Ease developers’ life with the React Suite 5. TypeScript will help with optimizing. Quick start & easily extensible code. 4 | 5 | ![preview](./preview-1.png) 6 | 7 | ![preview](./preview-2.png) 8 | 9 | ## Development 10 | 11 | Fork this repo to your namespace and clone to your local machine. 12 | 13 | ``` 14 | git clone git@github.com:/rsuite-admin-template.git 15 | $ cd rsuite-admin-template 16 | ``` 17 | 18 | Install dependencies 19 | 20 | ``` 21 | npm install 22 | ``` 23 | 24 | Now you can start the development server by running npm run dev 25 | 26 | It's serving at http://127.0.0.1:3100/ by default. 27 | 28 | ``` 29 | npm run dev 30 | ``` 31 | 32 | ## License 33 | 34 | MIT © [simonguo](https://github.com/simonguo) 35 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api, options) => { 2 | const { NODE_ENV } = options || process.env; 3 | const dev = NODE_ENV === 'development'; 4 | const modules = NODE_ENV === 'esm' ? false : 'commonjs'; 5 | 6 | if (api) { 7 | api.cache(() => NODE_ENV); 8 | } 9 | 10 | return { 11 | presets: [ 12 | ['@babel/preset-env', { modules, loose: true }], 13 | ['@babel/preset-react', { development: dev, runtime: 'automatic' }], 14 | '@babel/typescript' 15 | ], 16 | plugins: [ 17 | ['@babel/plugin-proposal-class-properties', { loose: true }], 18 | '@babel/plugin-proposal-optional-chaining', 19 | '@babel/plugin-proposal-export-namespace-from', 20 | '@babel/plugin-proposal-export-default-from', 21 | ['@babel/plugin-transform-runtime', { useESModules: !modules }], 22 | ['module-resolver', { alias: { '@': './src' } }] 23 | ] 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /gh-pages.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | var ghpages = require('gh-pages'); 3 | var path = require('path'); 4 | 5 | ghpages.publish(path.join(__dirname, './assets'), function (err) { 6 | console.log(err); 7 | }); 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rsuite-admin-template", 3 | "version": "1.0.0", 4 | "description": "RSUITE Management System", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "lib": "lib/" 8 | }, 9 | "scripts": { 10 | "dev": "webpack serve --mode development --port 3100 --host 0.0.0.0 --progress ", 11 | "clear": "rm -rf assets", 12 | "build:webpack": "NODE_ENV=production webpack --mode production --progress && cp -R public/* ./assets/ ", 13 | "build": "npm run clear && npm run build:webpack", 14 | "publish:gh-pages": "node gh-pages.js" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "rsuite", 19 | "admin", 20 | "template" 21 | ], 22 | "files": [ 23 | "CHANGELOG.md", 24 | "lib", 25 | "src" 26 | ], 27 | "author": "HYPERS Team", 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "git@github.com:rsuite/rsuite-admin-template.git" 32 | }, 33 | "dependencies": { 34 | "@babel/runtime": "^7.18.9", 35 | "@faker-js/faker": "^7.4.0", 36 | "@fullcalendar/daygrid": "^5.11.3", 37 | "@fullcalendar/interaction": "^5.11.3", 38 | "@fullcalendar/react": "^5.11.2", 39 | "@fullcalendar/timegrid": "^5.11.3", 40 | "@rsuite/icons": "^1.0.2", 41 | "apexcharts": "^3.35.4", 42 | "classnames": "^2.3.1", 43 | "date-fns": "^2.29.2", 44 | "lodash": "^4.17.21", 45 | "prop-types": "^15.6.0", 46 | "react": "^18.2.0", 47 | "react-apexcharts": "^1.4.0", 48 | "react-dom": "^18.2.0", 49 | "react-icons": "^4.4.0", 50 | "react-intl": "^6.0.5", 51 | "react-router": "6.3.0", 52 | "react-router-dom": "^6.3.0", 53 | "rsuite": "5.28.2" 54 | }, 55 | "devDependencies": { 56 | "@babel/cli": "^7.7.0", 57 | "@babel/core": "^7.7.2", 58 | "@babel/plugin-proposal-class-properties": "^7.0.0", 59 | "@babel/plugin-proposal-export-default-from": "^7.12.13", 60 | "@babel/plugin-proposal-export-namespace-from": "^7.12.13", 61 | "@babel/plugin-proposal-optional-chaining": "^7.6.0", 62 | "@babel/plugin-transform-runtime": "^7.1.0", 63 | "@babel/preset-env": "^7.7.7", 64 | "@babel/preset-react": "^7.7.4", 65 | "@babel/preset-typescript": "^7.12.7", 66 | "@svgr/webpack": "^6.3.1", 67 | "@types/jest": "^28.1.4", 68 | "@types/node": "^18.0.3", 69 | "@types/react": "^17.0.0", 70 | "@types/react-dom": "^17.0.0", 71 | "@typescript-eslint/eslint-plugin": "^5.30.5", 72 | "@typescript-eslint/parser": "^5.30.5", 73 | "autoprefixer": "^10.4.7", 74 | "babel-loader": "^8.1.0", 75 | "babel-plugin-module-resolver": "^4.1.0", 76 | "conventional-changelog-cli": "^2.2.2", 77 | "css-loader": "^6.7.1", 78 | "cssnano": "^5.1.12", 79 | "eslint": "^7.25.0", 80 | "eslint-config-prettier": "^8.3.0", 81 | "eslint-plugin-babel": "^5.3.1", 82 | "eslint-plugin-import": "^2.22.1", 83 | "eslint-plugin-react": "^7.23.2", 84 | "eslint-plugin-react-hooks": "^4.2.0", 85 | "file-loader": "^6.2.0", 86 | "gh-pages": "^1.1.0", 87 | "html-webpack-plugin": "^5.5.0", 88 | "husky": "^8.0.1", 89 | "less": "^4.1.3", 90 | "less-loader": "^11.0.0", 91 | "mini-css-extract-plugin": "^2.6.1", 92 | "postcss": "^8.4.14", 93 | "prettier": "^2.4.1", 94 | "typescript": "^4.6.4", 95 | "url-loader": "^4.1.1", 96 | "webpack": "^5.73.0", 97 | "webpack-cli": "^4.10.0", 98 | "webpack-dev-server": "^3.11.2" 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const plugins = [ 3 | require('autoprefixer'), 4 | require('cssnano')({ 5 | preset: [ 6 | 'default', 7 | { 8 | discardComments: { 9 | removeAll: true 10 | } 11 | } 12 | ] 13 | }) 14 | ]; 15 | 16 | module.exports = { 17 | plugins 18 | }; 19 | -------------------------------------------------------------------------------- /preview-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsuite/rsuite-admin-template/0e573766a0d48bd6d5c6633774b63c2f435d0ac9/preview-1.png -------------------------------------------------------------------------------- /preview-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsuite/rsuite-admin-template/0e573766a0d48bd6d5c6633774b63c2f435d0ac9/preview-2.png -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsuite/rsuite-admin-template/0e573766a0d48bd6d5c6633774b63c2f435d0ac9/public/favicon.ico -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Routes, Route } from 'react-router-dom'; 3 | import { IntlProvider } from 'react-intl'; 4 | import { CustomProvider } from 'rsuite'; 5 | import enGB from 'rsuite/locales/en_GB'; 6 | import locales from './locales'; 7 | import Frame from './components/Frame'; 8 | import DashboardPage from './pages/dashboard'; 9 | import Error404Page from './pages/authentication/404'; 10 | import Error403Page from './pages/authentication/403'; 11 | import Error500Page from './pages/authentication/500'; 12 | import Error503Page from './pages/authentication/503'; 13 | 14 | import SignInPage from './pages/authentication/sign-in'; 15 | import SignUpPage from './pages/authentication/sign-up'; 16 | import MembersPage from './pages/tables/members'; 17 | import VirtualizedTablePage from './pages/tables/virtualized'; 18 | import FormBasicPage from './pages/forms/basic'; 19 | import FormWizardPage from './pages/forms/wizard'; 20 | import CalendarPage from './pages/calendar'; 21 | import { appNavs } from './config'; 22 | 23 | const App = () => { 24 | return ( 25 | 26 | 27 | 28 | }> 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | } /> 34 | } /> 35 | } /> 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | } /> 41 | } /> 42 | 43 | } /> 44 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default App; 51 | -------------------------------------------------------------------------------- /src/components/Brand.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Logo from './Logo'; 4 | import { Stack } from 'rsuite'; 5 | 6 | const Brand = props => { 7 | return ( 8 | 9 | 10 | 11 | Admin Template 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default Brand; 18 | -------------------------------------------------------------------------------- /src/components/Copyright.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Stack } from 'rsuite'; 3 | 4 | const Copyright = () => { 5 | return ( 6 | 7 |
8 |

9 | © 2022, Made with ❤️ by{' '} 10 | 11 | RSUITE 12 | 13 |

14 |
15 |
16 | ); 17 | }; 18 | 19 | export default Copyright; 20 | -------------------------------------------------------------------------------- /src/components/ErrorPage/ErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as errors from '@/images/errors'; 3 | 4 | const ErrorPage = ({ code = 404, children }) => ( 5 |
6 |
7 | 8 |
9 |

{code}

10 | {children} 11 |
12 |
13 |
14 | ); 15 | 16 | export default ErrorPage; 17 | -------------------------------------------------------------------------------- /src/components/ErrorPage/index.ts: -------------------------------------------------------------------------------- 1 | import ErrorPage from './ErrorPage'; 2 | 3 | export default ErrorPage; 4 | -------------------------------------------------------------------------------- /src/components/ErrorPage/styles.less: -------------------------------------------------------------------------------- 1 | .error-page { 2 | display: flex; 3 | height: 100vh; 4 | margin-top: -40px; 5 | justify-content: center; 6 | align-items: center; 7 | 8 | &-code{ 9 | color: @B700; 10 | } 11 | &-title { 12 | font-size: 1.5rem; 13 | font-weight: bold; 14 | } 15 | &-subtitle { 16 | margin: 10px 0 20px 0; 17 | } 18 | 19 | .item { 20 | img { 21 | height: 260px; 22 | } 23 | .text { 24 | text-align: center; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Frame/Frame.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import classNames from 'classnames'; 3 | import { Container, Sidebar, Sidenav, Content, Nav, DOMHelper } from 'rsuite'; 4 | import { Outlet } from 'react-router-dom'; 5 | import NavToggle from './NavToggle'; 6 | import Header from '../Header'; 7 | import NavLink from '../NavLink'; 8 | import Brand from '../Brand'; 9 | 10 | const { getHeight, on } = DOMHelper; 11 | 12 | const NavItem = props => { 13 | const { title, eventKey, ...rest } = props; 14 | return ( 15 | 16 | {title} 17 | 18 | ); 19 | }; 20 | 21 | export interface NavItemData { 22 | eventKey: string; 23 | title: string; 24 | icon?: any; 25 | to?: string; 26 | target?: string; 27 | children?: NavItemData[]; 28 | } 29 | 30 | export interface FrameProps { 31 | navs: NavItemData[]; 32 | children?: React.ReactNode; 33 | } 34 | 35 | const Frame = (props: FrameProps) => { 36 | const { navs } = props; 37 | const [expand, setExpand] = useState(true); 38 | const [windowHeight, setWindowHeight] = useState(getHeight(window)); 39 | 40 | useEffect(() => { 41 | setWindowHeight(getHeight(window)); 42 | const resizeListenner = on(window, 'resize', () => setWindowHeight(getHeight(window))); 43 | 44 | return () => { 45 | resizeListenner.off(); 46 | }; 47 | }, []); 48 | 49 | const containerClasses = classNames('page-container', { 50 | 'container-full': !expand 51 | }); 52 | 53 | const navBodyStyle: React.CSSProperties = expand 54 | ? { height: windowHeight - 112, overflow: 'auto' } 55 | : {}; 56 | 57 | return ( 58 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | 93 | 94 | 95 | setExpand(!expand)} /> 96 | 97 | 98 | 99 |
100 | 101 | 102 | 103 | 104 | 105 | ); 106 | }; 107 | 108 | export default Frame; 109 | -------------------------------------------------------------------------------- /src/components/Frame/NavToggle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navbar, Nav } from 'rsuite'; 3 | import ArrowLeftLineIcon from '@rsuite/icons/ArrowLeftLine'; 4 | import ArrowRightLineIcon from '@rsuite/icons/ArrowRightLine'; 5 | 6 | interface NavToggleProps { 7 | expand?: boolean; 8 | onChange?: () => void; 9 | } 10 | 11 | const NavToggle = ({ expand, onChange }: NavToggleProps) => { 12 | return ( 13 | 14 | 21 | 22 | ); 23 | }; 24 | 25 | export default NavToggle; 26 | -------------------------------------------------------------------------------- /src/components/Frame/index.ts: -------------------------------------------------------------------------------- 1 | import Frame from './Frame'; 2 | 3 | export default Frame; 4 | -------------------------------------------------------------------------------- /src/components/Frame/styles.less: -------------------------------------------------------------------------------- 1 | .frame { 2 | height: 100vh; 3 | .rs-sidebar { 4 | background: #fff; 5 | } 6 | .rs-sidenav { 7 | flex: 1 1 auto; 8 | transition: none !important; 9 | border-top: 1px solid @B100; 10 | width: 100%; 11 | } 12 | 13 | .rs-content { 14 | padding: 0 10px; 15 | 16 | .rs-panel-header { 17 | .title { 18 | font-size: 18px; 19 | } 20 | .rs-breadcrumb { 21 | margin-bottom: 0; 22 | } 23 | } 24 | } 25 | .nav-toggle { 26 | border-top: 1px solid @B100; 27 | } 28 | 29 | .rs-sidenav-item, 30 | .rs-dropdown-item { 31 | &.active { 32 | color: @H500 !important; 33 | } 34 | &-icon { 35 | height: 1.3em !important; 36 | width: 1.3em !important; 37 | } 38 | } 39 | } 40 | 41 | .brand { 42 | padding: 10px 18px; 43 | font-size: 16px; 44 | white-space: nowrap; 45 | overflow: hidden; 46 | font-weight: bold; 47 | text-transform: uppercase; 48 | 49 | a { 50 | text-decoration: none; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { 3 | Dropdown, 4 | Popover, 5 | Whisper, 6 | WhisperInstance, 7 | Stack, 8 | Badge, 9 | Avatar, 10 | IconButton, 11 | List, 12 | Button 13 | } from 'rsuite'; 14 | import NoticeIcon from '@rsuite/icons/Notice'; 15 | import GearIcon from '@rsuite/icons/Gear'; 16 | import HelpOutlineIcon from '@rsuite/icons/HelpOutline'; 17 | import GithubIcon from '@rsuite/icons/legacy/Github'; 18 | import HeartIcon from '@rsuite/icons/legacy/HeartO'; 19 | 20 | const renderAdminSpeaker = ({ onClose, left, top, className }: any, ref) => { 21 | const handleSelect = eventKey => { 22 | onClose(); 23 | console.log(eventKey); 24 | }; 25 | return ( 26 | 27 | 28 | 29 |

Signed in as

30 | Administrator 31 |
32 | 33 | Set status 34 | Profile & account 35 | Feedback 36 | 37 | Settings 38 | Sign out 39 | } 41 | href="https://rsuitejs.com" 42 | target="_blank" 43 | as="a" 44 | > 45 | Help{' '} 46 | 47 |
48 |
49 | ); 50 | }; 51 | 52 | const renderSettingSpeaker = ({ onClose, left, top, className }: any, ref) => { 53 | const handleSelect = eventKey => { 54 | onClose(); 55 | console.log(eventKey); 56 | }; 57 | return ( 58 | 59 | 60 | 61 | Settings 62 | 63 | Applications 64 | Projects 65 | 66 | Members 67 | Teams 68 | Channels 69 | 70 | Integrations 71 | Customize 72 | 73 | 74 | ); 75 | }; 76 | 77 | const renderNoticeSpeaker = ({ onClose, left, top, className }: any, ref) => { 78 | const notifications = [ 79 | [ 80 | '7 hours ago', 81 | 'The charts of the dashboard have been fully upgraded and are more visually pleasing.' 82 | ], 83 | [ 84 | '13 hours ago', 85 | 'The function of virtualizing large lists has been added, and the style of the list can be customized as required.' 86 | ], 87 | ['2 days ago', 'Upgraded React 18 and Webpack 5.'], 88 | [ 89 | '3 days ago', 90 | 'Upgraded React Suite 5 to support TypeScript, which is more concise and efficient.' 91 | ] 92 | ]; 93 | 94 | return ( 95 | 96 | 97 | {notifications.map((item, index) => { 98 | const [time, content] = item; 99 | return ( 100 | 101 | 102 | {time} 103 | 104 | 105 |

{content}

106 |
107 | ); 108 | })} 109 |
110 |
111 | 112 |
113 |
114 | ); 115 | }; 116 | 117 | const Header = () => { 118 | const trigger = useRef(null); 119 | 120 | return ( 121 | 122 | } 124 | href="https://opencollective.com/rsuite" 125 | target="_blank" 126 | /> 127 | } 129 | href="https://github.com/rsuite/rsuite-admin-template" 130 | target="_blank" 131 | /> 132 | 133 | 134 | 137 | 138 | 139 | } 140 | /> 141 | 142 | 143 | 144 | } /> 145 | 146 | 147 | 148 | 155 | 156 | 157 | ); 158 | }; 159 | 160 | export default Header; 161 | -------------------------------------------------------------------------------- /src/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | 3 | export default Header; 4 | -------------------------------------------------------------------------------- /src/components/Header/styles.less: -------------------------------------------------------------------------------- 1 | .header { 2 | position: absolute; 3 | right: 30px; 4 | top: 20px; 5 | cursor: pointer; 6 | z-index: 1; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Logo/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface LogoProps { 4 | width?: number; 5 | height?: number; 6 | className?: string; 7 | style?: React.CSSProperties; 8 | } 9 | 10 | export default function Logo({ width, height, style, className = '' }: LogoProps) { 11 | const styles = { width, height, display: 'inline-block', ...style }; 12 | return ( 13 |
17 | 25 | React Suite 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 56 | 66 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import Logo from './Logo'; 2 | 3 | export default Logo; 4 | -------------------------------------------------------------------------------- /src/components/Logo/styles.less: -------------------------------------------------------------------------------- 1 | .rsuite-logo { 2 | .cls-1 { 3 | fill: #6292f0; 4 | } 5 | .cls-2 { 6 | fill: #ec727d; 7 | } 8 | .cls-1, 9 | .cls-2 { 10 | fill-rule: evenodd; 11 | } 12 | 13 | .polyline-limb { 14 | animation: limbLineMove 3s ease-out 1; 15 | } 16 | .polyline-axis { 17 | animation: axisLineMove 2s ease-out 1; 18 | } 19 | 20 | .circle { 21 | animation: circleMove 2s ease-out 1; 22 | } 23 | } 24 | 25 | .logo-animated { 26 | animation-duration: 1s; 27 | animation-fill-mode: both; 28 | } 29 | 30 | .logo-animated-delay-half-seconds { 31 | animation-delay: 0.5s; 32 | } 33 | 34 | .bounce-in { 35 | animation-name: logo-bounce-in; 36 | } 37 | 38 | @keyframes logo-bounce-in { 39 | from, 40 | 20%, 41 | 40%, 42 | 60%, 43 | 80%, 44 | to { 45 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 46 | } 47 | 0% { 48 | opacity: 0; 49 | transform: scale3d(0.3, 0.3, 0.3); 50 | } 51 | 20% { 52 | opacity: 1; 53 | transform: scale3d(1.1, 1.1, 1.1); 54 | } 55 | 40% { 56 | transform: scale3d(0.9, 0.9, 0.9); 57 | } 58 | 60% { 59 | transform: scale3d(1.03, 1.03, 1.03); 60 | } 61 | 80% { 62 | transform: scale3d(0.97, 0.97, 0.97); 63 | } 64 | to { 65 | opacity: 1; 66 | transform: scale3d(1, 1, 1); 67 | } 68 | } 69 | 70 | @keyframes axisLineMove { 71 | 0% { 72 | stroke-dasharray: 0, 500; 73 | } 74 | 100% { 75 | stroke-dasharray: 500, 500; 76 | } 77 | } 78 | 79 | @keyframes limbLineMove { 80 | 0% { 81 | stroke-dasharray: 0, 200; 82 | stroke: transparent; 83 | } 84 | 50% { 85 | stroke-dasharray: 0, 200; 86 | } 87 | 100% { 88 | stroke-dasharray: 200, 200; 89 | } 90 | } 91 | 92 | @keyframes circleMove { 93 | 0% { 94 | fill: transparent; 95 | } 96 | 50% { 97 | fill: transparent; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/components/NavLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLinkProps, NavLink as BaseNavLink } from 'react-router-dom'; 3 | 4 | const NavLink = React.forwardRef( 5 | ({ to, children, ...rest }, ref) => { 6 | return ( 7 | 8 | {children} 9 | 10 | ); 11 | } 12 | ); 13 | 14 | export default NavLink; 15 | -------------------------------------------------------------------------------- /src/components/PageContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Panel } from 'rsuite'; 3 | import Copyright from '@/components/Copyright'; 4 | 5 | const PageContent = props => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default PageContent; 15 | -------------------------------------------------------------------------------- /src/components/PageToolbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react'; 2 | import { Affix, Stack, DateRangePicker, IconButton, SelectPicker } from 'rsuite'; 3 | import SettingIcon from '@rsuite/icons/Setting'; 4 | import subDays from 'date-fns/subDays'; 5 | import startOfWeek from 'date-fns/startOfWeek'; 6 | import endOfWeek from 'date-fns/endOfWeek'; 7 | import addDays from 'date-fns/addDays'; 8 | import startOfMonth from 'date-fns/startOfMonth'; 9 | import endOfMonth from 'date-fns/endOfMonth'; 10 | import addMonths from 'date-fns/addMonths'; 11 | 12 | import type { RangeType } from 'rsuite/DateRangePicker'; 13 | 14 | interface Range extends RangeType { 15 | appearance?: 'default' | 'primary' | 'link' | 'subtle' | 'ghost'; 16 | } 17 | 18 | const predefinedRanges: Range[] = [ 19 | { 20 | label: 'Today', 21 | value: [new Date(), new Date()], 22 | placement: 'left' 23 | }, 24 | { 25 | label: 'Yesterday', 26 | value: [addDays(new Date(), -1), addDays(new Date(), -1)], 27 | placement: 'left' 28 | }, 29 | { 30 | label: 'This week', 31 | value: [startOfWeek(new Date()), endOfWeek(new Date())], 32 | placement: 'left' 33 | }, 34 | { 35 | label: 'Last 7 days', 36 | value: [subDays(new Date(), 6), new Date()], 37 | placement: 'left' 38 | }, 39 | { 40 | label: 'Last 30 days', 41 | value: [subDays(new Date(), 29), new Date()], 42 | placement: 'left' 43 | }, 44 | { 45 | label: 'This month', 46 | value: [startOfMonth(new Date()), new Date()], 47 | placement: 'left' 48 | }, 49 | { 50 | label: 'Last month', 51 | value: [startOfMonth(addMonths(new Date(), -1)), endOfMonth(addMonths(new Date(), -1))], 52 | placement: 'left' 53 | }, 54 | { 55 | label: 'This year', 56 | value: [new Date(new Date().getFullYear(), 0, 1), new Date()], 57 | placement: 'left' 58 | }, 59 | { 60 | label: 'Last year', 61 | value: [new Date(new Date().getFullYear() - 1, 0, 1), new Date(new Date().getFullYear(), 0, 0)], 62 | placement: 'left' 63 | }, 64 | { 65 | label: 'All time', 66 | value: [new Date(new Date().getFullYear() - 1, 0, 1), new Date()], 67 | placement: 'left' 68 | }, 69 | { 70 | label: 'Last week', 71 | closeOverlay: false, 72 | value: value => { 73 | const [start = new Date()] = value || []; 74 | return [ 75 | addDays(startOfWeek(start, { weekStartsOn: 0 }), -7), 76 | addDays(endOfWeek(start, { weekStartsOn: 0 }), -7) 77 | ]; 78 | }, 79 | appearance: 'default' 80 | }, 81 | { 82 | label: 'Next week', 83 | closeOverlay: false, 84 | value: value => { 85 | const [start = new Date()] = value || []; 86 | return [ 87 | addDays(startOfWeek(start, { weekStartsOn: 0 }), 7), 88 | addDays(endOfWeek(start, { weekStartsOn: 0 }), 7) 89 | ]; 90 | }, 91 | appearance: 'default' 92 | } 93 | ]; 94 | 95 | const PageToolbar = () => { 96 | const [fixed, setFixed] = useState(false); 97 | const containerRef = useRef(null); 98 | 99 | return ( 100 | 101 | 114 | 115 | containerRef.current as HTMLDivElement} 121 | data={[ 122 | { label: 'Daily', value: 'Daily' }, 123 | { label: 'Weekly', value: 'Weekly' }, 124 | { label: 'Monthly', value: 'Monthly' } 125 | ]} 126 | /> 127 | containerRef.current as HTMLDivElement} 133 | /> 134 | 135 | 136 | } /> 137 | 138 | 139 | ); 140 | }; 141 | 142 | export default PageToolbar; 143 | -------------------------------------------------------------------------------- /src/components/RadioTile/RadioTile.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import classNames from 'classnames'; 3 | import { Stack } from 'rsuite'; 4 | import CheckIcon from '@rsuite/icons/Check'; 5 | 6 | interface RadioTileProps { 7 | icon?: React.ReactNode; 8 | children: React.ReactNode; 9 | title?: React.ReactNode; 10 | name?: string; 11 | className?: string; 12 | value: string; 13 | onChange?: (value: string, event: React.ChangeEvent) => void; 14 | } 15 | 16 | const RadioTile = (props: RadioTileProps) => { 17 | const { icon, children, value, title, name, className, onChange, ...rest } = props; 18 | 19 | const checked = value === name; 20 | 21 | const handleChange = useCallback( 22 | (event: React.ChangeEvent) => { 23 | onChange?.(event.target.value, event); 24 | }, 25 | [onChange] 26 | ); 27 | 28 | const classes = classNames(className, 'rs-radio-tile', { checked }); 29 | 30 | return ( 31 | 32 | {icon} 33 |
34 | 35 |
{title}
36 |
{children}
37 |
38 | 39 |
40 |
41 |
42 | ); 43 | }; 44 | 45 | export default RadioTile; 46 | -------------------------------------------------------------------------------- /src/components/RadioTile/index.tsx: -------------------------------------------------------------------------------- 1 | import RadioTile from './RadioTile'; 2 | 3 | export default RadioTile; 4 | -------------------------------------------------------------------------------- /src/components/RadioTile/styles.less: -------------------------------------------------------------------------------- 1 | .rs-radio-tile { 2 | border-radius: 6px; 3 | overflow: hidden; 4 | border: 2px solid #e5e5ea; 5 | padding: 10px; 6 | position: relative; 7 | cursor: pointer; 8 | 9 | &:hover, 10 | &.checked { 11 | border: 2px solid #3498ff; 12 | background: aliceblue; 13 | } 14 | 15 | &-title { 16 | font-weight: bold; 17 | } 18 | 19 | .rs-radio-tile-check { 20 | background: #3498ff; 21 | border-bottom-left-radius: 50%; 22 | height: 48px; 23 | position: absolute; 24 | right: -24px; 25 | top: -24px; 26 | width: 48px; 27 | z-index: 3; 28 | opacity: 0; 29 | 30 | .rs-icon { 31 | position: absolute; 32 | font-size: 16px; 33 | top: 25px; 34 | left: 7px; 35 | color: #fff; 36 | } 37 | } 38 | 39 | &.checked .rs-radio-tile-check { 40 | opacity: 1; 41 | } 42 | 43 | input { 44 | opacity: 0; 45 | width: 0; 46 | height: 0; 47 | position: absolute; 48 | } 49 | .rs-icon { 50 | font-size: 32px; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Textarea.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'rsuite'; 3 | 4 | const Textarea = React.forwardRef((props, ref) => ( 5 | 6 | )); 7 | 8 | export default Textarea; 9 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ErrorPage } from './ErrorPage'; 2 | export { default as Header } from './Header'; 3 | export { default as Frame } from './Frame'; 4 | -------------------------------------------------------------------------------- /src/config.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@rsuite/icons'; 3 | import { VscTable, VscCalendar } from 'react-icons/vsc'; 4 | import { MdFingerprint, MdDashboard, MdModeEditOutline } from 'react-icons/md'; 5 | import CubesIcon from '@rsuite/icons/legacy/Cubes'; 6 | 7 | export const appNavs = [ 8 | { 9 | eventKey: 'dashboard', 10 | icon: , 11 | title: 'Dashboard', 12 | to: '/dashboard' 13 | }, 14 | { 15 | eventKey: 'calendar', 16 | icon: , 17 | title: 'Calendar', 18 | to: '/calendar' 19 | }, 20 | { 21 | eventKey: 'tables', 22 | icon: , 23 | title: 'Tables', 24 | to: '/table-members', 25 | children: [ 26 | { 27 | eventKey: 'members', 28 | title: 'Members', 29 | to: '/table-members' 30 | }, 31 | { 32 | eventKey: 'virtualized', 33 | title: 'Virtualized Table', 34 | to: '/table-virtualized' 35 | } 36 | ] 37 | }, 38 | { 39 | eventKey: 'forms', 40 | icon: , 41 | title: 'Forms', 42 | to: '/form-basic', 43 | children: [ 44 | { 45 | eventKey: 'form-basic', 46 | title: 'Basic', 47 | to: '/form-basic' 48 | }, 49 | { 50 | eventKey: 'form-wizard', 51 | title: 'Wizard', 52 | to: '/form-wizard' 53 | } 54 | ] 55 | }, 56 | { 57 | eventKey: 'authentication', 58 | title: 'Authentication', 59 | icon: , 60 | children: [ 61 | { 62 | eventKey: 'sign-in', 63 | title: 'Sign In', 64 | to: '/sign-in' 65 | }, 66 | 67 | { 68 | eventKey: 'sign-up', 69 | title: 'Sign Up', 70 | to: '/sign-up' 71 | }, 72 | { 73 | eventKey: 'error403', 74 | title: 'Error 403', 75 | to: '/error-403' 76 | }, 77 | { 78 | eventKey: 'error404', 79 | title: 'Error 404', 80 | to: '/error-404' 81 | }, 82 | { 83 | eventKey: 'error500', 84 | title: 'Error 500', 85 | to: '/error-500' 86 | }, 87 | { 88 | eventKey: 'error503', 89 | title: 'Error 503', 90 | to: '/error-503' 91 | } 92 | ] 93 | }, 94 | 95 | { 96 | eventKey: 'components', 97 | title: 'Components', 98 | icon: , 99 | href: 'https://rsuitejs.com/components/overview/', 100 | target: '_blank' 101 | } 102 | ]; 103 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /src/data/mock.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker/locale/en'; 2 | 3 | export function mockUsers(length: number) { 4 | const createRowData = rowIndex => { 5 | const firstName = faker.name.firstName(); 6 | const lastName = faker.name.lastName(); 7 | const gender = faker.name.gender(true) as 'female' | 'male'; 8 | const name = faker.name.findName(firstName, lastName, gender); 9 | const avatar = faker.image.avatar(); 10 | 11 | const city = faker.address.city(); 12 | const street = faker.address.street(); 13 | const email = faker.internet.email(); 14 | const postcode = faker.address.zipCode(); 15 | const phone = faker.phone.number(); 16 | const amount = faker.finance.amount(1000, 90000); 17 | 18 | const age = Math.floor(Math.random() * 30) + 18; 19 | const stars = Math.floor(Math.random() * 10000); 20 | const followers = Math.floor(Math.random() * 10000); 21 | const rating = 2 + Math.floor(Math.random() * 3); 22 | const progress = Math.floor(Math.random() * 100); 23 | 24 | return { 25 | id: rowIndex + 1, 26 | name, 27 | firstName, 28 | lastName, 29 | avatar, 30 | city, 31 | street, 32 | postcode, 33 | email, 34 | phone, 35 | gender, 36 | age, 37 | stars, 38 | followers, 39 | rating, 40 | progress, 41 | amount 42 | }; 43 | }; 44 | 45 | return Array.from({ length }).map((_, index) => { 46 | return createRowData(index); 47 | }); 48 | } 49 | 50 | export function mockTreeData(options: { 51 | limits: number[]; 52 | labels: string | string[] | ((layer: number, value: string, faker) => string); 53 | getRowData?: (layer: number, value: string) => any[]; 54 | }) { 55 | const { limits, labels, getRowData } = options; 56 | const depth = limits.length; 57 | 58 | const data = []; 59 | const mock = (list, parentValue?: string, layer = 0) => { 60 | const length = limits[layer]; 61 | 62 | Array.from({ length }).forEach((_, index) => { 63 | const value = parentValue ? parentValue + '-' + (index + 1) : index + 1 + ''; 64 | const children = []; 65 | const label = Array.isArray(labels) ? labels[layer] : labels; 66 | 67 | let row: any = { 68 | label: typeof label === 'function' ? label(layer, value, faker) : label + ' ' + value, 69 | value 70 | }; 71 | 72 | if (getRowData) { 73 | row = { 74 | ...row, 75 | ...getRowData(layer, value) 76 | }; 77 | } 78 | 79 | list.push(row); 80 | 81 | if (layer < depth - 1) { 82 | row.children = children; 83 | mock(children, value, layer + 1); 84 | } 85 | }); 86 | }; 87 | 88 | mock(data); 89 | 90 | return data; 91 | } 92 | -------------------------------------------------------------------------------- /src/images/charts/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | export const { default: PVIcon } = require('./pv.svg'); 3 | export const { default: UVIcon } = require('./uv.svg'); 4 | export const { default: VVICon } = require('./vv.svg'); 5 | -------------------------------------------------------------------------------- /src/images/charts/pv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 Copy 5 | Created with Sketch. 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/images/charts/uv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 11 Copy 5 | Created with Sketch. 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/images/charts/vv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 6 Copy 5 | Created with Sketch. 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/images/errors/403.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/images/errors/404.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group 4 | Created with Sketch. 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/images/errors/500.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group 4 | Created with Sketch. 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/images/errors/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | export const { default: Error404Img } = require('./404.svg'); 3 | export const { default: Error500Img } = require('./500.svg'); 4 | export const { default: Error503Img } = require('./503.svg'); 5 | export const { default: Error403Img } = require('./403.svg'); 6 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 | 9 | 10 | 11 |
12 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import App from './App'; 5 | 6 | import './styles/index.less'; 7 | 8 | const root = ReactDOM.createRoot(document.getElementById('root')); 9 | 10 | root.render( 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/locales/en-US/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | id: 'en-US', 3 | error404: 'Page not found', 4 | error500: 'Server error' 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en-US'; 2 | import zh from './zh-CN'; 3 | 4 | export default { 5 | en: en, 6 | zh: zh 7 | }; 8 | -------------------------------------------------------------------------------- /src/locales/zh-CN/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | id: 'zh-CN', 3 | error404: '该页面不存在', 4 | error500: '服务器没有响应' 5 | }; 6 | -------------------------------------------------------------------------------- /src/pages/authentication/403/Error403.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorPage from '@/components/ErrorPage'; 3 | import { IconButton } from 'rsuite'; 4 | import ArrowLeftLine from '@rsuite/icons/ArrowLeftLine'; 5 | 6 | export default () => ( 7 | 8 |

Oops… You just found an error page

9 |

10 | The current page is unavailable or you do not have permission to access.{' '} 11 |

12 | } appearance="primary" href="/"> 13 | Take me home 14 | 15 |
16 | ); 17 | -------------------------------------------------------------------------------- /src/pages/authentication/403/index.ts: -------------------------------------------------------------------------------- 1 | import Error403 from './Error403'; 2 | 3 | export default Error403; 4 | -------------------------------------------------------------------------------- /src/pages/authentication/404/Error404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorPage from '@/components/ErrorPage'; 3 | import { IconButton } from 'rsuite'; 4 | import ArrowLeftLine from '@rsuite/icons/ArrowLeftLine'; 5 | 6 | export default () => ( 7 | 8 |

Oops… You just found an error page

9 |

10 | We are sorry but the page you are looking for was not found 11 |

12 | } appearance="primary" href="/"> 13 | Take me home 14 | 15 |
16 | ); 17 | -------------------------------------------------------------------------------- /src/pages/authentication/404/index.ts: -------------------------------------------------------------------------------- 1 | import Error404 from './Error404'; 2 | 3 | export default Error404; 4 | -------------------------------------------------------------------------------- /src/pages/authentication/500/Error500.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorPage from '@/components/ErrorPage'; 3 | import { IconButton } from 'rsuite'; 4 | import ArrowLeftLine from '@rsuite/icons/ArrowLeftLine'; 5 | 6 | export default () => ( 7 | 8 |

Oops… You just found an error page

9 |

10 | We are sorry but our server encountered an internal error 11 |

12 | } appearance="primary" href="/"> 13 | Take me home 14 | 15 |
16 | ); 17 | -------------------------------------------------------------------------------- /src/pages/authentication/500/index.ts: -------------------------------------------------------------------------------- 1 | import Error500 from './Error500'; 2 | 3 | export default Error500; 4 | -------------------------------------------------------------------------------- /src/pages/authentication/503/Error503.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorPage from '@/components/ErrorPage'; 3 | import { IconButton } from 'rsuite'; 4 | import ArrowLeftLine from '@rsuite/icons/ArrowLeftLine'; 5 | 6 | export default () => ( 7 | 8 |

Oops… You just found an error page

9 |

This page is being updated and maintained.

10 | } appearance="primary" href="/"> 11 | Take me home 12 | 13 |
14 | ); 15 | -------------------------------------------------------------------------------- /src/pages/authentication/503/index.ts: -------------------------------------------------------------------------------- 1 | import Error503 from './Error503'; 2 | 3 | export default Error503; 4 | -------------------------------------------------------------------------------- /src/pages/authentication/sign-in/SignIn.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Button, Panel, IconButton, Stack, Divider } from 'rsuite'; 3 | import { Link } from 'react-router-dom'; 4 | import GithubIcon from '@rsuite/icons/legacy/Github'; 5 | import FacebookIcon from '@rsuite/icons/legacy/Facebook'; 6 | import GoogleIcon from '@rsuite/icons/legacy/Google'; 7 | import WechatIcon from '@rsuite/icons/legacy/Wechat'; 8 | import Brand from '@/components/Brand'; 9 | 10 | const SignUp = () => { 11 | return ( 12 | 20 | 21 | 22 | Sign In}> 23 |

24 | New Here? {' '} 25 | Create an Account 26 |

27 | 28 |
29 | 30 | Username or email address 31 | 32 | 33 | 34 | 35 | Password 36 | Forgot password? 37 | 38 | 39 | 40 | 41 | }> 42 | 43 | 44 | } appearance="subtle" /> 45 | } appearance="subtle" /> 46 | } appearance="subtle" /> 47 | } appearance="subtle" /> 48 | 49 | 50 | 51 |
52 |
53 |
54 | ); 55 | }; 56 | 57 | export default SignUp; 58 | -------------------------------------------------------------------------------- /src/pages/authentication/sign-in/index.ts: -------------------------------------------------------------------------------- 1 | import SignIn from './SignIn'; 2 | 3 | export default SignIn; 4 | -------------------------------------------------------------------------------- /src/pages/authentication/sign-up/SignUp.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Form, Button, Panel, InputGroup, Stack, Checkbox, Divider } from 'rsuite'; 4 | import EyeIcon from '@rsuite/icons/legacy/Eye'; 5 | import EyeSlashIcon from '@rsuite/icons/legacy/EyeSlash'; 6 | import { Link } from 'react-router-dom'; 7 | import Brand from '@/components/Brand'; 8 | 9 | const SignIn = () => { 10 | const [visible, setVisible] = React.useState(false); 11 | 12 | return ( 13 | 21 | 22 | Create an Account} 24 | bordered 25 | style={{ background: '#fff', width: 400 }} 26 | > 27 |

28 | Already have an account? Sign in here 29 |

30 | 31 | OR 32 | 33 |
34 | 35 | Username 36 | 37 | 38 | 39 | 40 | Email 41 | 42 | 43 | 44 | Password 45 | 46 | 51 | setVisible(!visible)}> 52 | {visible ? : } 53 | 54 | 55 | 56 | 57 | 58 | Confirm Password 59 | 60 | 61 | 62 | 63 | 64 | I Agree 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 |
76 |
77 | ); 78 | }; 79 | 80 | export default SignIn; 81 | -------------------------------------------------------------------------------- /src/pages/authentication/sign-up/index.ts: -------------------------------------------------------------------------------- 1 | import SignUp from './SignUp'; 2 | 3 | export default SignUp; 4 | -------------------------------------------------------------------------------- /src/pages/calendar/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FullCalendar, { DateSelectArg, EventClickArg, EventContentArg } from '@fullcalendar/react'; 3 | import dayGridPlugin from '@fullcalendar/daygrid'; 4 | import timeGridPlugin from '@fullcalendar/timegrid'; 5 | import interactionPlugin from '@fullcalendar/interaction'; 6 | import PageContent from '@/components/PageContent'; 7 | import { INITIAL_EVENTS } from './event-utils'; 8 | import EventModal from './EventModal'; 9 | 10 | const Calendar = () => { 11 | const [editable, setEditable] = React.useState(false); 12 | const handleDateSelect = (selectInfo: DateSelectArg) => { 13 | console.log(selectInfo); 14 | setEditable(true); 15 | }; 16 | 17 | const handleEventClick = (clickInfo: EventClickArg) => { 18 | console.log(clickInfo); 19 | setEditable(true); 20 | }; 21 | 22 | return ( 23 | 24 | 43 | setEditable(false)} 46 | onAddEvent={() => { 47 | setEditable(false); 48 | }} 49 | /> 50 | 51 | ); 52 | }; 53 | 54 | function renderEventContent(eventContent: EventContentArg) { 55 | const { timeText, event } = eventContent; 56 | return ( 57 | <> 58 | {timeText && ( 59 | <> 60 |
61 |
{eventContent.timeText}
62 | 63 | )} 64 |
{event.title}
65 | 66 | ); 67 | } 68 | 69 | export default Calendar; 70 | -------------------------------------------------------------------------------- /src/pages/calendar/EventModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Button, Form, DatePicker, ModalProps, Stack, Checkbox } from 'rsuite'; 3 | 4 | interface EventModalProps extends ModalProps { 5 | onAddEvent: (event: React.MouseEvent) => void; 6 | } 7 | 8 | const EventModal = (props: EventModalProps) => { 9 | const { onClose, open, onAddEvent, ...rest } = props; 10 | return ( 11 | 12 | 13 | Add a New Event 14 | 15 | 16 |
17 | 18 | Event Name 19 | 20 | 21 | 22 | Event Description 23 | 24 | 25 | 26 | Event Location 27 | 28 | 29 | 30 | Event Date 31 | 32 | 38 | 44 | All Day 45 | 46 | 47 |
48 |
49 | 50 | 53 | 56 | 57 |
58 | ); 59 | }; 60 | 61 | export default EventModal; 62 | -------------------------------------------------------------------------------- /src/pages/calendar/event-utils.tsx: -------------------------------------------------------------------------------- 1 | import { EventInput } from '@fullcalendar/react'; 2 | import uniqueId from 'lodash/uniqueId'; 3 | import { startOfMonth, addDays, format, endOfMonth } from 'date-fns'; 4 | 5 | const today = new Date(); 6 | const firstDay = startOfMonth(today); 7 | const lastDay = endOfMonth(today); 8 | const todayStr = format(today, 'yyyy-MM-dd'); 9 | 10 | console.log(todayStr); 11 | 12 | export const INITIAL_EVENTS: EventInput[] = [ 13 | { 14 | id: uniqueId(), 15 | title: '🎊 Project kick-off meeting', 16 | allDay: true, 17 | start: format(firstDay, 'yyyy-MM-dd') 18 | }, 19 | { 20 | id: uniqueId(), 21 | title: '🎉 Product launch', 22 | start: format(addDays(firstDay, 2), 'yyyy-MM-dd') + 'T10:00:00' 23 | }, 24 | 25 | { 26 | id: uniqueId(), 27 | title: 'Product training.', 28 | start: format(addDays(firstDay, 3), 'yyyy-MM-dd') + 'T10:00:00' 29 | }, 30 | { 31 | id: uniqueId(), 32 | title: 'Product Demo', 33 | start: format(addDays(firstDay, 3), 'yyyy-MM-dd') + 'T11:00:00' 34 | }, 35 | { 36 | id: uniqueId(), 37 | title: 'Product Exam', 38 | start: format(addDays(firstDay, 3), 'yyyy-MM-dd') + 'T12:00:00' 39 | }, 40 | 41 | { 42 | id: uniqueId(), 43 | title: 'Monitoring and alerting service design communication', 44 | start: format(addDays(firstDay, 5), 'yyyy-MM-dd') + 'T10:00:00' 45 | }, 46 | { 47 | id: uniqueId(), 48 | title: 'Design system brainstorming', 49 | start: format(addDays(firstDay, 5), 'yyyy-MM-dd') + 'T11:00:00' 50 | }, 51 | 52 | { 53 | id: uniqueId(), 54 | title: 'Test Case Review', 55 | start: format(addDays(firstDay, 15), 'yyyy-MM-dd') + 'T14:00:00' 56 | }, 57 | { 58 | id: uniqueId(), 59 | title: 'Development Design Review', 60 | start: format(addDays(firstDay, 15), 'yyyy-MM-dd') + 'T16:00:00' 61 | }, 62 | 63 | { 64 | id: uniqueId(), 65 | title: '💎 Product meeting', 66 | start: todayStr + 'T09:00:00', 67 | end: todayStr + 'T10:30:00' 68 | }, 69 | { 70 | id: uniqueId(), 71 | title: '👨‍💻 Coding ', 72 | start: todayStr + 'T10:00:00', 73 | end: todayStr + 'T11:30:00' 74 | }, 75 | { 76 | id: uniqueId(), 77 | title: '📖 Leadership training', 78 | start: todayStr + 'T12:00:00', 79 | end: todayStr + 'T14:00:00' 80 | }, 81 | { 82 | id: uniqueId(), 83 | title: '☕️ Afternoon tea time', 84 | start: todayStr + 'T14:00:00', 85 | end: todayStr + 'T16:00:00' 86 | }, 87 | { 88 | id: uniqueId(), 89 | title: 'Interview engineers.', 90 | start: todayStr + 'T16:00:00', 91 | end: todayStr + 'T18:00:00' 92 | }, 93 | { 94 | id: uniqueId(), 95 | title: '🎉 Product release', 96 | allDay: true, 97 | start: format(lastDay, 'yyyy-MM-dd') + 'T14:00:00' 98 | }, 99 | { 100 | id: uniqueId(), 101 | title: '🔬 Product acceptance', 102 | start: format(lastDay, 'yyyy-MM-dd') + 'T16:00:00' 103 | } 104 | ]; 105 | -------------------------------------------------------------------------------- /src/pages/calendar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Panel, Breadcrumb } from 'rsuite'; 3 | import Calendar from './Calendar'; 4 | 5 | const Page = () => { 6 | return ( 7 | 10 |

Calendar

11 | 12 | Home 13 | Calendar 14 | 15 | 16 | } 17 | > 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default Page; 24 | -------------------------------------------------------------------------------- /src/pages/calendar/styles.less: -------------------------------------------------------------------------------- 1 | .rs-container .fc { 2 | .fc-button { 3 | display: inline-block; 4 | margin-bottom: 0; // For input.btn 5 | font-weight: @btn-font-weight; 6 | text-align: center; 7 | vertical-align: middle; 8 | cursor: pointer; 9 | white-space: nowrap; 10 | transition: @btn-transition; 11 | // Reset border style in all browser 12 | border: var(--rs-btn-default-border, none); 13 | user-select: none; 14 | text-decoration: none; 15 | color: var(--rs-btn-default-text); 16 | background-color: var(--rs-btn-default-bg); 17 | border-radius: @border-radius; 18 | box-shadow: none !important; 19 | text-transform: capitalize; 20 | 21 | .high-contrast-mode({ 22 | transition: none; 23 | }); 24 | 25 | .rs-btn-md(); 26 | 27 | .with-focus-ring(); 28 | 29 | .button-activated({ 30 | color: var(--rs-btn-default-hover-text); 31 | background-color: var(--rs-btn-default-hover-bg); 32 | text-decoration: none; 33 | }); 34 | 35 | .button-pressed({ 36 | color: var(--rs-btn-default-active-text); 37 | background-color: var(--rs-btn-default-active-bg); 38 | }); 39 | 40 | .button-disabled({ 41 | cursor: @cursor-disabled; 42 | color: var(--rs-btn-default-disabled-text); 43 | background-color: var(--rs-btn-default-disabled-bg); 44 | }); 45 | 46 | &.fc-button-active { 47 | color: var(--rs-btn-default-active-text) !important; 48 | background-color: var(--rs-btn-default-active-bg) !important; 49 | } 50 | } 51 | .fc-col-header-cell-cushion { 52 | font-weight: 500; 53 | color: var(--rs-text-primary); 54 | } 55 | 56 | .fc-daygrid-day-number { 57 | color: var(--rs-text-primary); 58 | } 59 | 60 | .fc-scrollgrid { 61 | border: none; 62 | } 63 | .fc-scrollgrid td, 64 | .fc-scrollgrid th { 65 | color: red; 66 | border: 1px solid var(--rs-border-secondary); 67 | } 68 | 69 | .fc-scrollgrid th { 70 | height: 36px; 71 | line-height: 36px; 72 | } 73 | 74 | .fc-daygrid-event { 75 | margin-top: 1px; 76 | } 77 | .fc-daygrid-dot-event { 78 | padding: 4px; 79 | background-color: var(--rs-btn-default-bg); 80 | color: var(--rs-text-primary); 81 | } 82 | 83 | .fc-event-main { 84 | padding: 4px; 85 | } 86 | .fc-h-event { 87 | background-color: var(--rs-btn-primary-bg); 88 | color: var(--rs-btn-primary-text); 89 | border-color: var(--rs-btn-primary-bg); 90 | } 91 | .fc-event-title { 92 | font-weight: 200; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/pages/dashboard/BarChart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Chart from 'react-apexcharts'; 3 | import { Panel, Stack } from 'rsuite'; 4 | 5 | interface BarChartProps { 6 | title?: React.ReactNode; 7 | actions?: React.ReactNode; 8 | data: any; 9 | type?: 10 | | 'line' 11 | | 'area' 12 | | 'bar' 13 | | 'histogram' 14 | | 'pie' 15 | | 'donut' 16 | | 'radialBar' 17 | | 'scatter' 18 | | 'bubble' 19 | | 'heatmap' 20 | | 'treemap' 21 | | 'boxPlot' 22 | | 'candlestick' 23 | | 'radar' 24 | | 'polarArea' 25 | | 'rangeBar'; 26 | options?: any; 27 | labels?: string[]; 28 | } 29 | 30 | const defaultOptions = { 31 | chart: { 32 | fontFamily: 'inherit', 33 | parentHeightOffset: 0, 34 | toolbar: { 35 | show: false 36 | }, 37 | animations: { 38 | enabled: false 39 | }, 40 | stacked: true 41 | }, 42 | plotOptions: { 43 | bar: { 44 | columnWidth: '50%' 45 | } 46 | }, 47 | dataLabels: { 48 | enabled: false 49 | }, 50 | fill: { 51 | opacity: 1 52 | }, 53 | grid: { 54 | padding: { 55 | top: -20, 56 | right: 0, 57 | left: -4, 58 | bottom: -4 59 | }, 60 | strokeDashArray: 4, 61 | xaxis: { 62 | lines: { 63 | show: true 64 | } 65 | } 66 | }, 67 | xaxis: { 68 | tooltip: { 69 | enabled: false 70 | }, 71 | axisBorder: { 72 | show: false 73 | }, 74 | type: 'datetime' 75 | }, 76 | yaxis: { 77 | labels: { 78 | padding: 4 79 | } 80 | }, 81 | colors: ['#206bc4', '#79a6dc', '#bfe399'], 82 | legend: { 83 | show: false 84 | } 85 | }; 86 | 87 | const BarChart = ({ title, actions, data, type, labels, options }: BarChartProps) => ( 88 | 92 | {title} 93 | {actions} 94 | 95 | } 96 | > 97 | 103 | 104 | ); 105 | 106 | export default BarChart; 107 | -------------------------------------------------------------------------------- /src/pages/dashboard/ColorfulChart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import Chart from 'react-apexcharts'; 4 | 5 | interface ColorfulChartProps { 6 | className?: string; 7 | title: string; 8 | data: any; 9 | type?: 10 | | 'line' 11 | | 'area' 12 | | 'bar' 13 | | 'histogram' 14 | | 'pie' 15 | | 'donut' 16 | | 'radialBar' 17 | | 'scatter' 18 | | 'bubble' 19 | | 'heatmap' 20 | | 'treemap' 21 | | 'boxPlot' 22 | | 'candlestick' 23 | | 'radar' 24 | | 'polarArea' 25 | | 'rangeBar'; 26 | options?: any; 27 | } 28 | 29 | const defaultOptions = { 30 | chart: { 31 | id: 'sparkline1', 32 | type: 'line', 33 | height: 140, 34 | sparkline: { 35 | enabled: true 36 | }, 37 | group: 'sparklines' 38 | }, 39 | stroke: { 40 | curve: 'smooth' 41 | }, 42 | markers: { 43 | size: 0 44 | }, 45 | tooltip: { 46 | cssClass: 'tooltip-custom', 47 | marker: { 48 | show: false 49 | }, 50 | fixed: { 51 | enabled: true, 52 | position: 'right' 53 | }, 54 | x: { 55 | show: false 56 | } 57 | }, 58 | colors: ['#fff'] 59 | }; 60 | 61 | const ColorfulChart = ({ className, title, data, type, options }: ColorfulChartProps) => ( 62 |
63 |

{title}

64 | 70 |
71 | ); 72 | 73 | export default ColorfulChart; 74 | -------------------------------------------------------------------------------- /src/pages/dashboard/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row, Col, Panel, ButtonGroup, Button } from 'rsuite'; 3 | import * as images from '../../images/charts'; 4 | import BarChart from './BarChart'; 5 | import PieChart from './PieChart'; 6 | import DataTable from './DataTable'; 7 | 8 | const barChartData = [ 9 | { 10 | name: 'Web', 11 | data: [ 12 | 11, 8, 9, 10, 3, 11, 11, 11, 12, 13, 2, 12, 5, 8, 22, 6, 8, 6, 4, 1, 8, 24, 29, 51, 40, 47, 13 | 23, 26, 50, 26, 22, 27, 46, 47, 81, 46, 40 14 | ] 15 | }, 16 | { 17 | name: 'Social', 18 | data: [ 19 | 7, 5, 4, 3, 3, 11, 4, 7, 5, 12, 12, 15, 13, 12, 6, 7, 7, 1, 5, 5, 2, 12, 4, 6, 18, 3, 5, 2, 20 | 13, 15, 20, 47, 18, 15, 11, 10, 9 21 | ] 22 | }, 23 | { 24 | name: 'Other', 25 | data: [ 26 | 4, 9, 11, 7, 8, 3, 6, 5, 5, 4, 6, 4, 11, 10, 3, 6, 7, 5, 2, 8, 4, 9, 9, 2, 6, 7, 5, 1, 8, 3, 27 | 12, 3, 4, 9, 7, 11, 10 28 | ] 29 | } 30 | ]; 31 | 32 | const Dashboard = () => { 33 | return ( 34 | <> 35 | 36 | 37 | 38 | 39 |
Page Views
40 |
281,358
41 |
42 | 43 | 44 | 45 | 46 |
Visits
47 |
251,901
48 |
49 | 50 | 51 | 52 | 53 |
Unique Visitors
54 |
25,135
55 |
56 | 57 |
58 | 59 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | } 70 | data={barChartData} 71 | type="bar" 72 | labels={[ 73 | '2022-01-20', 74 | '2022-01-21', 75 | '2022-01-22', 76 | '2022-01-23', 77 | '2022-01-24', 78 | '2022-01-25', 79 | '2022-01-26', 80 | '2022-01-27', 81 | '2022-01-28', 82 | '2022-01-29', 83 | '2022-01-30', 84 | '2022-02-01', 85 | '2022-02-02', 86 | '2022-02-03', 87 | '2022-02-04', 88 | '2022-02-05', 89 | '2022-02-06', 90 | '2022-02-07', 91 | '2022-02-08', 92 | '2022-02-09', 93 | '2022-02-10', 94 | '2022-02-11', 95 | '2022-02-12', 96 | '2022-02-13', 97 | '2022-02-14', 98 | '2022-02-15', 99 | '2022-02-16', 100 | '2022-02-17', 101 | '2022-02-18', 102 | '2022-02-19', 103 | '2022-02-20', 104 | '2022-02-21', 105 | '2022-02-22', 106 | '2022-02-23', 107 | '2022-02-24', 108 | '2022-02-25', 109 | '2022-02-26' 110 | ]} 111 | /> 112 | 113 | 114 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 133 | 134 | 135 | 136 | ); 137 | }; 138 | 139 | export default Dashboard; 140 | -------------------------------------------------------------------------------- /src/pages/dashboard/DataTable.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Table, Panel } from 'rsuite'; 3 | 4 | const { Column, HeaderCell, Cell } = Table; 5 | 6 | const data = [ 7 | { 8 | id: 1, 9 | url: 'https://rsuitejs.com', 10 | visits: '105,253', 11 | unique: '23,361', 12 | bounce: '11%' 13 | }, 14 | { 15 | id: 2, 16 | url: 'https://rsuitejs.com/components/overview/', 17 | visits: '103,643', 18 | unique: '23,385', 19 | bounce: '17%' 20 | }, 21 | { 22 | id: 3, 23 | url: 'https://rsuitejs.com/components/table/', 24 | visits: '140,013', 25 | unique: '41,256', 26 | bounce: '13%' 27 | }, 28 | { 29 | id: 4, 30 | url: 'https://rsuitejs.com/components/drawer/', 31 | visits: '194,532', 32 | unique: '19,038', 33 | bounce: '18%' 34 | }, 35 | { 36 | id: 5, 37 | url: 'https://rsuitejs.com/guide/usage/', 38 | visits: '26,353', 39 | unique: '1,000', 40 | bounce: '20%' 41 | }, 42 | { 43 | id: 6, 44 | url: 'https://rsuitejs.com/guide/customization/', 45 | visits: '11,973', 46 | unique: '4,786', 47 | bounce: '24%' 48 | } 49 | ]; 50 | 51 | const DataTable = () => { 52 | return ( 53 | 54 | 55 | 56 | PAGE NAME 57 | 58 | {rowData => { 59 | return ( 60 | 61 | {rowData.url} 62 | 63 | ); 64 | }} 65 | 66 | 67 | 68 | 69 | VISITORS 70 | 71 | 72 | 73 | 74 | UNIQUE 75 | 76 | 77 | 78 | 79 | BOUNCE RATE 80 | 81 | 82 |
83 |
84 | ); 85 | }; 86 | 87 | export default DataTable; 88 | -------------------------------------------------------------------------------- /src/pages/dashboard/PieChart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Chart from 'react-apexcharts'; 3 | import { Panel } from 'rsuite'; 4 | 5 | interface PieChartProps { 6 | title: string; 7 | data: any; 8 | type?: 9 | | 'line' 10 | | 'area' 11 | | 'bar' 12 | | 'histogram' 13 | | 'pie' 14 | | 'donut' 15 | | 'radialBar' 16 | | 'scatter' 17 | | 'bubble' 18 | | 'heatmap' 19 | | 'treemap' 20 | | 'boxPlot' 21 | | 'candlestick' 22 | | 'radar' 23 | | 'polarArea' 24 | | 'rangeBar'; 25 | options?: any; 26 | labels?: string[]; 27 | } 28 | 29 | const defaultOptions = { 30 | dataLabels: { 31 | enabled: false 32 | }, 33 | plotOptions: { 34 | pie: { 35 | customScale: 0.8, 36 | donut: { 37 | size: '75%' 38 | }, 39 | offsetY: 0 40 | }, 41 | stroke: { 42 | colors: undefined 43 | } 44 | }, 45 | colors: ['#5f71e4', '#2dce88', '#fa6340', '#f5365d', '#13cdef'], 46 | legend: { 47 | position: 'bottom', 48 | offsetY: 0 49 | } 50 | }; 51 | 52 | const PieChart = ({ title, data, type, labels, options }: PieChartProps) => ( 53 | 54 | 60 | 61 | ); 62 | 63 | export default PieChart; 64 | -------------------------------------------------------------------------------- /src/pages/dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Panel } from 'rsuite'; 3 | import Dashboard from './Dashboard'; 4 | import Copyright from '@/components/Copyright'; 5 | import PageToolbar from '@/components/PageToolbar'; 6 | 7 | const Page = () => { 8 | return ( 9 | Dashboard}> 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default Page; 18 | -------------------------------------------------------------------------------- /src/pages/dashboard/styles.less: -------------------------------------------------------------------------------- 1 | .dashboard-header { 2 | .rs-panel { 3 | // background: #fff; 4 | color: #fff; 5 | } 6 | .chart-img { 7 | width: 100px; 8 | position: absolute; 9 | left: 26px; 10 | top: 34px; 11 | opacity: 0.5; 12 | } 13 | .trend-box { 14 | .rs-panel-body { 15 | text-align: right; 16 | .value { 17 | font-size: 36px; 18 | } 19 | } 20 | } 21 | } 22 | 23 | .ct-chart-white-colors() { 24 | .ct-label { 25 | color: #eee; 26 | } 27 | .ct-series-a { 28 | .ct-bar, 29 | .ct-slice-donut, 30 | .ct-line, 31 | .ct-point { 32 | stroke: #eee; 33 | } 34 | } 35 | .ct-grid { 36 | stroke: hsla(0, 0%, 100%, 0.2); 37 | } 38 | } 39 | .colorful-chart { 40 | color: #fff; 41 | margin-top: 30px; 42 | border-radius: 6px; 43 | h3 { 44 | line-height: 22px; 45 | text-align: right; 46 | color: rgba(255, 255, 255, 0.5); 47 | padding: 10px; 48 | font-size: 18px; 49 | } 50 | } 51 | 52 | .ct-chart-magenta { 53 | background: linear-gradient(60deg, #ec407a, #d81b60); 54 | .ct-chart-white-colors(); 55 | } 56 | 57 | .ct-chart-orange { 58 | background: linear-gradient(60deg, #ffa726, #fb8c00); 59 | .ct-chart-white-colors(); 60 | } 61 | 62 | .ct-chart-azure { 63 | background: linear-gradient(60deg, #26c6da, #00acc1); 64 | .ct-chart-white-colors(); 65 | } 66 | 67 | .card { 68 | background: #fff; 69 | margin-top: 30px; 70 | border-radius: 6px; 71 | 72 | h3 { 73 | line-height: 22px; 74 | margin-bottom: 20px; 75 | font-size: 18px; 76 | } 77 | } 78 | 79 | .apexcharts-theme-light { 80 | .tooltip-custom { 81 | color: @B700; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/pages/forms/basic/BasicForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Form, 4 | RadioGroup, 5 | Radio, 6 | Checkbox, 7 | CheckboxGroup, 8 | DatePicker, 9 | DateRangePicker, 10 | CheckPicker, 11 | SelectPicker, 12 | TagPicker, 13 | Input, 14 | TagInput, 15 | MaskedInput, 16 | InputPicker, 17 | InputNumber, 18 | Cascader, 19 | MultiCascader, 20 | Rate, 21 | Uploader, 22 | Message, 23 | Divider, 24 | TreePicker, 25 | CheckTreePicker, 26 | ButtonToolbar, 27 | Button, 28 | Toggle, 29 | AutoComplete 30 | } from 'rsuite'; 31 | import PageContent from '@/components/PageContent'; 32 | 33 | import { mockTreeData } from '@/data/mock'; 34 | 35 | const treeData = mockTreeData({ limits: [2, 3, 3], labels: ['Provincial', 'County', 'Town'] }); 36 | const selectData = ['Eugenia', 'Bryan', 'Linda', 'Nancy', 'Lloyd', 'Alice'].map(item => ({ 37 | label: item, 38 | value: item 39 | })); 40 | 41 | const Textarea = React.forwardRef((props, ref) => ( 42 | 43 | )); 44 | 45 | const BasicForm = () => { 46 | return ( 47 | 48 | 49 | The following demonstrates the common components of forms provided by rsuite. For more 50 | detailed usage of forms, please refer to the{' '} 51 | 52 | form usage 53 | 54 | . 55 | 56 | 57 |
58 | 59 | Input 60 | 61 | 62 | 63 | 64 | MaskedInput 65 | 86 | 87 | 88 | 89 | TagInput 90 | 91 | 92 | 93 | 94 | InputNumber 95 | 96 | 97 | 98 | 99 | AutoComplete 100 | 101 | 102 | 103 | 104 | Textarea 105 | 106 | 107 | 108 | 109 | Checkbox 110 | 111 | HTML 112 | CSS 113 | Javascript 114 | 115 | 116 | 117 | 118 | Radio 119 | 120 | HTML 121 | CSS 122 | Javascript 123 | 124 | 125 | 126 | 127 | DatePicker 128 | 129 | 130 | 131 | 132 | DateRangePicker 133 | 134 | 135 | 136 | 137 | CheckPicker 138 | 139 | 140 | 141 | 142 | SelectPicker 143 | 144 | 145 | 146 | 147 | TagPicker 148 | 149 | 150 | 151 | 152 | InputPicker 153 | 154 | 155 | 156 | 157 | Cascader 158 | 159 | 160 | 161 | 162 | MultiCascader 163 | 164 | 165 | 166 | 167 | TreePicker 168 | 169 | 170 | 171 | 172 | CheckTreePicker 173 | 174 | 175 | 176 | 177 | Rate 178 | 179 | 180 | 181 | 182 | Uploader 183 | 184 | 185 | 186 | 187 | Toggle 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
198 |
199 | ); 200 | }; 201 | 202 | export default BasicForm; 203 | -------------------------------------------------------------------------------- /src/pages/forms/basic/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BasicForm from './BasicForm'; 3 | 4 | import { Breadcrumb, Panel } from 'rsuite'; 5 | 6 | const Page = () => { 7 | return ( 8 | 11 |

Basic Form

12 | 13 | Home 14 | Forms 15 | Basic Form 16 | 17 | 18 | } 19 | > 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default Page; 26 | -------------------------------------------------------------------------------- /src/pages/forms/basic/styles.less: -------------------------------------------------------------------------------- 1 | .basic-form { 2 | .rs-input, 3 | .rs-input-number, 4 | .rs-picker { 5 | width: 400px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/BusinessDetailForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, SelectPicker } from 'rsuite'; 3 | import Textarea from '@/components/Textarea'; 4 | import FormHeader from './FormHeader'; 5 | 6 | const BusinessDetailForm = () => { 7 | return ( 8 |
9 | 13 | 14 | 15 | Business Name 16 | 17 | 18 | 19 | 20 | Shortened Descriptor 21 | 22 | 23 | Customers will see this shortened version of your statement descriptor. 24 | 25 | 26 | 27 | 28 | Corporation Type 29 | 47 | 48 | Different team sizes will be assigned different management modes. Of course the fees are 49 | different. 50 | 51 | 52 | 53 | 54 | Business Description 55 | 56 | 57 | 58 | 59 | Contact Email 60 | 61 | 62 | 63 | ); 64 | }; 65 | 66 | export default BusinessDetailForm; 67 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/Completed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ButtonToolbar, Button, Stack } from 'rsuite'; 3 | import CheckRoundIcon from '@rsuite/icons/CheckRound'; 4 | 5 | const Completed = () => { 6 | return ( 7 |
8 |
9 | 10 | 11 |
12 |
Your Are Done!
13 |

You can start working on a new project.

14 |
15 |
16 |
17 | 18 |

19 | Once you have created this project, if you return to Project Web App you see it listed as a 20 | project in the Project Center. Updates made to the task list on the project site are 21 | reflected in the Project Center in Project Web App. 22 |

23 |

You can also click the button below to start working on the project.

24 | 25 | 26 | 27 | 28 | 29 |
30 | ); 31 | }; 32 | 33 | export default Completed; 34 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/FormHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface FormHeaderProps { 4 | title: string; 5 | description: string; 6 | } 7 | 8 | const FormHeader = ({ title, description }: FormHeaderProps) => { 9 | return ( 10 |
11 |
{title}
12 |

{description}

13 |
14 | ); 15 | }; 16 | 17 | export default FormHeader; 18 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/ProjectInfoForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Stack, InputGroup } from 'rsuite'; 3 | import { Icon } from '@rsuite/icons'; 4 | import { VscLock, VscWorkspaceTrusted } from 'react-icons/vsc'; 5 | import RadioTile from '@/components/RadioTile'; 6 | import Textarea from '@/components/Textarea'; 7 | import FormHeader from './FormHeader'; 8 | 9 | const ProjectInfoForm = () => { 10 | const [level, setLevel] = React.useState('Private'); 11 | 12 | return ( 13 |
14 | 19 | 20 | 21 | Project Name 22 | 23 | Project names must be unique. 24 | 25 | 26 | 27 | Project URL 28 | 29 | 30 | https://rsuitejs.com/ 31 | 32 | 33 | 34 | Want to house several dependent projects under the same namespace? Create a group. 35 | 36 | 37 | 38 | 39 | Project description (optional) 40 | 41 | 42 | 43 | 44 | Visibility Level 45 | 46 | } 48 | title="Private" 49 | value={level} 50 | name="Private" 51 | onChange={setLevel} 52 | > 53 | Project access must be granted explicitly to each user. If this project is part of a 54 | group, access will be granted to members of the group. 55 | 56 | } 58 | title="Internal" 59 | value={level} 60 | name="Internal" 61 | onChange={setLevel} 62 | > 63 | The project can be accessed by any logged in user except external users. 64 | 65 | 66 | 67 | 68 | ); 69 | }; 70 | 71 | export default ProjectInfoForm; 72 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/ProjectTypeForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Form, Stack } from 'rsuite'; 3 | import RadioTile from '@/components/RadioTile'; 4 | import { Icon } from '@rsuite/icons'; 5 | import { VscNotebookTemplate, VscRepoClone, VscFile } from 'react-icons/vsc'; 6 | import FormHeader from './FormHeader'; 7 | 8 | const ProjectTypeForm = () => { 9 | const [type, setType] = useState('personal'); 10 | 11 | return ( 12 |
13 | 17 | 18 | 19 | } 21 | title="Create blank project" 22 | value={type} 23 | name="personal" 24 | onChange={setType} 25 | > 26 | Create a blank project to house your files, plan your work, and collaborate on code, among 27 | other things. 28 | 29 | } 31 | title="Create from template" 32 | value={type} 33 | name="brand" 34 | onChange={setType} 35 | > 36 | Create a project pre-populated with the necessary files to get you started quickly. 37 | 38 | } 40 | title="Import project" 41 | value={type} 42 | name="group" 43 | onChange={setType} 44 | > 45 | Migrate your data from an external source like GitHub, Bitbucket, or another instance of 46 | GitLab. 47 | 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default ProjectTypeForm; 54 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/TeamSettingsForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Form, Stack, SelectPicker } from 'rsuite'; 3 | import RadioTile from '@/components/RadioTile'; 4 | import { Icon } from '@rsuite/icons'; 5 | import { FaGit, FaGithub, FaGitlab } from 'react-icons/fa'; 6 | 7 | import FormHeader from './FormHeader'; 8 | 9 | const TeamSettingsForm = () => { 10 | const [type, setType] = useState('1'); 11 | 12 | return ( 13 |
14 | 18 | 19 | 20 | Team Name 21 | 22 | 23 | 24 | 25 | Specify Team Size 26 | 5 people' }, 32 | { value: 2, label: '5-20 people' }, 33 | { value: 3, label: '20-50 people' }, 34 | { value: 4, label: '50+ people' } 35 | ]} 36 | block 37 | /> 38 | 39 | Different team sizes will be assigned different management modes. Of course the fees are 40 | different. 41 | 42 | 43 | 44 | 45 | Choose a team workflow 46 | 47 | } 49 | title="Git Flow" 50 | value={type} 51 | name="1" 52 | onChange={setType} 53 | > 54 | Considered to be a bit complicated and advanced for many of today’s projects, GitFlow 55 | enables parallel development where developers can work separately from the master branch 56 | on features where a feature branch is created from the master branch. 57 | 58 | } 60 | title="GitHub Flow" 61 | value={type} 62 | name="2" 63 | onChange={setType} 64 | > 65 | GitHub Flow is a simpler alternative to GitFlow ideal for smaller teams as they don’t 66 | need to manage multiple versions. 67 | 68 | 69 | } 71 | title="GitLab Flow" 72 | value={type} 73 | name="3" 74 | onChange={setType} 75 | > 76 | GitLab Flow is a simpler alternative to GitFlow that combines feature-driven development 77 | and feature branching with issue tracking. 78 | 79 | 80 | 81 | 82 | ); 83 | }; 84 | 85 | export default TeamSettingsForm; 86 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/WizardForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Steps, Divider, Stack, IconButton } from 'rsuite'; 3 | import PageContent from '@/components/PageContent'; 4 | 5 | import PageNextIcon from '@rsuite/icons/PageNext'; 6 | import PagePreviousIcon from '@rsuite/icons/PagePrevious'; 7 | 8 | import ProjectTypeForm from './ProjectTypeForm'; 9 | import TeamSettingsForm from './TeamSettingsForm'; 10 | import BusinessDetailForm from './BusinessDetailForm'; 11 | import ProjectInfoForm from './ProjectInfoForm'; 12 | import Completed from './Completed'; 13 | 14 | const forms = [ProjectTypeForm, ProjectInfoForm, TeamSettingsForm, BusinessDetailForm, Completed]; 15 | 16 | const WizardForm = () => { 17 | const [step, setStep] = useState(0); 18 | const Form = forms[step]; 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | {step !== 0 && ( 37 | } onClick={() => setStep(Math.max(step - 1, 0))}> 38 | Back 39 | 40 | )} 41 | 42 | {step !== forms.length - 1 && ( 43 | } 45 | placement="right" 46 | appearance="primary" 47 | onClick={() => setStep(Math.min(step + 1, 4))} 48 | > 49 | Continue 50 | 51 | )} 52 | 53 |
54 |
55 | ); 56 | }; 57 | 58 | export default WizardForm; 59 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import WizardForm from './WizardForm'; 3 | 4 | import { Breadcrumb, Panel } from 'rsuite'; 5 | 6 | const Page = () => { 7 | return ( 8 | 11 |

Wizard Form

12 | 13 | Home 14 | Forms 15 | Wizard Form 16 | 17 | 18 | } 19 | > 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default Page; 26 | -------------------------------------------------------------------------------- /src/pages/forms/wizard/styles.less: -------------------------------------------------------------------------------- 1 | .wizard-form { 2 | width: 1000px; 3 | margin: 0 auto; 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/tables/members/Cells.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Popover, Whisper, Checkbox, Dropdown, IconButton, Table, CellProps } from 'rsuite'; 3 | import MoreIcon from '@rsuite/icons/legacy/More'; 4 | 5 | const { Cell } = Table; 6 | 7 | export const NameCell = ({ rowData, dataKey, ...props }: CellProps) => { 8 | const speaker = ( 9 | 10 |

11 | Name: {rowData.name} 12 |

13 |

14 | Gender: {rowData.gender} 15 |

16 |

17 | City: {rowData.city} 18 |

19 |

20 | Street: {rowData.street} 21 |

22 |
23 | ); 24 | 25 | return ( 26 | 27 | 28 | {dataKey ? rowData[dataKey] : null} 29 | 30 | 31 | ); 32 | }; 33 | 34 | export const ImageCell = ({ rowData, dataKey, ...props }: CellProps) => ( 35 | 36 |
47 | 48 |
49 |
50 | ); 51 | 52 | export const CheckCell = ({ 53 | rowData, 54 | onChange, 55 | checkedKeys, 56 | dataKey, 57 | ...props 58 | }: CellProps & { 59 | checkedKeys: number[]; 60 | onChange: (value: any, checked: boolean) => void; 61 | }) => ( 62 | 63 |
64 | item === rowData[dataKey!])} 69 | /> 70 |
71 |
72 | ); 73 | 74 | const renderMenu = ({ onClose, left, top, className }: any, ref) => { 75 | const handleSelect = eventKey => { 76 | onClose(); 77 | console.log(eventKey); 78 | }; 79 | return ( 80 | 81 | 82 | Follow 83 | Sponsor 84 | Add to friends 85 | View Profile 86 | Block 87 | 88 | 89 | ); 90 | }; 91 | 92 | export const ActionCell = props => { 93 | return ( 94 | 95 | 96 | } /> 97 | 98 | 99 | ); 100 | }; 101 | -------------------------------------------------------------------------------- /src/pages/tables/members/DataTable.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { 3 | Input, 4 | InputGroup, 5 | Table, 6 | Button, 7 | DOMHelper, 8 | Progress, 9 | Checkbox, 10 | Stack, 11 | SelectPicker 12 | } from 'rsuite'; 13 | import SearchIcon from '@rsuite/icons/Search'; 14 | import MoreIcon from '@rsuite/icons/legacy/More'; 15 | import DrawerView from './DrawerView'; 16 | import { mockUsers } from '@/data/mock'; 17 | import { NameCell, ImageCell, CheckCell, ActionCell } from './Cells'; 18 | 19 | const data = mockUsers(20); 20 | 21 | const { Column, HeaderCell, Cell } = Table; 22 | const { getHeight } = DOMHelper; 23 | 24 | const ratingList = Array.from({ length: 5 }).map((_, index) => { 25 | return { 26 | value: index + 1, 27 | label: Array.from({ length: index + 1 }) 28 | .map(() => '⭐️') 29 | .join('') 30 | }; 31 | }); 32 | 33 | const DataTable = () => { 34 | const [showDrawer, setShowDrawer] = useState(false); 35 | const [checkedKeys, setCheckedKeys] = useState([]); 36 | const [sortColumn, setSortColumn] = useState(); 37 | const [sortType, setSortType] = useState(); 38 | const [searchKeyword, setSearchKeyword] = useState(''); 39 | const [rating, setRating] = useState(null); 40 | 41 | let checked = false; 42 | let indeterminate = false; 43 | 44 | if (checkedKeys.length === data.length) { 45 | checked = true; 46 | } else if (checkedKeys.length === 0) { 47 | checked = false; 48 | } else if (checkedKeys.length > 0 && checkedKeys.length < data.length) { 49 | indeterminate = true; 50 | } 51 | 52 | const handleCheckAll = (_value, checked) => { 53 | const keys = checked ? data.map(item => item.id) : []; 54 | setCheckedKeys(keys); 55 | }; 56 | const handleCheck = (value, checked) => { 57 | const keys = checked ? [...checkedKeys, value] : checkedKeys.filter(item => item !== value); 58 | setCheckedKeys(keys); 59 | }; 60 | 61 | const handleSortColumn = (sortColumn, sortType) => { 62 | setSortColumn(sortColumn); 63 | setSortType(sortType); 64 | }; 65 | 66 | const filteredData = () => { 67 | const filtered = data.filter(item => { 68 | if (!item.name.includes(searchKeyword)) { 69 | return false; 70 | } 71 | 72 | if (rating && item.rating !== rating) { 73 | return false; 74 | } 75 | 76 | return true; 77 | }); 78 | 79 | if (sortColumn && sortType) { 80 | return filtered.sort((a, b) => { 81 | let x: any = a[sortColumn]; 82 | let y: any = b[sortColumn]; 83 | 84 | if (typeof x === 'string') { 85 | x = x.charCodeAt(0); 86 | } 87 | if (typeof y === 'string') { 88 | y = y.charCodeAt(0); 89 | } 90 | 91 | if (sortType === 'asc') { 92 | return x - y; 93 | } else { 94 | return y - x; 95 | } 96 | }); 97 | } 98 | return filtered; 99 | }; 100 | 101 | return ( 102 | <> 103 | 104 | 107 | 108 | 109 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 132 | 133 | Id 134 | 135 | 136 | 137 | 138 | 139 |
140 | 146 |
147 |
148 | 149 |
150 | 151 | Avatar 152 | 153 | 154 | 155 | 156 | Name 157 | 158 | 159 | 160 | 161 | Skill Proficiency 162 | 163 | {rowData => } 164 | 165 | 166 | 167 | 168 | Rating 169 | 170 | {rowData => 171 | Array.from({ length: rowData.rating }).map((_, i) => ⭐️) 172 | } 173 | 174 | 175 | 176 | 177 | Income 178 | {rowData => `$${rowData.amount}`} 179 | 180 | 181 | 182 | Email 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
193 | 194 | setShowDrawer(false)} /> 195 | 196 | ); 197 | }; 198 | 199 | export default DataTable; 200 | -------------------------------------------------------------------------------- /src/pages/tables/members/DrawerView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Drawer, 4 | DrawerProps, 5 | Button, 6 | Form, 7 | Stack, 8 | InputNumber, 9 | InputGroup, 10 | Slider, 11 | Rate 12 | } from 'rsuite'; 13 | 14 | const DrawerView = (props: DrawerProps) => { 15 | const { onClose, ...rest } = props; 16 | return ( 17 | 18 | 19 | Add a new member 20 | 21 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | First Name 35 | 36 | 37 | 38 | Last Name 39 | 40 | 41 | 42 | 43 | Email 44 | 45 | 46 | 47 | City 48 | 49 | 50 | 51 | Street 52 | 53 | 54 | 55 | 56 | Rating 57 | 58 | 59 | 60 | 61 | Skill Proficiency 62 | 63 | 64 | 65 | 66 | Income 67 | 68 | $ 69 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default DrawerView; 79 | -------------------------------------------------------------------------------- /src/pages/tables/members/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Breadcrumb, Panel } from 'rsuite'; 3 | import DataTable from './DataTable'; 4 | 5 | const Page = () => { 6 | return ( 7 | 10 |

Members

11 | 12 | Home 13 | Tables 14 | Members 15 | 16 | 17 | } 18 | > 19 | 20 |
21 | ); 22 | }; 23 | 24 | export default Page; 25 | -------------------------------------------------------------------------------- /src/pages/tables/members/styles.less: -------------------------------------------------------------------------------- 1 | .table-toolbar { 2 | padding: 20px; 3 | background: #fff; 4 | border-radius: 4px 4px 0 0; 5 | 6 | } 7 | 8 | .rs-drawer-footer { 9 | text-align: left; 10 | } 11 | 12 | .rs-table { 13 | background: #fff; 14 | 15 | .link-group { 16 | cursor: pointer; 17 | .rs-table-cell-content { 18 | padding: 5px; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/tables/members/users.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 1, 4 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/justinrob/128.jpg', 5 | city: 'New Amieshire', 6 | email: 'Leora13@yahoo.com', 7 | firstName: 'Ernest Schuppe', 8 | lastName: 'Schuppe', 9 | street: 'Ratke Port', 10 | zipCode: '17026-3154', 11 | date: '2016-09-23T07:57:40.195Z', 12 | bs: 'global drive functionalities', 13 | catchPhrase: 'Intuitive impactful software', 14 | companyName: 'Lebsack - Nicolas', 15 | words: 'saepe et omnis', 16 | sentence: 'Quos aut sunt id nihil qui.', 17 | stars: 820, 18 | followers: 70 19 | }, 20 | { 21 | id: 2, 22 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/thaisselenator_/128.jpg', 23 | city: 'New Gust', 24 | email: 'Mose_Gerhold51@yahoo.com', 25 | firstName: 'Janis', 26 | lastName: 'Vandervort', 27 | street: 'Dickinson Keys', 28 | zipCode: '43767', 29 | date: '2017-03-06T09:59:12.551Z', 30 | bs: 'e-business maximize bandwidth', 31 | catchPhrase: 'De-engineered discrete secured line', 32 | companyName: 'Glover - Hermiston', 33 | words: 'deleniti dolor nihil', 34 | sentence: 'Illo quidem libero corporis laborum.', 35 | stars: 1200, 36 | followers: 170 37 | }, 38 | { 39 | id: 3, 40 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/arpitnj/128.jpg', 41 | city: 'Lefflerstad', 42 | email: 'Frieda.Sauer61@gmail.com', 43 | firstName: 'Makenzie', 44 | lastName: 'Bode', 45 | street: 'Legros Divide', 46 | zipCode: '54812', 47 | date: '2016-12-08T13:44:26.557Z', 48 | bs: 'plug-and-play e-enable content', 49 | catchPhrase: 'Ergonomic 6th generation challenge', 50 | companyName: 'Williamson - Kassulke', 51 | words: 'quidem earum magnam', 52 | sentence: 'Nam qui perferendis ut rem vitae saepe.', 53 | stars: 610, 54 | followers: 170 55 | }, 56 | { 57 | id: 4, 58 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/brajeshwar/128.jpg', 59 | city: 'East Catalina', 60 | email: 'Eloisa.OHara@hotmail.com', 61 | firstName: 'Ciara', 62 | lastName: 'Towne', 63 | street: 'Schimmel Ramp', 64 | zipCode: '76315-2246', 65 | date: '2016-07-19T12:54:30.994Z', 66 | bs: 'extensible innovate e-business', 67 | catchPhrase: 'Upgradable local model', 68 | companyName: 'Hilpert, Eichmann and Brown', 69 | words: 'exercitationem rerum sit', 70 | sentence: 'Qui rerum ipsa atque qui.', 71 | stars: 5322, 72 | followers: 170 73 | }, 74 | { 75 | id: 5, 76 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/dev_essentials/128.jpg', 77 | city: 'Ritchieborough', 78 | email: 'Brisa46@hotmail.com', 79 | firstName: 'Suzanne', 80 | lastName: 'Wolff', 81 | street: 'Lemuel Radial', 82 | zipCode: '88870-3897', 83 | date: '2017-02-23T17:11:53.875Z', 84 | bs: 'back-end orchestrate networks', 85 | catchPhrase: 'Exclusive human-resource knowledge base', 86 | companyName: 'Mayer - Considine', 87 | words: 'voluptatum tempore at', 88 | sentence: 'Enim quia deleniti molestiae aut.', 89 | stars: 852, 90 | followers: 770 91 | }, 92 | { 93 | id: 6, 94 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/petrangr/128.jpg', 95 | city: 'Lake Emery', 96 | email: 'Cody.Schultz56@gmail.com', 97 | firstName: 'Alessandra', 98 | lastName: 'Feeney', 99 | street: 'Mosciski Estate', 100 | zipCode: '81514', 101 | date: '2016-06-30T05:23:18.734Z', 102 | bs: 'sexy evolve technologies', 103 | catchPhrase: 'Virtual hybrid throughput', 104 | companyName: 'Nikolaus and Sons', 105 | words: 'alias minus repudiandae', 106 | sentence: 'Sed qui eius excepturi sunt voluptates.', 107 | stars: 3209, 108 | followers: 2780 109 | }, 110 | { 111 | id: 7, 112 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/knilob/128.jpg', 113 | city: 'East Dejuan', 114 | email: 'Enrico_Beer@yahoo.com', 115 | firstName: 'Margret', 116 | lastName: 'Heller', 117 | street: 'Gunner Drive', 118 | zipCode: '17423-9317', 119 | date: '2017-03-13T21:09:47.253Z', 120 | bs: 'wireless morph synergies', 121 | catchPhrase: 'Profit-focused radical help-desk', 122 | companyName: 'Corwin, Maggio and Wintheiser', 123 | words: 'temporibus possimus neque', 124 | sentence: 'Eum amet ea non natus qui assumenda illo officia qui.', 125 | stars: 9920, 126 | followers: 570 127 | }, 128 | { 129 | id: 8, 130 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/tom_even/128.jpg', 131 | city: 'Schummtown', 132 | email: 'Mitchel.Herman@yahoo.com', 133 | firstName: 'Emiliano', 134 | lastName: 'Moore', 135 | street: 'Maria Junctions', 136 | zipCode: '33930-7081', 137 | date: '2016-03-27T07:26:57.345Z', 138 | bs: 'customized integrate e-tailers', 139 | catchPhrase: 'Total system-worthy contingency', 140 | companyName: 'Gulgowski - Botsford', 141 | words: 'deleniti ipsa hic', 142 | sentence: 'Ducimus id quaerat neque.', 143 | stars: 3820, 144 | followers: 880 145 | }, 146 | { 147 | id: 9, 148 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/chandlervdw/128.jpg', 149 | city: 'Gilberthaven', 150 | email: 'Gaylord_Reichel16@yahoo.com', 151 | firstName: 'Alessandra', 152 | lastName: 'Smith', 153 | street: 'Kali Spurs', 154 | zipCode: '01370', 155 | date: '2017-01-24T22:11:53.835Z', 156 | bs: 'extensible repurpose action-items', 157 | catchPhrase: 'Virtual dedicated definition', 158 | companyName: 'Maggio LLC', 159 | words: 'libero unde est', 160 | sentence: 'Non adipisci hic laboriosam et qui laudantium aspernatur.', 161 | stars: 330, 162 | followers: 590 163 | }, 164 | { 165 | id: 10, 166 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/mwarkentin/128.jpg', 167 | city: 'Felicitychester', 168 | email: 'Eileen48@gmail.com', 169 | firstName: 'Eldridge', 170 | lastName: 'Bins', 171 | street: 'Casper Squares', 172 | zipCode: '80025-1552', 173 | date: '2016-07-20T05:59:45.630Z', 174 | bs: 'cutting-edge expedite partnerships', 175 | catchPhrase: 'Organic user-facing functionalities', 176 | companyName: 'Leffler, Cummerata and Price', 177 | words: 'sed exercitationem quas', 178 | sentence: 'Voluptas dolorem quasi aspernatur.', 179 | stars: 923, 180 | followers: 704 181 | }, 182 | { 183 | id: 11, 184 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/gipsy_raf/128.jpg', 185 | city: 'Caleighhaven', 186 | email: 'Rico_Nolan@hotmail.com', 187 | firstName: 'Claude', 188 | lastName: 'Hermiston', 189 | street: 'Bode Pine', 190 | zipCode: '76773', 191 | date: '2017-03-13T08:02:41.211Z', 192 | bs: 'back-end innovate infomediaries', 193 | catchPhrase: 'Stand-alone global customer loyalty', 194 | companyName: 'Heller, Rosenbaum and Lockman', 195 | words: 'ut quia ut', 196 | sentence: 'Eos consequatur magni incidunt.', 197 | stars: 421, 198 | followers: 403 199 | }, 200 | { 201 | id: 12, 202 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/knilob/128.jpg', 203 | city: 'Herzogmouth', 204 | email: 'Dawn_Metz@yahoo.com', 205 | firstName: 'Clarabelle', 206 | lastName: 'Ankunding', 207 | street: 'Nolan Summit', 208 | zipCode: '04355', 209 | date: '2016-07-09T09:07:34.744Z', 210 | bs: 'granular deliver relationships', 211 | catchPhrase: 'Multi-lateral zero defect analyzer', 212 | companyName: 'Mante, Oberbrunner and Collins', 213 | words: 'eos fuga repellat', 214 | sentence: 'Cum corporis molestias quia.', 215 | stars: 8203, 216 | followers: 704 217 | }, 218 | { 219 | id: 13, 220 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/kirangopal/128.jpg', 221 | city: 'Eulaliabury', 222 | email: 'Ron.Franecki@gmail.com', 223 | firstName: 'Hubert', 224 | lastName: 'Boehm', 225 | street: 'Anastacio Springs', 226 | zipCode: '91444', 227 | date: '2016-04-22T16:37:24.331Z', 228 | bs: 'one-to-one transition methodologies', 229 | catchPhrase: 'Switchable asymmetric function', 230 | companyName: 'Greenholt, Homenick and Considine', 231 | words: 'sed incidunt quo', 232 | sentence: 'Sed adipisci aliquam ut eius ut ipsa consequatur.', 233 | stars: 8209, 234 | followers: 909 235 | }, 236 | { 237 | id: 14, 238 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/kerem/128.jpg', 239 | city: 'East Alice', 240 | email: 'Hayley52@yahoo.com', 241 | firstName: 'Vladimir', 242 | lastName: 'Breitenberg', 243 | street: 'Lula Port', 244 | zipCode: '04635', 245 | date: '2016-09-26T01:25:23.057Z', 246 | bs: 'virtual monetize communities', 247 | catchPhrase: 'Mandatory user-facing methodology', 248 | companyName: 'Kshlerin - Pfeffer', 249 | words: 'eaque enim unde', 250 | sentence: 'Sed voluptas fugiat nihil delectus architecto et voluptatibus quis voluptas.', 251 | stars: 8251, 252 | followers: 178 253 | }, 254 | { 255 | id: 15, 256 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/layerssss/128.jpg', 257 | city: 'East Frankie', 258 | email: 'Duane.Rempel@hotmail.com', 259 | firstName: 'Haylee', 260 | lastName: 'Purdy', 261 | street: 'Dena Walk', 262 | zipCode: '94111-0802', 263 | date: '2016-11-26T16:36:38.472Z', 264 | bs: 'enterprise drive users', 265 | catchPhrase: 'Customizable non-volatile paradigm', 266 | companyName: 'Lemke, Mitchell and Harber', 267 | words: 'dolores ipsum earum', 268 | sentence: 'Nemo molestiae ad sit cupiditate neque.', 269 | stars: 3099, 270 | followers: 707 271 | }, 272 | { 273 | id: 16, 274 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/dreizle/128.jpg', 275 | city: 'New Kendall', 276 | email: 'Eddie_Bartell@hotmail.com', 277 | firstName: 'Herminia', 278 | lastName: 'Altenwerth', 279 | street: 'Kshlerin Cape', 280 | zipCode: '86614-9727', 281 | date: '2016-09-28T19:50:18.308Z', 282 | bs: 'cutting-edge target models', 283 | catchPhrase: 'Triple-buffered fault-tolerant concept', 284 | companyName: 'Gislason - Nicolas', 285 | words: 'perferendis magnam minima', 286 | sentence: 'Fuga in dolorem vel eligendi deserunt voluptatem.', 287 | stars: 8491, 288 | followers: 463 289 | }, 290 | { 291 | id: 17, 292 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/nessoila/128.jpg', 293 | city: 'Port Whitney', 294 | email: 'Josephine_Legros@yahoo.com', 295 | firstName: 'Erick', 296 | lastName: 'Klein', 297 | street: 'Megane Cliffs', 298 | zipCode: '42168', 299 | date: '2016-04-02T05:03:42.377Z', 300 | bs: 'user-centric leverage experiences', 301 | catchPhrase: 'Centralized systematic parallelism', 302 | companyName: 'Olson and Sons', 303 | words: 'facere est in', 304 | sentence: 'Ducimus aliquid ut.', 305 | stars: 9820, 306 | followers: 670 307 | }, 308 | { 309 | id: 18, 310 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/mrebay007/128.jpg', 311 | city: 'West Meda', 312 | email: 'Jared.Hudson@hotmail.com', 313 | firstName: 'Lisandro', 314 | lastName: 'Barton', 315 | street: 'Torrance Union', 316 | zipCode: '19477', 317 | date: '2016-08-01T14:24:45.209Z', 318 | bs: 'open-source exploit markets', 319 | catchPhrase: 'Open-source impactful framework', 320 | companyName: 'Volkman and Sons', 321 | words: 'a tempore hic', 322 | sentence: 'Quod veniam nemo impedit mollitia.', 323 | stars: 1220, 324 | followers: 708 325 | }, 326 | { 327 | id: 19, 328 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/brenton_clarke/128.jpg', 329 | city: 'Darrenport', 330 | email: 'Delpha.Tromp9@yahoo.com', 331 | firstName: 'Ashton', 332 | lastName: 'Daugherty', 333 | street: 'Hermann Port', 334 | zipCode: '25133-9181', 335 | date: '2016-07-29T09:49:39.424Z', 336 | bs: 'wireless optimize deliverables', 337 | catchPhrase: 'Ergonomic human-resource algorithm', 338 | companyName: 'Grady LLC', 339 | words: 'libero ut repellat', 340 | sentence: 'Vel quod ullam.', 341 | stars: 420, 342 | followers: 30 343 | }, 344 | { 345 | id: 20, 346 | avartar: 'https://s3.amazonaws.com/uifaces/faces/twitter/josep_martins/128.jpg', 347 | city: 'Janiyahaven', 348 | email: 'Ariel.Maggio9@yahoo.com', 349 | firstName: 'Cassandra', 350 | lastName: 'Schmitt', 351 | street: 'Windler Lodge', 352 | zipCode: '87582-2944', 353 | date: '2017-01-21T12:35:27.741Z', 354 | bs: 'holistic cultivate relationships', 355 | catchPhrase: 'Enterprise-wide system-worthy data-warehouse', 356 | companyName: 'Ankunding Group', 357 | words: 'blanditiis voluptates repellat', 358 | sentence: 'Non quis non dignissimos sit rerum voluptatem culpa quibusdam.', 359 | stars: 20, 360 | followers: 188 361 | } 362 | ]; 363 | -------------------------------------------------------------------------------- /src/pages/tables/virtualized/VirtualizedTable.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DOMHelper, Table } from 'rsuite'; 3 | import { mockUsers } from '@/data/mock'; 4 | 5 | const { Column, HeaderCell, Cell } = Table; 6 | const { getHeight } = DOMHelper; 7 | 8 | const data = mockUsers(1000); 9 | 10 | const VirtualizedTable = () => { 11 | return ( 12 | 18 | 19 | Id 20 | 21 | 22 | 23 | 24 | First Name 25 | 26 | 27 | 28 | 29 | Last Name 30 | 31 | 32 | 33 | 34 | Gender 35 | 36 | 37 | 38 | 39 | Age 40 | 41 | 42 | 43 | 44 | City 45 | 46 | 47 | 48 | 49 | Email 50 | 51 | 52 |
53 | ); 54 | }; 55 | 56 | export default VirtualizedTable; 57 | -------------------------------------------------------------------------------- /src/pages/tables/virtualized/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import VirtualizedTable from './VirtualizedTable'; 3 | 4 | import { Breadcrumb, Panel } from 'rsuite'; 5 | 6 | const Page = () => { 7 | return ( 8 | 11 |

Virtualized Table

12 | 13 | Home 14 | Tables 15 | Virtualized Table 16 | 17 | 18 | } 19 | > 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default Page; 26 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import '~rsuite/styles/index.less'; 2 | @import '../components/Frame/styles.less'; 3 | @import '../components/Logo/styles.less'; 4 | @import '../components/ErrorPage/styles.less'; 5 | @import '../components/Header/styles.less'; 6 | @import '../components/RadioTile/styles.less'; 7 | 8 | @import '../pages/tables/members/styles.less'; 9 | @import '../pages/dashboard/styles.less'; 10 | @import '../pages/forms/basic/styles.less'; 11 | @import '../pages/forms/wizard/styles.less'; 12 | @import '../pages/calendar/styles.less'; 13 | 14 | //rewrite base color 15 | @base-color: #34c3ff; 16 | 17 | body { 18 | background: #f5f8fa; 19 | } 20 | 21 | .text-muted { 22 | color: @B700; 23 | } 24 | 25 | .rs-sidebar { 26 | position: fixed; 27 | height: 100vh; 28 | z-index: 3; 29 | } 30 | 31 | .page-container { 32 | padding-left: 260px; 33 | transition: padding 0.5s; 34 | &.container-full { 35 | padding-left: 60px; 36 | } 37 | } 38 | 39 | .bg-gradient-orange { 40 | background: linear-gradient(87deg, #fb6340 0, #fbb140 100%); 41 | } 42 | 43 | .bg-gradient-red { 44 | background: linear-gradient(87deg, #f5365c 0, #f56036 100%); 45 | } 46 | 47 | .bg-gradient-green { 48 | background: linear-gradient(87deg, #2dce89 0, #2dcecc 100%); 49 | } 50 | 51 | .bg-gradient-blue { 52 | background: linear-gradient(87deg, #11cdef 0, #1171ef 100%); 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/formatValue.ts: -------------------------------------------------------------------------------- 1 | import toThousands from './toThousands'; 2 | 3 | export default function formatValue(value: number) { 4 | if (value === null || typeof value === 'undefined') { 5 | return '--'; 6 | } else if (value === 0) { 7 | return '0'; 8 | } else if (0 < value && value < 1000) { 9 | return '< 1k'; 10 | } else if (1000 <= value && value < 1000000) { 11 | return toThousands(value / 1000) + ' k'; 12 | } else if (1000000 <= value && value < 1000000000) { 13 | return toThousands(value / 1000000) + ' M'; 14 | } else if (1000000000 <= value && value < 1000000000000) { 15 | return toThousands(value / 1000000000) + ' B'; 16 | } else if (1000000000000 <= value) { 17 | return 'INF.'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/highlightValue.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import toThousands from './toThousands'; 3 | 4 | const Highlight = ({ value, unit }: { value: string; unit: string }) => ( 5 | 6 | {value} {unit}{' '} 7 | 8 | ); 9 | 10 | export default function highlightValue(value, fixed) { 11 | if (value === null || typeof value === 'undefined') { 12 | return '--'; 13 | } else if (value === 0) { 14 | return '0'; 15 | } else if (0 < value && value < 1000) { 16 | return '< 1k'; 17 | } else if (1000 <= value && value < 1000000) { 18 | return ; 19 | } else if (1000000 <= value && value < 1000000000) { 20 | return ; 21 | } else if (1000000000 <= value) { 22 | return ; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as toThousands } from './toThousands'; 2 | export { default as highlightValue } from './highlightValue'; 3 | export { default as formatValue } from './formatValue'; 4 | -------------------------------------------------------------------------------- /src/utils/toThousands.ts: -------------------------------------------------------------------------------- 1 | export default function toThousands(value: number, fixed = 0) { 2 | return (value.toFixed(fixed) + '').replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,'); 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "declaration": true, 5 | "allowJs": true, 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "noImplicitAny": false, 9 | "noUnusedParameters": true, 10 | "noUnusedLocals": true, 11 | "sourceMap": true, 12 | "moduleResolution": "node", 13 | "target": "esnext", 14 | "module": "esnext", 15 | "jsx": "react", 16 | "skipLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@/*": ["./src/*"] 20 | } 21 | }, 22 | "exclude": ["node_modules"], 23 | "include": ["./src/**/*.ts", "./src/**/*.tsx"] 24 | } 25 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] 3 | } 4 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | const HtmlwebpackPlugin = require('html-webpack-plugin'); 6 | 7 | module.exports = { 8 | entry: './src/index.tsx', 9 | devtool: 'source-map', 10 | resolve: { 11 | // Add '.ts' and '.tsx' as resolvable extensions. 12 | extensions: ['.ts', '.tsx', '.js', '.json'] 13 | }, 14 | devServer: { 15 | hot: true, 16 | disableHostCheck: true, 17 | historyApiFallback: true, 18 | contentBase: path.resolve(__dirname, ''), 19 | publicPath: '/' 20 | }, 21 | output: { 22 | path: path.resolve(__dirname, 'assets'), 23 | filename: 'bundle.js', 24 | publicPath: './' 25 | }, 26 | 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.tsx?$/, 31 | use: ['babel-loader'], 32 | exclude: /node_modules/ 33 | }, 34 | { 35 | test: /\.(jpg|png|svg)$/, 36 | use: [ 37 | { 38 | loader: 'url-loader', 39 | options: { 40 | limit: 8192, 41 | publicPath: '/' 42 | } 43 | } 44 | ] 45 | }, 46 | { 47 | test: /\.(less|css)$/, 48 | use: [ 49 | MiniCssExtractPlugin.loader, 50 | { 51 | loader: 'css-loader' 52 | }, 53 | { 54 | loader: 'less-loader', 55 | options: { 56 | sourceMap: true, 57 | lessOptions: { 58 | javascriptEnabled: true 59 | } 60 | } 61 | } 62 | ] 63 | } 64 | ] 65 | }, 66 | plugins: [ 67 | new HtmlwebpackPlugin({ 68 | title: 'Admin Dashboard Template', 69 | filename: 'index.html', 70 | template: './src/index.html', 71 | inject: true, 72 | hash: true, 73 | path: './' 74 | }), 75 | new MiniCssExtractPlugin({ 76 | filename: '[name].css', 77 | chunkFilename: '[id].css' 78 | }) 79 | ] 80 | }; 81 | --------------------------------------------------------------------------------