├── app ├── _components │ ├── FormField │ │ ├── interface.ts │ │ ├── CheckRadioGroup.tsx │ │ ├── FieldLabel.tsx │ │ ├── CheckRadio.tsx │ │ ├── FilePicker.tsx │ │ └── index.tsx │ ├── CardBox │ │ ├── Component │ │ │ ├── Empty.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Body.tsx │ │ │ └── Title.tsx │ │ ├── index.tsx │ │ ├── Client.tsx │ │ ├── Modal.tsx │ │ ├── Widget.tsx │ │ ├── User.tsx │ │ └── Transaction.tsx │ ├── Section │ │ ├── Main.tsx │ │ ├── Banner │ │ │ ├── index.tsx │ │ │ └── StarOnGitHub.tsx │ │ ├── Title.tsx │ │ ├── FullScreen.tsx │ │ └── TitleLineWithButton.tsx │ ├── Divider.tsx │ ├── JustboilLogo │ │ ├── index.tsx │ │ └── logoPath.js │ ├── StyleSelect │ │ ├── OnVisit.ts │ │ └── StylePickBox.tsx │ ├── Buttons.tsx │ ├── Icon │ │ ├── index.tsx │ │ └── Rounded.tsx │ ├── PillTag │ │ ├── Plain.tsx │ │ ├── Trend.tsx │ │ └── index.tsx │ ├── OverlayLayer.tsx │ ├── NumberDynamic.tsx │ ├── NotificationBar.tsx │ └── Button.tsx ├── _lib │ ├── config.ts │ └── colors.ts ├── _stores │ ├── StoreProvider.tsx │ ├── hooks.ts │ ├── store.ts │ ├── mainSlice.ts │ └── darkModeSlice.ts ├── dashboard │ ├── _components │ │ ├── NavBar │ │ │ ├── MenuList.tsx │ │ │ ├── Item │ │ │ │ ├── Plain.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── UserAvatar │ │ │ ├── CurrentUser.tsx │ │ │ └── index.tsx │ │ ├── AsideMenu │ │ │ ├── List.tsx │ │ │ ├── index.tsx │ │ │ ├── Layer.tsx │ │ │ └── Item.tsx │ │ ├── ChartLineSample │ │ │ ├── index.tsx │ │ │ ├── ComponentBlock.tsx │ │ │ └── config.ts │ │ ├── FooterBar.tsx │ │ └── Table │ │ │ └── SampleClients.tsx │ ├── ui │ │ ├── _components │ │ │ ├── DarkModeExample.tsx │ │ │ ├── PillsExample.tsx │ │ │ ├── NotificationsExample.tsx │ │ │ ├── ModalExamples.tsx │ │ │ └── ButtonsExample.tsx │ │ └── page.tsx │ ├── profile │ │ ├── page.tsx │ │ └── _components │ │ │ ├── ProfileForm.tsx │ │ │ └── PasswordForm.tsx │ ├── _lib │ │ ├── menuAside.ts │ │ ├── menuNavBar.ts │ │ └── sampleData.ts │ ├── tables │ │ └── page.tsx │ ├── responsive │ │ └── page.tsx │ ├── layout.tsx │ ├── page.tsx │ └── forms │ │ └── page.tsx ├── login │ ├── page.tsx │ └── _components │ │ └── LoginForm.tsx ├── error │ └── page.tsx ├── page.tsx ├── _interfaces │ └── index.ts └── layout.tsx ├── public └── favicon.png ├── postcss.config.js ├── .prettierrc.json ├── next-env.d.ts ├── css ├── _progress.css ├── _keyframes.css ├── styles │ ├── _white.css │ └── _basic.css ├── main.css ├── _table.css ├── _scrollbars.css └── _checkbox-radio-switch.css ├── .gitignore ├── eslint.config.mjs ├── next.config.ts ├── tsconfig.json ├── LICENSE ├── package.json ├── .github └── workflows │ └── build.yml └── README.md /app/_components/FormField/interface.ts: -------------------------------------------------------------------------------- 1 | export type FormFieldData = { 2 | className: string; 3 | }; 4 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justboil/admin-one-react-tailwind/HEAD/public/favicon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | plugins: { 4 | '@tailwindcss/postcss': {}, 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "printWidth": 100, 4 | "plugins": ["prettier-plugin-tailwindcss"], 5 | "tailwindStylesheet": "./css/main.css" 6 | } 7 | -------------------------------------------------------------------------------- /app/_lib/config.ts: -------------------------------------------------------------------------------- 1 | export const containerMaxW = "xl:max-w-6xl xl:mx-auto"; 2 | 3 | export const appTitle = "Free Tailwind 4 React Next Typescript dashboard template"; 4 | 5 | export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}`; 6 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /app/_components/CardBox/Component/Empty.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const CardBoxComponentEmpty = () => { 4 | return ( 5 |
6 |

Nothing's here…

7 |
8 | ); 9 | }; 10 | 11 | export default CardBoxComponentEmpty; 12 | -------------------------------------------------------------------------------- /app/_components/CardBox/Component/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | className?: string; 5 | children?: ReactNode; 6 | }; 7 | 8 | export default function CardBoxComponentFooter({ className, children }: Props) { 9 | return
{children}
; 10 | } 11 | -------------------------------------------------------------------------------- /app/_components/Section/Main.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { containerMaxW } from "../../_lib/config"; 3 | 4 | type Props = { 5 | children: ReactNode; 6 | }; 7 | 8 | export default function SectionMain({ children }: Props) { 9 | return
{children}
; 10 | } 11 | -------------------------------------------------------------------------------- /app/_stores/StoreProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ReactNode } from "react"; 4 | import { store } from "./store"; 5 | import { Provider } from "react-redux"; 6 | 7 | interface Props { 8 | readonly children: ReactNode; 9 | } 10 | 11 | export default function StoreProvider({ children }: Props) { 12 | return {children}; 13 | } 14 | -------------------------------------------------------------------------------- /app/_stores/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; 2 | import type { RootState, AppDispatch } from "./store"; 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | export const useAppDispatch: () => AppDispatch = useDispatch; 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; 7 | -------------------------------------------------------------------------------- /app/_components/CardBox/Component/Body.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | noPadding?: boolean; 5 | className?: string; 6 | children?: ReactNode; 7 | }; 8 | 9 | export default function CardBoxComponentBody({ noPadding = false, className, children }: Props) { 10 | return
{children}
; 11 | } 12 | -------------------------------------------------------------------------------- /app/_components/Divider.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { 4 | navBar?: boolean; 5 | }; 6 | 7 | export default function Divider({ navBar = false }: Props) { 8 | const classAddon = navBar 9 | ? "hidden lg:block lg:my-0.5 dark:border-slate-700" 10 | : "my-6 -mx-6 dark:border-slate-800"; 11 | 12 | return
; 13 | } 14 | -------------------------------------------------------------------------------- /app/_components/Section/Banner/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | className?: string; 5 | children: ReactNode; 6 | }; 7 | 8 | const SectionBanner = ({ className = "", children }: Props) => { 9 | return ( 10 |
{children}
11 | ); 12 | }; 13 | 14 | export default SectionBanner; 15 | -------------------------------------------------------------------------------- /css/_progress.css: -------------------------------------------------------------------------------- 1 | @layer base { 2 | progress { 3 | @apply h-3 overflow-hidden rounded-full; 4 | } 5 | 6 | progress::-webkit-progress-bar { 7 | @apply bg-blue-200; 8 | } 9 | 10 | progress::-webkit-progress-value { 11 | @apply bg-blue-500; 12 | } 13 | 14 | progress::-moz-progress-bar { 15 | @apply bg-blue-500; 16 | } 17 | 18 | progress::-ms-fill { 19 | @apply border-0 bg-blue-500; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/_components/FormField/CheckRadioGroup.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | type Props = { 4 | isColumn?: boolean; 5 | children: ReactNode; 6 | }; 7 | 8 | const FormCheckRadioGroup = (props: Props) => { 9 | return ( 10 |
11 | {props.children} 12 |
13 | ); 14 | }; 15 | 16 | export default FormCheckRadioGroup; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # next.js 10 | /.next/ 11 | /out/ 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # local env files 26 | .env.local 27 | .env.development.local 28 | .env.test.local 29 | .env.production.local 30 | 31 | # vercel 32 | .vercel -------------------------------------------------------------------------------- /app/_components/CardBox/Component/Title.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | title: string; 5 | children?: ReactNode; 6 | }; 7 | 8 | const CardBoxComponentTitle = ({ title, children }: Props) => { 9 | return ( 10 |
11 |

{title}

12 | {children} 13 |
14 | ); 15 | }; 16 | 17 | export default CardBoxComponentTitle; 18 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /app/_components/JustboilLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import justboilLogoPath from "./logoPath"; 3 | 4 | type Props = { 5 | className?: string; 6 | }; 7 | 8 | export default function JustboilLogo({ className = "" }: Props) { 9 | return ( 10 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /app/_components/StyleSelect/OnVisit.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { useAppDispatch, useAppSelector } from "../../_stores/hooks"; 5 | import { setDarkMode } from "../../_stores/darkModeSlice"; 6 | 7 | export function OnVisit() { 8 | const darkMode = useAppSelector((state) => state.darkMode.isEnabled); 9 | const dispatch = useAppDispatch(); 10 | 11 | useEffect(() => { 12 | if (darkMode) { 13 | dispatch(setDarkMode(false)); 14 | } 15 | }); 16 | 17 | return "..."; 18 | } 19 | -------------------------------------------------------------------------------- /app/dashboard/_components/NavBar/MenuList.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MenuNavBarItem } from "../../../_interfaces"; 3 | import NavBarItem from "./Item"; 4 | 5 | type Props = { 6 | menu: MenuNavBarItem[]; 7 | onRouteChange: () => void; 8 | }; 9 | 10 | export default function NavBarMenuList({ menu, ...props }: Props) { 11 | return ( 12 | <> 13 | {menu.map((item, index) => ( 14 | 15 | ))} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /app/_components/FormField/FieldLabel.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | 3 | type Props = { 4 | htmlFor?: string; 5 | children: ReactNode; 6 | className?: string; 7 | }; 8 | 9 | const FieldLabel = ({ htmlFor, children, ...props }: Props) => { 10 | return ( 11 | 17 | ); 18 | }; 19 | 20 | export default FieldLabel; 21 | -------------------------------------------------------------------------------- /css/_keyframes.css: -------------------------------------------------------------------------------- 1 | @theme { 2 | --animate-overlay-fade-in: overlay-fade-in 250ms ease-in-out; 3 | --animate-fade-in: fade-in 250ms ease-in-out; 4 | --animate-fade-out: fade-out 250ms ease-in-out; 5 | } 6 | 7 | @keyframes overlay-fade-in { 8 | from { 9 | opacity: 0; 10 | } 11 | to { 12 | opacity: 0.9; 13 | } 14 | } 15 | 16 | @keyframes fade-in { 17 | from { 18 | opacity: 0; 19 | } 20 | to { 21 | opacity: 1; 22 | } 23 | } 24 | 25 | @keyframes fade-out { 26 | from { 27 | opacity: 1; 28 | } 29 | to { 30 | opacity: 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/_stores/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import darkModeReducer from "./darkModeSlice"; 3 | import mainReducer from "./mainSlice"; 4 | 5 | export const store = configureStore({ 6 | reducer: { 7 | darkMode: darkModeReducer, 8 | main: mainReducer, 9 | }, 10 | }); 11 | 12 | // Infer the `RootState` and `AppDispatch` types from the store itself 13 | export type RootState = ReturnType; 14 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 15 | export type AppDispatch = typeof store.dispatch; 16 | -------------------------------------------------------------------------------- /app/dashboard/_components/UserAvatar/CurrentUser.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { useAppSelector } from "../../../_stores/hooks"; 3 | import UserAvatar from "."; 4 | 5 | type Props = { 6 | className?: string; 7 | children?: ReactNode; 8 | }; 9 | 10 | export default function UserAvatarCurrentUser({ className = "", children }: Props) { 11 | const userEmail = useAppSelector((state) => state.main.userEmail); 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /app/_components/Buttons.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | 3 | type Props = { 4 | type?: string; 5 | mb?: string; 6 | noWrap?: boolean; 7 | classAddon?: string; 8 | children: ReactNode; 9 | className?: string; 10 | }; 11 | 12 | const Buttons = ({ 13 | type = "justify-start", 14 | mb = "-mb-3", 15 | noWrap = false, 16 | children, 17 | className, 18 | }: Props) => { 19 | return ( 20 |
25 | {children} 26 |
27 | ); 28 | }; 29 | 30 | export default Buttons; 31 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | output: process.env.IS_OUTPUT_EXPORT ? "export" : "standalone", 5 | basePath: "/admin-one-react-tailwind", 6 | async redirects() { 7 | return [ 8 | { 9 | source: "/", 10 | destination: "/admin-one-react-tailwind", 11 | basePath: false, 12 | permanent: false, 13 | }, 14 | ]; 15 | }, 16 | images: { 17 | unoptimized: true, 18 | remotePatterns: [ 19 | { 20 | protocol: "https", 21 | hostname: "static.justboil.me", 22 | }, 23 | ], 24 | }, 25 | }; 26 | 27 | export default nextConfig; 28 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CardBox from "../_components/CardBox"; 3 | import SectionFullScreen from "../_components/Section/FullScreen"; 4 | import { getPageTitle } from "../_lib/config"; 5 | import { Metadata } from "next"; 6 | import LoginForm from "./_components/LoginForm"; 7 | 8 | export const metadata: Metadata = { 9 | title: getPageTitle("Login"), 10 | }; 11 | 12 | const LoginPage = () => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default LoginPage; 23 | -------------------------------------------------------------------------------- /app/_components/FormField/CheckRadio.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | type Props = { 4 | children: ReactNode; 5 | type: "checkbox" | "radio" | "switch"; 6 | label?: string; 7 | className?: string; 8 | isGrouped?: boolean; 9 | disabled?: boolean; 10 | }; 11 | 12 | const FormCheckRadio = (props: Props) => { 13 | return ( 14 | 21 | ); 22 | }; 23 | 24 | export default FormCheckRadio; 25 | -------------------------------------------------------------------------------- /app/dashboard/_components/NavBar/Item/Plain.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | display?: string; 5 | useMargin?: boolean; 6 | children: ReactNode; 7 | onClick?: (e: React.MouseEvent) => void; 8 | }; 9 | 10 | export default function NavBarItemPlain({ 11 | display = "flex", 12 | useMargin = false, 13 | onClick, 14 | children, 15 | }: Props) { 16 | const classBase = "items-center cursor-pointer dark:text-white dark:hover:text-slate-400"; 17 | const classAddon = `${display} navbar-item-label ${useMargin ? "my-2 mx-3" : "py-2 px-3"}`; 18 | 19 | return ( 20 |
21 | {children} 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /app/_components/Icon/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | path: string; 5 | w?: string; 6 | h?: string; 7 | size?: string | number | null; 8 | className?: string; 9 | children?: ReactNode; 10 | }; 11 | 12 | export default function Icon({ 13 | path, 14 | w = "w-6", 15 | h = "h-6", 16 | size = null, 17 | className = "", 18 | children, 19 | }: Props) { 20 | const iconSize = size ?? 16; 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | {children} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /app/_components/Section/Title.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | custom?: boolean; 5 | first?: boolean; 6 | last?: boolean; 7 | children: ReactNode; 8 | }; 9 | 10 | const SectionTitle = ({ custom = false, first = false, last = false, children }: Props) => { 11 | let classAddon = "-my-6"; 12 | 13 | if (first) { 14 | classAddon = "-mb-6"; 15 | } else if (last) { 16 | classAddon = "-mt-6"; 17 | } 18 | 19 | return ( 20 |
21 | {custom && children} 22 | {!custom &&

{children}

} 23 |
24 | ); 25 | }; 26 | 27 | export default SectionTitle; 28 | -------------------------------------------------------------------------------- /app/_components/Icon/Rounded.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ColorKey } from "../../_interfaces"; 3 | import { colorsBgLight, colorsText } from "../../_lib/colors"; 4 | import Icon from "."; 5 | 6 | type Props = { 7 | icon: string; 8 | color: ColorKey; 9 | w?: string; 10 | h?: string; 11 | bg?: boolean; 12 | className?: string; 13 | }; 14 | 15 | export default function IconRounded({ 16 | icon, 17 | color, 18 | w = "w-12", 19 | h = "h-12", 20 | bg = false, 21 | className = "", 22 | }: Props) { 23 | const classAddon = bg 24 | ? colorsBgLight[color] 25 | : `${colorsText[color]} bg-gray-50 dark:bg-slate-800`; 26 | 27 | return ( 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /app/dashboard/_components/AsideMenu/List.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MenuAsideItem } from "../../../_interfaces"; 3 | import AsideMenuItem from "./Item"; 4 | 5 | type Props = { 6 | menu: MenuAsideItem[]; 7 | isDropdownList?: boolean; 8 | className?: string; 9 | onRouteChange: () => void; 10 | }; 11 | 12 | export default function AsideMenuList({ 13 | menu, 14 | isDropdownList = false, 15 | className = "", 16 | ...props 17 | }: Props) { 18 | return ( 19 |
    20 | {menu.map((item, index) => ( 21 | 27 | ))} 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ], 26 | "strictNullChecks": true 27 | }, 28 | "include": [ 29 | "**/*.ts", 30 | "**/*.tsx", 31 | "next-env.d.ts", 32 | ".next/types/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /app/_components/Section/Banner/StarOnGitHub.tsx: -------------------------------------------------------------------------------- 1 | import { mdiGithub } from "@mdi/js"; 2 | import React from "react"; 3 | import { gradientBgPinkRed } from "../../../_lib/colors"; 4 | import Button from "../../Button"; 5 | import SectionBanner from "."; 6 | 7 | const SectionBannerStarOnGitHub = () => { 8 | return ( 9 | 10 |

11 | Like the project? Please star on GitHub ;-) 12 |

13 |
14 |
22 |
23 | ); 24 | }; 25 | 26 | export default SectionBannerStarOnGitHub; 27 | -------------------------------------------------------------------------------- /app/dashboard/ui/_components/DarkModeExample.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Button from "../../../_components/Button"; 4 | import CardBox from "../../../_components/CardBox"; 5 | import SectionMain from "../../../_components/Section/Main"; 6 | import { setDarkMode } from "../../../_stores/darkModeSlice"; 7 | import { useAppDispatch } from "../../../_stores/hooks"; 8 | 9 | export default function DarkModeExample() { 10 | const dispatch = useAppDispatch(); 11 | 12 | return ( 13 | 14 | 15 |
16 |
18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/_components/PillTag/Plain.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Icon from "../Icon"; 3 | 4 | type Props = { 5 | label?: string; 6 | icon?: string | null; 7 | className?: string; 8 | small?: boolean; 9 | }; 10 | 11 | const PillTagPlain = ({ small = false, className = "", ...props }: Props) => { 12 | return ( 13 |
18 | {props.icon && ( 19 | 26 | )} 27 | {props.label && {props.label}} 28 |
29 | ); 30 | }; 31 | 32 | export default PillTagPlain; 33 | -------------------------------------------------------------------------------- /app/dashboard/_components/ChartLineSample/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Chart, 4 | LineElement, 5 | PointElement, 6 | LineController, 7 | LinearScale, 8 | CategoryScale, 9 | Tooltip, 10 | } from "chart.js"; 11 | import { Line } from "react-chartjs-2"; 12 | 13 | Chart.register(LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip); 14 | 15 | const options = { 16 | responsive: true, 17 | maintainAspectRatio: false, 18 | scales: { 19 | y: { 20 | display: false, 21 | }, 22 | x: { 23 | display: true, 24 | }, 25 | }, 26 | plugins: { 27 | legend: { 28 | display: false, 29 | }, 30 | }, 31 | }; 32 | 33 | const ChartLineSample = ({ data }) => { 34 | return ; 35 | }; 36 | 37 | export default ChartLineSample; 38 | -------------------------------------------------------------------------------- /app/error/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Button from "../_components/Button"; 3 | import CardBox from "../_components/CardBox"; 4 | import SectionFullScreen from "../_components/Section/FullScreen"; 5 | import { getPageTitle } from "../_lib/config"; 6 | import { Metadata } from "next"; 7 | 8 | export const metadata: Metadata = { 9 | title: getPageTitle("Error"), 10 | }; 11 | 12 | export default function ErrorPage() { 13 | return ( 14 | 15 | } 18 | > 19 |
20 |

Unhandled exception

21 | 22 |

An Error Occurred

23 |
24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /app/_components/PillTag/Trend.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | mdiChevronUp, 4 | mdiChevronDown, 5 | mdiAlertCircleOutline, 6 | mdiInformationOutline, 7 | mdiCheckCircleOutline, 8 | mdiAlertOutline, 9 | } from "@mdi/js"; 10 | import { ColorKey, TrendType } from "../../_interfaces"; 11 | import PillTag from "."; 12 | 13 | type Props = { 14 | label: string; 15 | type: TrendType; 16 | color: ColorKey; 17 | small?: boolean; 18 | }; 19 | 20 | const PillTagTrend = ({ small = false, ...props }: Props) => { 21 | const trendIcon = { 22 | up: mdiChevronUp, 23 | down: mdiChevronDown, 24 | success: mdiCheckCircleOutline, 25 | danger: mdiAlertOutline, 26 | warning: mdiAlertCircleOutline, 27 | info: mdiInformationOutline, 28 | }[props.type]; 29 | 30 | return ; 31 | }; 32 | 33 | export default PillTagTrend; 34 | -------------------------------------------------------------------------------- /app/_components/Section/FullScreen.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { ReactNode } from "react"; 4 | import { BgKey } from "../../_interfaces"; 5 | import { gradientBgPurplePink, gradientBgDark, gradientBgPinkRed } from "../../_lib/colors"; 6 | import { useAppSelector } from "../../_stores/hooks"; 7 | 8 | type Props = { 9 | bg: BgKey; 10 | children: ReactNode; 11 | }; 12 | 13 | export default function SectionFullScreen({ bg, children }: Props) { 14 | const darkMode = useAppSelector((state) => state.darkMode.isEnabled); 15 | 16 | let componentClass = "flex min-h-screen items-center justify-center "; 17 | 18 | if (darkMode) { 19 | componentClass += gradientBgDark; 20 | } else if (bg === "purplePink") { 21 | componentClass += gradientBgPurplePink; 22 | } else if (bg === "pinkRed") { 23 | componentClass += gradientBgPinkRed; 24 | } 25 | 26 | return
{children}
; 27 | } 28 | -------------------------------------------------------------------------------- /css/styles/_white.css: -------------------------------------------------------------------------------- 1 | @theme { 2 | --scrollbar-track-light: var(--color-gray-100); 3 | --scrollbar-thumb-light: var(--color-gray-300); 4 | --scrollbar-light: var(--scrollbar-thumb-light) var(--scrollbar-track-light); 5 | } 6 | 7 | @utility style-white { 8 | &:not(.dark) { 9 | .aside { 10 | @apply bg-white; 11 | } 12 | .aside-scrollbar { 13 | @apply scrollbar-styled-light; 14 | } 15 | .aside-menu-item { 16 | @apply text-blue-600 hover:text-black; 17 | } 18 | .aside-menu-item-active { 19 | @apply text-black; 20 | } 21 | .aside-menu-dropdown { 22 | @apply bg-gray-100/75; 23 | } 24 | .navbar-item-label { 25 | @apply text-blue-600; 26 | } 27 | .navbar-item-label-hover { 28 | @apply hover:text-black; 29 | } 30 | .navbar-item-label-active { 31 | @apply text-black; 32 | } 33 | .overlay { 34 | @apply from-white via-gray-100 to-white; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/_stores/mainSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { UserPayloadObject } from "../_interfaces"; 3 | 4 | interface MainState { 5 | userName: string; 6 | userEmail: null | string; 7 | isFieldFocusRegistered: boolean; 8 | } 9 | 10 | const initialState: MainState = { 11 | /* User */ 12 | userName: "John Doe", 13 | userEmail: "doe.doe.doe@example.com", 14 | 15 | /* Field focus with ctrl+k (to register only once) */ 16 | isFieldFocusRegistered: false, 17 | }; 18 | 19 | export const mainSlice = createSlice({ 20 | name: "main", 21 | initialState, 22 | reducers: { 23 | setUser: (state, action: PayloadAction) => { 24 | state.userName = action.payload.name; 25 | state.userEmail = action.payload.email; 26 | }, 27 | }, 28 | }); 29 | 30 | // Action creators are generated for each case reducer function 31 | export const { setUser } = mainSlice.actions; 32 | 33 | export default mainSlice.reducer; 34 | -------------------------------------------------------------------------------- /app/dashboard/_components/UserAvatar/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | // Why disabled: 3 | // avatars.dicebear.com provides svg avatars 4 | // next/image needs dangerouslyAllowSVG option for that 5 | 6 | import React, { ReactNode } from "react"; 7 | 8 | type Props = { 9 | username: string; 10 | api?: string; 11 | className?: string; 12 | children?: ReactNode; 13 | }; 14 | 15 | export default function UserAvatar({ 16 | username, 17 | api = "avataaars", 18 | className = "", 19 | children, 20 | }: Props) { 21 | const avatarImage = `https://api.dicebear.com/7.x/${api}/svg?seed=${username.replace( 22 | /[^a-z0-9]+/gi, 23 | "-", 24 | )}`; 25 | 26 | return ( 27 |
28 | {username 33 | {children} 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /css/styles/_basic.css: -------------------------------------------------------------------------------- 1 | @theme { 2 | --scrollbar-track-basic: var(--color-gray-900); 3 | --scrollbar-thumb-basic: var(--color-gray-600); 4 | --scrollbar-basic: var(--scrollbar-thumb-basic) var(--scrollbar-track-basic); 5 | } 6 | 7 | @utility style-basic { 8 | &:not(.dark) { 9 | .aside { 10 | @apply bg-gray-800; 11 | } 12 | .aside-scrollbar { 13 | @apply scrollbar-styled-basic; 14 | } 15 | .aside-brand { 16 | @apply bg-gray-900 text-white; 17 | } 18 | .aside-menu-item { 19 | @apply text-gray-300 hover:text-white; 20 | } 21 | .aside-menu-item-active { 22 | @apply text-white; 23 | } 24 | .aside-menu-dropdown { 25 | @apply bg-gray-700/50; 26 | } 27 | .navbar-item-label { 28 | @apply text-black hover:text-blue-500; 29 | } 30 | .navbar-item-label-active { 31 | @apply text-blue-600; 32 | } 33 | .overlay { 34 | @apply from-gray-700 via-gray-900 to-gray-700; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/_components/OverlayLayer.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | type Props = { 4 | zIndex?: string; 5 | type?: string; 6 | children?: ReactNode; 7 | className?: string; 8 | onClick?: (e: React.MouseEvent) => void; 9 | }; 10 | 11 | export default function OverlayLayer({ 12 | zIndex = "z-50", 13 | type = "flex", 14 | children, 15 | className, 16 | ...props 17 | }: Props) { 18 | const handleClick = (e: React.MouseEvent) => { 19 | e.preventDefault(); 20 | 21 | if (props.onClick) { 22 | props.onClick(e); 23 | } 24 | }; 25 | 26 | return ( 27 |
30 |
34 | 35 | {children} 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /app/dashboard/_components/AsideMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MenuAsideItem } from "../../../_interfaces"; 3 | import AsideMenuLayer from "./Layer"; 4 | import OverlayLayer from "../../../_components/OverlayLayer"; 5 | 6 | type Props = { 7 | menu: MenuAsideItem[]; 8 | isAsideMobileExpanded: boolean; 9 | isAsideLgActive: boolean; 10 | onAsideLgClose: () => void; 11 | onRouteChange: () => void; 12 | }; 13 | 14 | export default function AsideMenu({ 15 | isAsideMobileExpanded = false, 16 | isAsideLgActive = false, 17 | ...props 18 | }: Props) { 19 | return ( 20 | <> 21 | 29 | {isAsideLgActive && } 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /app/_components/PillTag/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ColorKey } from "../../_interfaces"; 3 | import { colorsBgLight, colorsOutline } from "../../_lib/colors"; 4 | import PillTagPlain from "./Plain"; 5 | 6 | type Props = { 7 | label?: string; 8 | color: ColorKey; 9 | icon?: string | null; 10 | small?: boolean; 11 | outline?: boolean; 12 | className?: string; 13 | isGrouped?: boolean; 14 | }; 15 | 16 | const PillTag = ({ small = false, outline = false, className = "", ...props }: Props) => { 17 | const layoutClassName = small ? "py-1 px-3" : "py-1.5 px-4"; 18 | const colorClassName = outline ? colorsOutline[props.color] : colorsBgLight[props.color]; 19 | const groupedClassName = props.isGrouped ? "mr-3 last:mr-0 mb-3" : ""; 20 | 21 | return ( 22 | 28 | ); 29 | }; 30 | 31 | export default PillTag; 32 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @plugin '@tailwindcss/forms'; 3 | 4 | @source inline('style-{basic,white}'); 5 | 6 | @custom-variant dark (&:where(.dark, .dark *)); 7 | 8 | @theme { 9 | --transition-position: right, left, top, bottom, margin, padding; 10 | } 11 | 12 | @import './_checkbox-radio-switch.css'; 13 | @import './_keyframes.css'; 14 | @import './_progress.css'; 15 | @import './_scrollbars.css'; 16 | @import './_table.css'; 17 | 18 | @import './styles/_basic.css'; 19 | @import './styles/_white.css'; 20 | 21 | /* 22 | The default border color has changed to `currentColor` in Tailwind CSS v4, 23 | so we've added these compatibility styles to make sure everything still 24 | looks the same as it did with Tailwind CSS v3. 25 | 26 | If we ever want to remove these styles, we need to add an explicit border 27 | color utility to any element that depends on these defaults. 28 | */ 29 | @layer base { 30 | *, 31 | ::after, 32 | ::before, 33 | ::backdrop, 34 | ::file-selector-button { 35 | border-color: var(--color-gray-200, currentColor); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/dashboard/_components/ChartLineSample/ComponentBlock.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { mdiChartPie, mdiReload } from "@mdi/js"; 4 | import { useState } from "react"; 5 | import ChartLineSample from "."; 6 | import Button from "../../../_components/Button"; 7 | import CardBox from "../../../_components/CardBox"; 8 | import SectionTitleLineWithButton from "../../../_components/Section/TitleLineWithButton"; 9 | import { sampleChartData } from "./config"; 10 | 11 | export default function ChartLineSampleComponentBlock() { 12 | const [chartData, setChartData] = useState(sampleChartData()); 13 | 14 | const fillChartData = (e: React.MouseEvent) => { 15 | e.preventDefault(); 16 | 17 | setChartData(sampleChartData()); 18 | }; 19 | 20 | return ( 21 | <> 22 | 23 | 47 | 48 |
51 | 52 |
53 |
    54 | 55 |
56 | 57 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /css/_checkbox-radio-switch.css: -------------------------------------------------------------------------------- 1 | @theme { 2 | --checkbox-checked: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E"); 3 | --radio-checked: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E"); 4 | --switch-checked: translate3d(110%, 0, 0); 5 | } 6 | 7 | @utility jb-custom-check { 8 | & { 9 | @apply inline-flex items-center cursor-pointer relative; 10 | } 11 | 12 | &.disabled { 13 | @apply cursor-not-allowed opacity-50; 14 | } 15 | 16 | & input { 17 | @apply absolute left-0 opacity-0 -z-1; 18 | } 19 | 20 | & input + .jb-check { 21 | @apply border-gray-700 border transition-colors duration-200 dark:bg-slate-800; 22 | } 23 | 24 | & input:focus + .jb-check { 25 | @apply ring-3 ring-blue-700; 26 | } 27 | 28 | &.checkbox input + .jb-check, 29 | &.radio input + .jb-check { 30 | @apply block w-5 h-5; 31 | } 32 | 33 | &.checkbox input + .jb-check { 34 | @apply rounded-sm; 35 | } 36 | 37 | &.switch input + .jb-check { 38 | @apply flex items-center shrink-0 w-12 h-6 p-0.5 bg-gray-200; 39 | } 40 | 41 | &.radio input + .jb-check, 42 | &.switch input + .jb-check, 43 | &.switch input + .jb-check:before { 44 | @apply rounded-full; 45 | } 46 | 47 | &.checkbox input:checked + .jb-check, 48 | &.radio input:checked + .jb-check { 49 | @apply bg-no-repeat bg-center border-4; 50 | } 51 | 52 | &.checkbox input:checked + .jb-check { 53 | background-image: var(--checkbox-checked); 54 | } 55 | 56 | &.radio input:checked + .jb-check { 57 | background-image: var(--radio-checked); 58 | } 59 | 60 | &.switch input:checked + .jb-check, 61 | &.checkbox input:checked + .jb-check, 62 | &.radio input:checked + .jb-check { 63 | @apply bg-blue-600 border-blue-600; 64 | } 65 | 66 | &.switch input + .jb-check:before { 67 | content: ''; 68 | @apply block w-5 h-5 bg-white border border-gray-700; 69 | } 70 | 71 | &.switch input:checked + .jb-check:before { 72 | transform: var(--switch-checked); 73 | @apply border-blue-600; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/dashboard/responsive/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import SectionMain from "../../_components/Section/Main"; 3 | import SectionTitle from "../../_components/Section/Title"; 4 | import { appTitle, getPageTitle } from "../../_lib/config"; 5 | import { Metadata } from "next"; 6 | 7 | export const metadata: Metadata = { 8 | title: getPageTitle("Responsive"), 9 | }; 10 | 11 | export default function ResponsivePage() { 12 | return ( 13 | <> 14 | Mobile & Tablet 15 | 16 | 17 |
18 | {`Mobile 25 |
26 |
27 | 28 | Small laptop 1024px 29 | 30 | 31 |
32 | {`Small 39 |
40 |
41 | 42 | 43 |
44 | {`Small 51 |
52 |
53 | 54 | Laptop & desktop 55 | 56 | 57 |
58 | {`Laptop 65 |
66 |
67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /app/dashboard/ui/page.tsx: -------------------------------------------------------------------------------- 1 | import { mdiAlertCircle, mdiReload } from "@mdi/js"; 2 | import Button from "../../_components/Button"; 3 | import Buttons from "../../_components/Buttons"; 4 | import CardBox from "../../_components/CardBox"; 5 | import CardBoxComponentEmpty from "../../_components/CardBox/Component/Empty"; 6 | import CardBoxComponentTitle from "../../_components/CardBox/Component/Title"; 7 | import SectionMain from "../../_components/Section/Main"; 8 | import SectionTitle from "../../_components/Section/Title"; 9 | import SectionTitleLineWithButton from "../../_components/Section/TitleLineWithButton"; 10 | import { getPageTitle } from "../../_lib/config"; 11 | import { Metadata } from "next"; 12 | import DarkModeExample from "./_components/DarkModeExample"; 13 | import ModalExamples from "./_components/ModalExamples"; 14 | import NotificationsExample from "./_components/NotificationsExample"; 15 | import ButtonsExample from "./_components/ButtonsExample"; 16 | import PillsExample from "./_components/PillsExample"; 17 | 18 | export const metadata: Metadata = { 19 | title: getPageTitle("UI"), 20 | }; 21 | 22 | export default function UiPage() { 23 | const CardSamplesFooter = ( 24 | 25 |