first commit
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
50
README.md
Normal file
50
README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||||
|
|
||||||
|
- Configure the top-level `parserOptions` property like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default tseslint.config({
|
||||||
|
languageOptions: {
|
||||||
|
// other options...
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||||
|
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||||
|
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// eslint.config.js
|
||||||
|
import react from 'eslint-plugin-react'
|
||||||
|
|
||||||
|
export default tseslint.config({
|
||||||
|
// Set the react version
|
||||||
|
settings: { react: { version: '18.3' } },
|
||||||
|
plugins: {
|
||||||
|
// Add the react plugin
|
||||||
|
react,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// other rules...
|
||||||
|
// Enable its recommended rules
|
||||||
|
...react.configs.recommended.rules,
|
||||||
|
...react.configs['jsx-runtime'].rules,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
28
eslint.config.js
Normal file
28
eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5184
package-lock.json
generated
Normal file
5184
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
package.json
Normal file
40
package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "rtk-dashboard",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/icons": "^2.2.4",
|
||||||
|
"@chakra-ui/react": "^3.3.1",
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
|
"framer-motion": "^11.17.0",
|
||||||
|
"json-server": "^1.0.0-beta.3",
|
||||||
|
"next-themes": "^0.4.4",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
|
"react-router-dom": "^7.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"@types/react": "^18.3.18",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.16",
|
||||||
|
"globals": "^15.14.0",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"typescript-eslint": "^8.18.2",
|
||||||
|
"vite": "^6.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/images/logo/logo.png
Normal file
BIN
public/images/logo/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
0
src/App.css
Normal file
0
src/App.css
Normal file
24
src/App.tsx
Normal file
24
src/App.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
|
import Sidebar from "./userComponents/Sidebar";
|
||||||
|
import Navbar from "./userComponents/Navbar";
|
||||||
|
import Dashboard from "./pages/Dashboard";
|
||||||
|
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<Sidebar />
|
||||||
|
<div style={{ flexGrow: 1 }}>
|
||||||
|
<Navbar />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Dashboard />} />
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
1
src/assets/react.svg
Normal file
1
src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
47
src/components/ui/accordion.tsx
Normal file
47
src/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Accordion, HStack } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
import { LuChevronDown } from "react-icons/lu"
|
||||||
|
|
||||||
|
interface AccordionItemTriggerProps extends Accordion.ItemTriggerProps {
|
||||||
|
indicatorPlacement?: "start" | "end"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AccordionItemTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
AccordionItemTriggerProps
|
||||||
|
>(function AccordionItemTrigger(props, ref) {
|
||||||
|
const { children, indicatorPlacement = "end", ...rest } = props
|
||||||
|
return (
|
||||||
|
<Accordion.ItemTrigger {...rest} ref={ref}>
|
||||||
|
{indicatorPlacement === "start" && (
|
||||||
|
<Accordion.ItemIndicator rotate={{ base: "-90deg", _open: "0deg" }}>
|
||||||
|
<LuChevronDown />
|
||||||
|
</Accordion.ItemIndicator>
|
||||||
|
)}
|
||||||
|
<HStack gap="4" flex="1" textAlign="start" width="full">
|
||||||
|
{children}
|
||||||
|
</HStack>
|
||||||
|
{indicatorPlacement === "end" && (
|
||||||
|
<Accordion.ItemIndicator>
|
||||||
|
<LuChevronDown />
|
||||||
|
</Accordion.ItemIndicator>
|
||||||
|
)}
|
||||||
|
</Accordion.ItemTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
interface AccordionItemContentProps extends Accordion.ItemContentProps {}
|
||||||
|
|
||||||
|
export const AccordionItemContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
AccordionItemContentProps
|
||||||
|
>(function AccordionItemContent(props, ref) {
|
||||||
|
return (
|
||||||
|
<Accordion.ItemContent>
|
||||||
|
<Accordion.ItemBody {...props} ref={ref} />
|
||||||
|
</Accordion.ItemContent>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const AccordionRoot = Accordion.Root
|
||||||
|
export const AccordionItem = Accordion.Item
|
||||||
74
src/components/ui/avatar.tsx
Normal file
74
src/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type { GroupProps, SlotRecipeProps } from "@chakra-ui/react"
|
||||||
|
import { Avatar as ChakraAvatar, Group } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>
|
||||||
|
|
||||||
|
export interface AvatarProps extends ChakraAvatar.RootProps {
|
||||||
|
name?: string
|
||||||
|
src?: string
|
||||||
|
srcSet?: string
|
||||||
|
loading?: ImageProps["loading"]
|
||||||
|
icon?: React.ReactElement
|
||||||
|
fallback?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
|
||||||
|
function Avatar(props, ref) {
|
||||||
|
const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
|
||||||
|
props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Root ref={ref} {...rest}>
|
||||||
|
<AvatarFallback name={name} icon={icon}>
|
||||||
|
{fallback}
|
||||||
|
</AvatarFallback>
|
||||||
|
<ChakraAvatar.Image src={src} srcSet={srcSet} loading={loading} />
|
||||||
|
{children}
|
||||||
|
</ChakraAvatar.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
|
||||||
|
name?: string
|
||||||
|
icon?: React.ReactElement
|
||||||
|
}
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
|
||||||
|
function AvatarFallback(props, ref) {
|
||||||
|
const { name, icon, children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Fallback ref={ref} {...rest}>
|
||||||
|
{children}
|
||||||
|
{name != null && children == null && <>{getInitials(name)}</>}
|
||||||
|
{name == null && children == null && (
|
||||||
|
<ChakraAvatar.Icon asChild={!!icon}>{icon}</ChakraAvatar.Icon>
|
||||||
|
)}
|
||||||
|
</ChakraAvatar.Fallback>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function getInitials(name: string) {
|
||||||
|
const names = name.trim().split(" ")
|
||||||
|
const firstName = names[0] != null ? names[0] : ""
|
||||||
|
const lastName = names.length > 1 ? names[names.length - 1] : ""
|
||||||
|
return firstName && lastName
|
||||||
|
? `${firstName.charAt(0)}${lastName.charAt(0)}`
|
||||||
|
: firstName.charAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AvatarGroupProps extends GroupProps, SlotRecipeProps<"avatar"> {}
|
||||||
|
|
||||||
|
export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
|
||||||
|
function AvatarGroup(props, ref) {
|
||||||
|
const { size, variant, borderless, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
|
||||||
|
<Group gap="0" spaceX="-3" ref={ref} {...rest} />
|
||||||
|
</ChakraAvatar.PropsProvider>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
40
src/components/ui/button.tsx
Normal file
40
src/components/ui/button.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react"
|
||||||
|
import {
|
||||||
|
AbsoluteCenter,
|
||||||
|
Button as ChakraButton,
|
||||||
|
Span,
|
||||||
|
Spinner,
|
||||||
|
} from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface ButtonLoadingProps {
|
||||||
|
loading?: boolean
|
||||||
|
loadingText?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
|
||||||
|
|
||||||
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
function Button(props, ref) {
|
||||||
|
const { loading, disabled, loadingText, children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraButton disabled={loading || disabled} ref={ref} {...rest}>
|
||||||
|
{loading && !loadingText ? (
|
||||||
|
<>
|
||||||
|
<AbsoluteCenter display="inline-flex">
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
</AbsoluteCenter>
|
||||||
|
<Span opacity={0}>{children}</Span>
|
||||||
|
</>
|
||||||
|
) : loading && loadingText ? (
|
||||||
|
<>
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
{loadingText}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</ChakraButton>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
25
src/components/ui/checkbox.tsx
Normal file
25
src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface CheckboxProps extends ChakraCheckbox.RootProps {
|
||||||
|
icon?: React.ReactNode
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
rootRef?: React.Ref<HTMLLabelElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
|
function Checkbox(props, ref) {
|
||||||
|
const { icon, children, inputProps, rootRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraCheckbox.Root ref={rootRef} {...rest}>
|
||||||
|
<ChakraCheckbox.HiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraCheckbox.Control>
|
||||||
|
{icon || <ChakraCheckbox.Indicator />}
|
||||||
|
</ChakraCheckbox.Control>
|
||||||
|
{children != null && (
|
||||||
|
<ChakraCheckbox.Label>{children}</ChakraCheckbox.Label>
|
||||||
|
)}
|
||||||
|
</ChakraCheckbox.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
17
src/components/ui/close-button.tsx
Normal file
17
src/components/ui/close-button.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { ButtonProps } from "@chakra-ui/react"
|
||||||
|
import { IconButton as ChakraIconButton } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
import { LuX } from "react-icons/lu"
|
||||||
|
|
||||||
|
export type CloseButtonProps = ButtonProps
|
||||||
|
|
||||||
|
export const CloseButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
CloseButtonProps
|
||||||
|
>(function CloseButton(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraIconButton variant="ghost" aria-label="Close" ref={ref} {...props}>
|
||||||
|
{props.children ?? <LuX />}
|
||||||
|
</ChakraIconButton>
|
||||||
|
)
|
||||||
|
})
|
||||||
67
src/components/ui/color-mode.tsx
Normal file
67
src/components/ui/color-mode.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type { IconButtonProps } from "@chakra-ui/react"
|
||||||
|
import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react"
|
||||||
|
import { ThemeProvider, useTheme } from "next-themes"
|
||||||
|
import type { ThemeProviderProps } from "next-themes"
|
||||||
|
import * as React from "react"
|
||||||
|
import { LuMoon, LuSun } from "react-icons/lu"
|
||||||
|
|
||||||
|
export interface ColorModeProviderProps extends ThemeProviderProps {}
|
||||||
|
|
||||||
|
export function ColorModeProvider(props: ColorModeProviderProps) {
|
||||||
|
return (
|
||||||
|
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColorMode() {
|
||||||
|
const { resolvedTheme, setTheme } = useTheme()
|
||||||
|
const toggleColorMode = () => {
|
||||||
|
setTheme(resolvedTheme === "light" ? "dark" : "light")
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
colorMode: resolvedTheme,
|
||||||
|
setColorMode: setTheme,
|
||||||
|
toggleColorMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColorModeValue<T>(light: T, dark: T) {
|
||||||
|
const { colorMode } = useColorMode()
|
||||||
|
return colorMode === "light" ? light : dark
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorModeIcon() {
|
||||||
|
const { colorMode } = useColorMode()
|
||||||
|
return colorMode === "light" ? <LuSun /> : <LuMoon />
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
|
||||||
|
|
||||||
|
export const ColorModeButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ColorModeButtonProps
|
||||||
|
>(function ColorModeButton(props, ref) {
|
||||||
|
const { toggleColorMode } = useColorMode()
|
||||||
|
return (
|
||||||
|
<ClientOnly fallback={<Skeleton boxSize="8" />}>
|
||||||
|
<IconButton
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
variant="ghost"
|
||||||
|
aria-label="Toggle color mode"
|
||||||
|
size="sm"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
css={{
|
||||||
|
_icon: {
|
||||||
|
width: "5",
|
||||||
|
height: "5",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ColorModeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</ClientOnly>
|
||||||
|
)
|
||||||
|
})
|
||||||
62
src/components/ui/dialog.tsx
Normal file
62
src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react"
|
||||||
|
import { CloseButton } from "./close-button"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface DialogContentProps extends ChakraDialog.ContentProps {
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
backdrop?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DialogContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DialogContentProps
|
||||||
|
>(function DialogContent(props, ref) {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
portalled = true,
|
||||||
|
portalRef,
|
||||||
|
backdrop = true,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
{backdrop && <ChakraDialog.Backdrop />}
|
||||||
|
<ChakraDialog.Positioner>
|
||||||
|
<ChakraDialog.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDialog.Content>
|
||||||
|
</ChakraDialog.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DialogCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDialog.CloseTriggerProps
|
||||||
|
>(function DialogCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDialog.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref}>
|
||||||
|
{props.children}
|
||||||
|
</CloseButton>
|
||||||
|
</ChakraDialog.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DialogRoot = ChakraDialog.Root
|
||||||
|
export const DialogFooter = ChakraDialog.Footer
|
||||||
|
export const DialogHeader = ChakraDialog.Header
|
||||||
|
export const DialogBody = ChakraDialog.Body
|
||||||
|
export const DialogBackdrop = ChakraDialog.Backdrop
|
||||||
|
export const DialogTitle = ChakraDialog.Title
|
||||||
|
export const DialogDescription = ChakraDialog.Description
|
||||||
|
export const DialogTrigger = ChakraDialog.Trigger
|
||||||
|
export const DialogActionTrigger = ChakraDialog.ActionTrigger
|
||||||
52
src/components/ui/drawer.tsx
Normal file
52
src/components/ui/drawer.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"
|
||||||
|
import { CloseButton } from "./close-button"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface DrawerContentProps extends ChakraDrawer.ContentProps {
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
offset?: ChakraDrawer.ContentProps["padding"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DrawerContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DrawerContentProps
|
||||||
|
>(function DrawerContent(props, ref) {
|
||||||
|
const { children, portalled = true, portalRef, offset, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraDrawer.Positioner padding={offset}>
|
||||||
|
<ChakraDrawer.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDrawer.Content>
|
||||||
|
</ChakraDrawer.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DrawerCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDrawer.CloseTriggerProps
|
||||||
|
>(function DrawerCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDrawer.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref} />
|
||||||
|
</ChakraDrawer.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DrawerTrigger = ChakraDrawer.Trigger
|
||||||
|
export const DrawerRoot = ChakraDrawer.Root
|
||||||
|
export const DrawerFooter = ChakraDrawer.Footer
|
||||||
|
export const DrawerHeader = ChakraDrawer.Header
|
||||||
|
export const DrawerBody = ChakraDrawer.Body
|
||||||
|
export const DrawerBackdrop = ChakraDrawer.Backdrop
|
||||||
|
export const DrawerDescription = ChakraDrawer.Description
|
||||||
|
export const DrawerTitle = ChakraDrawer.Title
|
||||||
|
export const DrawerActionTrigger = ChakraDrawer.ActionTrigger
|
||||||
33
src/components/ui/field.tsx
Normal file
33
src/components/ui/field.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Field as ChakraField } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface FieldProps extends Omit<ChakraField.RootProps, "label"> {
|
||||||
|
label?: React.ReactNode
|
||||||
|
helperText?: React.ReactNode
|
||||||
|
errorText?: React.ReactNode
|
||||||
|
optionalText?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||||
|
function Field(props, ref) {
|
||||||
|
const { label, children, helperText, errorText, optionalText, ...rest } =
|
||||||
|
props
|
||||||
|
return (
|
||||||
|
<ChakraField.Root ref={ref} {...rest}>
|
||||||
|
{label && (
|
||||||
|
<ChakraField.Label>
|
||||||
|
{label}
|
||||||
|
<ChakraField.RequiredIndicator fallback={optionalText} />
|
||||||
|
</ChakraField.Label>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
{helperText && (
|
||||||
|
<ChakraField.HelperText>{helperText}</ChakraField.HelperText>
|
||||||
|
)}
|
||||||
|
{errorText && (
|
||||||
|
<ChakraField.ErrorText>{errorText}</ChakraField.ErrorText>
|
||||||
|
)}
|
||||||
|
</ChakraField.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
53
src/components/ui/input-group.tsx
Normal file
53
src/components/ui/input-group.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { BoxProps, InputElementProps } from "@chakra-ui/react"
|
||||||
|
import { Group, InputElement } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface InputGroupProps extends BoxProps {
|
||||||
|
startElementProps?: InputElementProps
|
||||||
|
endElementProps?: InputElementProps
|
||||||
|
startElement?: React.ReactNode
|
||||||
|
endElement?: React.ReactNode
|
||||||
|
children: React.ReactElement<InputElementProps>
|
||||||
|
startOffset?: InputElementProps["paddingStart"]
|
||||||
|
endOffset?: InputElementProps["paddingEnd"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||||
|
function InputGroup(props, ref) {
|
||||||
|
const {
|
||||||
|
startElement,
|
||||||
|
startElementProps,
|
||||||
|
endElement,
|
||||||
|
endElementProps,
|
||||||
|
children,
|
||||||
|
startOffset = "6px",
|
||||||
|
endOffset = "6px",
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const child =
|
||||||
|
React.Children.only<React.ReactElement<InputElementProps>>(children)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group ref={ref} {...rest}>
|
||||||
|
{startElement && (
|
||||||
|
<InputElement pointerEvents="none" {...startElementProps}>
|
||||||
|
{startElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
{React.cloneElement(child, {
|
||||||
|
...(startElement && {
|
||||||
|
ps: `calc(var(--input-height) - ${startOffset})`,
|
||||||
|
}),
|
||||||
|
...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
|
||||||
|
...children.props,
|
||||||
|
})}
|
||||||
|
{endElement && (
|
||||||
|
<InputElement placement="end" {...endElementProps}>
|
||||||
|
{endElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
59
src/components/ui/popover.tsx
Normal file
59
src/components/ui/popover.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Popover as ChakraPopover, Portal } from "@chakra-ui/react"
|
||||||
|
import { CloseButton } from "./close-button"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface PopoverContentProps extends ChakraPopover.ContentProps {
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PopoverContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
PopoverContentProps
|
||||||
|
>(function PopoverContent(props, ref) {
|
||||||
|
const { portalled = true, portalRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraPopover.Positioner>
|
||||||
|
<ChakraPopover.Content ref={ref} {...rest} />
|
||||||
|
</ChakraPopover.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverArrow = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraPopover.ArrowProps
|
||||||
|
>(function PopoverArrow(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.Arrow {...props} ref={ref}>
|
||||||
|
<ChakraPopover.ArrowTip />
|
||||||
|
</ChakraPopover.Arrow>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraPopover.CloseTriggerProps
|
||||||
|
>(function PopoverCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="1"
|
||||||
|
insetEnd="1"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" />
|
||||||
|
</ChakraPopover.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverTitle = ChakraPopover.Title
|
||||||
|
export const PopoverDescription = ChakraPopover.Description
|
||||||
|
export const PopoverFooter = ChakraPopover.Footer
|
||||||
|
export const PopoverHeader = ChakraPopover.Header
|
||||||
|
export const PopoverRoot = ChakraPopover.Root
|
||||||
|
export const PopoverBody = ChakraPopover.Body
|
||||||
|
export const PopoverTrigger = ChakraPopover.Trigger
|
||||||
15
src/components/ui/provider.tsx
Normal file
15
src/components/ui/provider.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
|
||||||
|
import {
|
||||||
|
ColorModeProvider,
|
||||||
|
type ColorModeProviderProps,
|
||||||
|
} from "./color-mode"
|
||||||
|
|
||||||
|
export function Provider(props: ColorModeProviderProps) {
|
||||||
|
return (
|
||||||
|
<ChakraProvider value={defaultSystem}>
|
||||||
|
<ColorModeProvider {...props} />
|
||||||
|
</ChakraProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
24
src/components/ui/radio.tsx
Normal file
24
src/components/ui/radio.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface RadioProps extends ChakraRadioGroup.ItemProps {
|
||||||
|
rootRef?: React.Ref<HTMLDivElement>
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
||||||
|
function Radio(props, ref) {
|
||||||
|
const { children, inputProps, rootRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraRadioGroup.Item ref={rootRef} {...rest}>
|
||||||
|
<ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraRadioGroup.ItemIndicator />
|
||||||
|
{children && (
|
||||||
|
<ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
|
||||||
|
)}
|
||||||
|
</ChakraRadioGroup.Item>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const RadioGroup = ChakraRadioGroup.Root
|
||||||
82
src/components/ui/slider.tsx
Normal file
82
src/components/ui/slider.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { Slider as ChakraSlider, For, HStack } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface SliderProps extends ChakraSlider.RootProps {
|
||||||
|
marks?: Array<number | { value: number; label: React.ReactNode }>
|
||||||
|
label?: React.ReactNode
|
||||||
|
showValue?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
||||||
|
function Slider(props, ref) {
|
||||||
|
const { marks: marksProp, label, showValue, ...rest } = props
|
||||||
|
const value = props.defaultValue ?? props.value
|
||||||
|
|
||||||
|
const marks = marksProp?.map((mark) => {
|
||||||
|
if (typeof mark === "number") return { value: mark, label: undefined }
|
||||||
|
return mark
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasMarkLabel = !!marks?.some((mark) => mark.label)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraSlider.Root ref={ref} thumbAlignment="center" {...rest}>
|
||||||
|
{label && !showValue && (
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
)}
|
||||||
|
{label && showValue && (
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
<ChakraSlider.ValueText />
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
<ChakraSlider.Control data-has-mark-label={hasMarkLabel || undefined}>
|
||||||
|
<ChakraSlider.Track>
|
||||||
|
<ChakraSlider.Range />
|
||||||
|
</ChakraSlider.Track>
|
||||||
|
<SliderThumbs value={value} />
|
||||||
|
<SliderMarks marks={marks} />
|
||||||
|
</ChakraSlider.Control>
|
||||||
|
</ChakraSlider.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function SliderThumbs(props: { value?: number[] }) {
|
||||||
|
const { value } = props
|
||||||
|
return (
|
||||||
|
<For each={value}>
|
||||||
|
{(_, index) => (
|
||||||
|
<ChakraSlider.Thumb key={index} index={index}>
|
||||||
|
<ChakraSlider.HiddenInput />
|
||||||
|
</ChakraSlider.Thumb>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SliderMarksProps {
|
||||||
|
marks?: Array<number | { value: number; label: React.ReactNode }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
|
||||||
|
function SliderMarks(props, ref) {
|
||||||
|
const { marks } = props
|
||||||
|
if (!marks?.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraSlider.MarkerGroup ref={ref}>
|
||||||
|
{marks.map((mark, index) => {
|
||||||
|
const value = typeof mark === "number" ? mark : mark.value
|
||||||
|
const label = typeof mark === "number" ? undefined : mark.label
|
||||||
|
return (
|
||||||
|
<ChakraSlider.Marker key={index} value={value}>
|
||||||
|
<ChakraSlider.MarkerIndicator />
|
||||||
|
{label}
|
||||||
|
</ChakraSlider.Marker>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ChakraSlider.MarkerGroup>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
46
src/components/ui/tooltip.tsx
Normal file
46
src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface TooltipProps extends ChakraTooltip.RootProps {
|
||||||
|
showArrow?: boolean
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
content: React.ReactNode
|
||||||
|
contentProps?: ChakraTooltip.ContentProps
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
||||||
|
function Tooltip(props, ref) {
|
||||||
|
const {
|
||||||
|
showArrow,
|
||||||
|
children,
|
||||||
|
disabled,
|
||||||
|
portalled,
|
||||||
|
content,
|
||||||
|
contentProps,
|
||||||
|
portalRef,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
if (disabled) return children
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraTooltip.Root {...rest}>
|
||||||
|
<ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraTooltip.Positioner>
|
||||||
|
<ChakraTooltip.Content ref={ref} {...contentProps}>
|
||||||
|
{showArrow && (
|
||||||
|
<ChakraTooltip.Arrow>
|
||||||
|
<ChakraTooltip.ArrowTip />
|
||||||
|
</ChakraTooltip.Arrow>
|
||||||
|
)}
|
||||||
|
{content}
|
||||||
|
</ChakraTooltip.Content>
|
||||||
|
</ChakraTooltip.Positioner>
|
||||||
|
</Portal>
|
||||||
|
</ChakraTooltip.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
0
src/index.css
Normal file
0
src/index.css
Normal file
13
src/main.tsx
Normal file
13
src/main.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import "./index.css";
|
||||||
|
import App from "./App.tsx";
|
||||||
|
import { Provider } from "./components/ui/provider.tsx";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(
|
||||||
|
<Provider>
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
15
src/pages/Dashboard.tsx
Normal file
15
src/pages/Dashboard.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { SimpleGrid } from "@chakra-ui/react";
|
||||||
|
import DashboardCard from "../userComponents/DashboardCard";
|
||||||
|
|
||||||
|
const Dashboard = () => {
|
||||||
|
return (
|
||||||
|
<SimpleGrid bgColor={"#00000"} columns={{ base: 1, md: 3 }} gap={2} p={6} ml="200px">
|
||||||
|
<DashboardCard title="Users" value="1,234" />
|
||||||
|
<DashboardCard title="Revenue" value="$12,345" />
|
||||||
|
<DashboardCard title="Orders" value="567" />
|
||||||
|
</SimpleGrid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
24
src/userComponents/DashboardCard.tsx
Normal file
24
src/userComponents/DashboardCard.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box, Text } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
const DashboardCard = ({ title, value }) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="white"
|
||||||
|
p={6}
|
||||||
|
borderRadius="lg"
|
||||||
|
shadow="md"
|
||||||
|
textAlign="center"
|
||||||
|
_hover={{ shadow: "lg" }}
|
||||||
|
>
|
||||||
|
<Text fontSize="lg" fontWeight="bold" mb={2}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="2xl" fontWeight="bold" color="blue.500">
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardCard;
|
||||||
17
src/userComponents/Navbar.tsx
Normal file
17
src/userComponents/Navbar.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box, Flex, Text } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
return (
|
||||||
|
<Box bg="blue.600" color="white" w="100%" p={4}>
|
||||||
|
<Flex justify="space-between" align="center">
|
||||||
|
<Text fontSize="xl" fontWeight="bold">
|
||||||
|
Dashboard
|
||||||
|
</Text>
|
||||||
|
<Text>Welcome, User!</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
173
src/userComponents/Sidebar.tsx
Normal file
173
src/userComponents/Sidebar.tsx
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Box, VStack, Link, Stack, Text, Image, Flex } from "@chakra-ui/react";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
import { MdDashboard } from "react-icons/md";
|
||||||
|
import logo from "../../public/images/logo/logo.png";
|
||||||
|
import { FaChevronUp } from "react-icons/fa";
|
||||||
|
import { FaChevronDown } from "react-icons/fa";
|
||||||
|
import { IoDocumentTextOutline } from "react-icons/io5";
|
||||||
|
import { FiEdit } from "react-icons/fi";
|
||||||
|
import { FaRegUser } from "react-icons/fa";
|
||||||
|
import { IoBag } from "react-icons/io5";
|
||||||
|
import { MdGroups } from "react-icons/md";
|
||||||
|
import { BiSupport } from "react-icons/bi";
|
||||||
|
import { GiDatabase } from "react-icons/gi";
|
||||||
|
|
||||||
|
const Sidebar = () => {
|
||||||
|
const [activeSection, setActiveSection] = useState("dashboard");
|
||||||
|
|
||||||
|
const toggleSection = (section) => {
|
||||||
|
setActiveSection(activeSection === section ? null : section);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VStack
|
||||||
|
w="294px"
|
||||||
|
bg="#F6F6F6"
|
||||||
|
color="black"
|
||||||
|
h="100vh"
|
||||||
|
// overflowY="scroll"
|
||||||
|
p={4}
|
||||||
|
position="fixed"
|
||||||
|
>
|
||||||
|
<Image src={logo} height="20px" width="auto" mb={8} />
|
||||||
|
<VStack justifyContent="flex-start" align="stretch" spacing={4}>
|
||||||
|
{sidebarItems.map((item) => (
|
||||||
|
<Box key={item.value}>
|
||||||
|
<Flex
|
||||||
|
boxShadow=" 0px 4px 20px 0px #00000014"
|
||||||
|
align="center"
|
||||||
|
justify="space-between"
|
||||||
|
p={3}
|
||||||
|
bg={activeSection === item.value ? "#02A0A0" : "transparent"}
|
||||||
|
borderRadius="md"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => toggleSection(item.value)}
|
||||||
|
w="254px"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
as={RouterLink}
|
||||||
|
to={item.route}
|
||||||
|
color={activeSection === item.value ? "#FFFFFF" : "#000000"}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
width="100%"
|
||||||
|
align="center"
|
||||||
|
gap={3}
|
||||||
|
>
|
||||||
|
{/* <MdDashboard
|
||||||
|
color={activeSection === item.value ? "#FFFFFF" : "#000000"}
|
||||||
|
size="20px"
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
{item.icon}
|
||||||
|
<Text width="100%" fontSize="sm" fontWeight="medium">
|
||||||
|
{item.title}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
{item.links && (
|
||||||
|
<Box ml="auto">
|
||||||
|
{activeSection === item.value ? (
|
||||||
|
<FaChevronUp color="#FFFFFF" />
|
||||||
|
) : (
|
||||||
|
<FaChevronDown color="#000000" />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
{/* Sub-links */}
|
||||||
|
{item.links && activeSection === item.value && (
|
||||||
|
<VStack align="start" pl={8} spacing={2} mt={2}>
|
||||||
|
{item.links.map((link) => (
|
||||||
|
<Link
|
||||||
|
as={RouterLink}
|
||||||
|
to={link.path}
|
||||||
|
key={link.path}
|
||||||
|
color="gray.600"
|
||||||
|
_hover={{ color: "black" }}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sidebarItems = [
|
||||||
|
{
|
||||||
|
value: "dashboard",
|
||||||
|
title: "Dashboard",
|
||||||
|
route: "/",
|
||||||
|
icon: <MdDashboard />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "manageuser",
|
||||||
|
title: "Manage User",
|
||||||
|
links: [
|
||||||
|
{ label: "Register Users", path: "/manage-users/register-users" },
|
||||||
|
{ label: "Deactivated Accounts", path: "/manage-users/deactivated-accs" },
|
||||||
|
],
|
||||||
|
icon: <IoDocumentTextOutline />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "managePost",
|
||||||
|
title: "Manage Post",
|
||||||
|
icon: <FiEdit />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "manageSubAdmin",
|
||||||
|
title: "Manage Sub-Admin",
|
||||||
|
icon: <FaRegUser />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "manageJobs",
|
||||||
|
title: "Manage Jobs",
|
||||||
|
icon: <IoBag />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "manageGroups",
|
||||||
|
title: "Manage Groups",
|
||||||
|
icon: <MdGroups color="gray" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "manageContactUs",
|
||||||
|
title: "Manage Contact Us",
|
||||||
|
icon: <BiSupport />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "manageCms",
|
||||||
|
title: "Manage CMS",
|
||||||
|
links: [
|
||||||
|
{ label: "Profile", path: "/settings/profile" },
|
||||||
|
{ label: "Preferences", path: "/settings/preferences" },
|
||||||
|
],
|
||||||
|
icon: <IoDocumentTextOutline />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "myprofle",
|
||||||
|
title: "My Profile",
|
||||||
|
links: [
|
||||||
|
{ label: "Settings", path: "/master/settings" },
|
||||||
|
{ label: "Modules", path: "/master/modules" },
|
||||||
|
],
|
||||||
|
icon: <FaRegUser />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "masterModule",
|
||||||
|
title: "Master Module",
|
||||||
|
links: [
|
||||||
|
{ label: "Settings", path: "/master/settings" },
|
||||||
|
{ label: "Modules", path: "/master/modules" },
|
||||||
|
],
|
||||||
|
icon: <GiDatabase />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
26
tsconfig.app.json
Normal file
26
tsconfig.app.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
24
tsconfig.node.json
Normal file
24
tsconfig.node.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
7
vite.config.ts
Normal file
7
vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react-swc'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user