mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 21:11:53 +00:00
feat: set basic sidebar and the main layout
This commit is contained in:
+7
-6
@@ -1,12 +1,13 @@
|
||||
import "@/styles/globals.css";
|
||||
|
||||
import { Navbar } from "@nextui-org/react";
|
||||
import clsx from "clsx";
|
||||
import { Metadata, Viewport } from "next";
|
||||
import React from "react";
|
||||
|
||||
import { fontSans } from "@/config/fonts";
|
||||
import { siteConfig } from "@/config/site";
|
||||
|
||||
import { SidebarWrap } from "../components/sidebar/SidebarWrap";
|
||||
import { Providers } from "./providers";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -42,11 +43,11 @@ export default function RootLayout({
|
||||
)}
|
||||
>
|
||||
<Providers themeProps={{ attribute: "class", defaultTheme: "dark" }}>
|
||||
<div className="relative flex flex-col h-screen">
|
||||
<Navbar />
|
||||
<main className="container mx-auto max-w-7xl pt-16 px-6 flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
<div className="flex items-center dark h-screen justify-center overflow-hidden">
|
||||
<div className="flex h-dvh w-full">
|
||||
<SidebarWrap />
|
||||
<main className="w-full flex-1 flex-col p-4">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
|
||||
+168
-56
@@ -1,65 +1,177 @@
|
||||
"use client";
|
||||
|
||||
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
Input,
|
||||
Link,
|
||||
} from "@nextui-org/react";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
export default function Home() {
|
||||
const [isVisible, setIsVisible] = React.useState(false);
|
||||
|
||||
const toggleVisibility = () => setIsVisible(!isVisible);
|
||||
|
||||
return (
|
||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
||||
<div className="inline-block max-w-lg text-center justify-center">
|
||||
<Card>
|
||||
<CardHeader className="pb-0">
|
||||
<h1 className="pl-3">Login</h1>
|
||||
</CardHeader>
|
||||
<CardBody className="w-72 pb-0">
|
||||
<Input
|
||||
label="Email"
|
||||
variant="bordered"
|
||||
placeholder="Enter your email"
|
||||
className="mb-2"
|
||||
<>
|
||||
<main className="h-full w-full overflow-visible ">
|
||||
<header className="flex items-center gap-3 rounded-medium border-small border-divider p-4">
|
||||
<Button isIconOnly size="sm" variant="light">
|
||||
<Icon
|
||||
className="text-default-500"
|
||||
height={24}
|
||||
icon="solar:sidebar-minimalistic-outline"
|
||||
width={24}
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
variant="bordered"
|
||||
placeholder="Enter your password"
|
||||
endContent={
|
||||
<button
|
||||
className="focus:outline-none"
|
||||
type="button"
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
{isVisible ? (
|
||||
<EyeSlashIcon className="text-2xl text-default-400 pointer-events-none w-5 h-5" />
|
||||
) : (
|
||||
<EyeIcon className="text-2xl text-default-400 pointer-events-none w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
type={isVisible ? "text" : "password"}
|
||||
className="max-w-xs"
|
||||
/>
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
<Button href="/clouds" as={Link} color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<p className="mt-24">This is a page with "use client", useState</p>
|
||||
</div>
|
||||
</section>
|
||||
</Button>
|
||||
<h2 className="text-medium font-medium text-default-700">Overview</h2>
|
||||
</header>
|
||||
<div className="flex h-dvh-minus-16 w-full mt-4 flex-col gap-4 p-4 overflow-y-scroll">
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
<p> hi hi from the overview page </p>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
import { IconSvgProps } from "../types/index";
|
||||
|
||||
export const AcmeIcon: React.FC<IconSvgProps> = ({
|
||||
size = 32,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
fill="none"
|
||||
height={size || height}
|
||||
viewBox="0 0 32 32"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,327 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
type ListboxProps,
|
||||
type ListboxSectionProps,
|
||||
type Selection,
|
||||
} from "@nextui-org/react";
|
||||
import {
|
||||
Listbox,
|
||||
ListboxItem,
|
||||
ListboxSection,
|
||||
Tooltip,
|
||||
} from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
export enum SidebarItemType {
|
||||
Nest = "nest",
|
||||
}
|
||||
|
||||
export type SidebarItem = {
|
||||
key: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
href?: string;
|
||||
type?: SidebarItemType.Nest;
|
||||
startContent?: React.ReactNode;
|
||||
endContent?: React.ReactNode;
|
||||
items?: SidebarItem[];
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export type SidebarProps = Omit<ListboxProps<SidebarItem>, "children"> & {
|
||||
items: SidebarItem[];
|
||||
isCompact?: boolean;
|
||||
hideEndContent?: boolean;
|
||||
iconClassName?: string;
|
||||
sectionClasses?: ListboxSectionProps["classNames"];
|
||||
classNames?: ListboxProps["classNames"];
|
||||
defaultSelectedKey: string;
|
||||
onSelect?: (key: string) => void;
|
||||
};
|
||||
|
||||
const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
|
||||
(
|
||||
{
|
||||
items,
|
||||
isCompact,
|
||||
defaultSelectedKey,
|
||||
onSelect,
|
||||
hideEndContent,
|
||||
sectionClasses: sectionClassesProp = {},
|
||||
itemClasses: itemClassesProp = {},
|
||||
iconClassName,
|
||||
classNames,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [selected, setSelected] =
|
||||
React.useState<React.Key>(defaultSelectedKey);
|
||||
|
||||
const sectionClasses = {
|
||||
...sectionClassesProp,
|
||||
base: cn(sectionClassesProp?.base, "w-full", {
|
||||
"p-0 max-w-[44px]": isCompact,
|
||||
}),
|
||||
group: cn(sectionClassesProp?.group, {
|
||||
"flex flex-col gap-1": isCompact,
|
||||
}),
|
||||
heading: cn(sectionClassesProp?.heading, {
|
||||
hidden: isCompact,
|
||||
}),
|
||||
};
|
||||
|
||||
const itemClasses = {
|
||||
...itemClassesProp,
|
||||
base: cn(itemClassesProp?.base, {
|
||||
"w-11 h-11 gap-0 p-0": isCompact,
|
||||
}),
|
||||
};
|
||||
|
||||
const renderNestItem = React.useCallback(
|
||||
(item: SidebarItem) => {
|
||||
const isNestType =
|
||||
item.items &&
|
||||
item.items?.length > 0 &&
|
||||
item?.type === SidebarItemType.Nest;
|
||||
|
||||
if (isNestType) {
|
||||
// Is a nest type item , so we need to remove the href
|
||||
delete item.href;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListboxItem
|
||||
{...item}
|
||||
key={item.key}
|
||||
classNames={{
|
||||
base: cn(
|
||||
{
|
||||
"h-auto p-0": !isCompact && isNestType,
|
||||
},
|
||||
{
|
||||
"inline-block w-11": isCompact && isNestType,
|
||||
},
|
||||
),
|
||||
}}
|
||||
endContent={
|
||||
isCompact || isNestType || hideEndContent
|
||||
? null
|
||||
: item.endContent ?? null
|
||||
}
|
||||
startContent={
|
||||
isCompact || isNestType ? null : item.icon ? (
|
||||
<Icon
|
||||
className={cn(
|
||||
"text-default-500 group-data-[selected=true]:text-foreground",
|
||||
iconClassName,
|
||||
)}
|
||||
icon={item.icon}
|
||||
width={24}
|
||||
/>
|
||||
) : (
|
||||
item.startContent ?? null
|
||||
)
|
||||
}
|
||||
title={isCompact || isNestType ? null : item.title}
|
||||
>
|
||||
{isCompact ? (
|
||||
<Tooltip content={item.title} placement="right">
|
||||
<div className="flex w-full items-center justify-center">
|
||||
{item.icon ? (
|
||||
<Icon
|
||||
className={cn(
|
||||
"text-default-500 group-data-[selected=true]:text-foreground",
|
||||
iconClassName,
|
||||
)}
|
||||
icon={item.icon}
|
||||
width={24}
|
||||
/>
|
||||
) : (
|
||||
item.startContent ?? null
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{!isCompact && isNestType ? (
|
||||
<Accordion className={"p-0"}>
|
||||
<AccordionItem
|
||||
key={item.key}
|
||||
aria-label={item.title}
|
||||
classNames={{
|
||||
heading: "pr-3",
|
||||
trigger: "p-0",
|
||||
content: "py-0 pl-4",
|
||||
}}
|
||||
title={
|
||||
item.icon ? (
|
||||
<div
|
||||
className={"flex h-11 items-center gap-2 px-2 py-1.5"}
|
||||
>
|
||||
<Icon
|
||||
className={cn(
|
||||
"text-default-500 group-data-[selected=true]:text-foreground",
|
||||
iconClassName,
|
||||
)}
|
||||
icon={item.icon}
|
||||
width={24}
|
||||
/>
|
||||
<span className="text-small font-medium text-default-500 group-data-[selected=true]:text-foreground">
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
item.startContent ?? null
|
||||
)
|
||||
}
|
||||
>
|
||||
{item.items && item.items?.length > 0 ? (
|
||||
<Listbox
|
||||
className={"mt-0.5"}
|
||||
classNames={{
|
||||
list: cn("border-l border-default-200 pl-4"),
|
||||
}}
|
||||
items={item.items}
|
||||
variant="flat"
|
||||
>
|
||||
{item.items.map(renderItem)}
|
||||
</Listbox>
|
||||
) : (
|
||||
renderItem(item)
|
||||
)}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
) : null}
|
||||
</ListboxItem>
|
||||
);
|
||||
},
|
||||
[isCompact, hideEndContent, iconClassName, items],
|
||||
);
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
(item: SidebarItem) => {
|
||||
const isNestType =
|
||||
item.items &&
|
||||
item.items?.length > 0 &&
|
||||
item?.type === SidebarItemType.Nest;
|
||||
|
||||
if (isNestType) {
|
||||
return renderNestItem(item);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListboxItem
|
||||
{...item}
|
||||
key={item.key}
|
||||
endContent={
|
||||
isCompact || hideEndContent ? null : item.endContent ?? null
|
||||
}
|
||||
startContent={
|
||||
isCompact ? null : item.icon ? (
|
||||
<Icon
|
||||
className={cn(
|
||||
"text-default-500 group-data-[selected=true]:text-foreground",
|
||||
iconClassName,
|
||||
)}
|
||||
icon={item.icon}
|
||||
width={24}
|
||||
/>
|
||||
) : (
|
||||
item.startContent ?? null
|
||||
)
|
||||
}
|
||||
textValue={item.title}
|
||||
title={isCompact ? null : item.title}
|
||||
>
|
||||
{isCompact ? (
|
||||
<Tooltip content={item.title} placement="right">
|
||||
<div className="flex w-full items-center justify-center">
|
||||
{item.icon ? (
|
||||
<Icon
|
||||
className={cn(
|
||||
"text-default-500 group-data-[selected=true]:text-foreground",
|
||||
iconClassName,
|
||||
)}
|
||||
icon={item.icon}
|
||||
width={24}
|
||||
/>
|
||||
) : (
|
||||
item.startContent ?? null
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</ListboxItem>
|
||||
);
|
||||
},
|
||||
[isCompact, hideEndContent, iconClassName, itemClasses?.base],
|
||||
);
|
||||
|
||||
return (
|
||||
<Listbox
|
||||
key={isCompact ? "compact" : "default"}
|
||||
ref={ref}
|
||||
hideSelectedIcon
|
||||
as="nav"
|
||||
className={cn("list-none", className)}
|
||||
classNames={{
|
||||
...classNames,
|
||||
list: cn("items-center", classNames?.list),
|
||||
}}
|
||||
color="default"
|
||||
itemClasses={{
|
||||
...itemClasses,
|
||||
base: cn(
|
||||
"px-3 min-h-11 rounded-large h-[44px] data-[selected=true]:bg-default-100",
|
||||
itemClasses?.base,
|
||||
),
|
||||
title: cn(
|
||||
"text-small font-medium text-default-500 group-data-[selected=true]:text-foreground",
|
||||
itemClasses?.title,
|
||||
),
|
||||
}}
|
||||
items={items}
|
||||
selectedKeys={[selected] as unknown as Selection}
|
||||
selectionMode="single"
|
||||
variant="flat"
|
||||
onSelectionChange={(keys) => {
|
||||
const key = Array.from(keys)[0];
|
||||
|
||||
setSelected(key as React.Key);
|
||||
onSelect?.(key as string);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{(item) => {
|
||||
return item.items &&
|
||||
item.items?.length > 0 &&
|
||||
item?.type === SidebarItemType.Nest ? (
|
||||
renderNestItem(item)
|
||||
) : item.items && item.items?.length > 0 ? (
|
||||
<ListboxSection
|
||||
key={item.key}
|
||||
classNames={sectionClasses}
|
||||
showDivider={isCompact}
|
||||
title={item.title}
|
||||
>
|
||||
{item.items.map(renderItem)}
|
||||
</ListboxSection>
|
||||
) : (
|
||||
renderItem(item)
|
||||
);
|
||||
}}
|
||||
</Listbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Sidebar.displayName = "Sidebar";
|
||||
|
||||
export default Sidebar;
|
||||
@@ -0,0 +1,503 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Chip } from "@nextui-org/react";
|
||||
|
||||
import { SidebarItem, SidebarItemType } from "./Sidebar";
|
||||
import TeamAvatar from "./TeamAvatar";
|
||||
|
||||
/**
|
||||
* Please check the https://nextui.org/docs/guide/routing to have a seamless router integration
|
||||
*/
|
||||
|
||||
export const items: SidebarItem[] = [
|
||||
{
|
||||
key: "home",
|
||||
href: "#",
|
||||
icon: "solar:home-2-linear",
|
||||
title: "Home",
|
||||
},
|
||||
{
|
||||
key: "projects",
|
||||
href: "#",
|
||||
icon: "solar:widget-2-outline",
|
||||
title: "Projects",
|
||||
endContent: (
|
||||
<Icon
|
||||
className="text-default-400"
|
||||
icon="solar:add-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "tasks",
|
||||
href: "#",
|
||||
icon: "solar:checklist-minimalistic-outline",
|
||||
title: "Tasks",
|
||||
endContent: (
|
||||
<Icon
|
||||
className="text-default-400"
|
||||
icon="solar:add-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "team",
|
||||
href: "#",
|
||||
icon: "solar:users-group-two-rounded-outline",
|
||||
title: "Team",
|
||||
},
|
||||
{
|
||||
key: "tracker",
|
||||
href: "#",
|
||||
icon: "solar:sort-by-time-linear",
|
||||
title: "Tracker",
|
||||
endContent: (
|
||||
<Chip size="sm" variant="flat">
|
||||
New
|
||||
</Chip>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "analytics",
|
||||
href: "#",
|
||||
icon: "solar:chart-outline",
|
||||
title: "Analytics",
|
||||
},
|
||||
{
|
||||
key: "perks",
|
||||
href: "#",
|
||||
icon: "solar:gift-linear",
|
||||
title: "Perks",
|
||||
endContent: (
|
||||
<Chip size="sm" variant="flat">
|
||||
3
|
||||
</Chip>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "expenses",
|
||||
href: "#",
|
||||
icon: "solar:bill-list-outline",
|
||||
title: "Expenses",
|
||||
},
|
||||
{
|
||||
key: "settings",
|
||||
href: "#",
|
||||
icon: "solar:settings-outline",
|
||||
title: "Settings",
|
||||
},
|
||||
];
|
||||
|
||||
export const sectionItems: SidebarItem[] = [
|
||||
{
|
||||
key: "dashboards",
|
||||
title: "Dashboards",
|
||||
items: [
|
||||
{
|
||||
key: "home",
|
||||
href: "/",
|
||||
icon: "solar:home-2-linear",
|
||||
title: "Overview",
|
||||
},
|
||||
// {
|
||||
// key: "projects",
|
||||
// href: "#",
|
||||
// icon: "solar:widget-2-outline",
|
||||
// title: "Projects",
|
||||
// endContent: (
|
||||
// <Icon
|
||||
// className="text-default-400"
|
||||
// icon="solar:add-circle-line-duotone"
|
||||
// width={24}
|
||||
// />
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// key: "tasks",
|
||||
// href: "#",
|
||||
// icon: "solar:checklist-minimalistic-outline",
|
||||
// title: "Tasks",
|
||||
// endContent: (
|
||||
// <Icon
|
||||
// className="text-default-400"
|
||||
// icon="solar:add-circle-line-duotone"
|
||||
// width={24}
|
||||
// />
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
key: "services",
|
||||
href: "/services",
|
||||
icon: "solar:users-group-two-rounded-outline",
|
||||
title: "Services",
|
||||
},
|
||||
{
|
||||
key: "compliance",
|
||||
href: "/compliance",
|
||||
icon: "solar:sort-by-time-linear",
|
||||
title: "Compliance",
|
||||
endContent: (
|
||||
<Chip size="sm" variant="flat">
|
||||
New
|
||||
</Chip>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "scan",
|
||||
title: "Scan",
|
||||
items: [
|
||||
{
|
||||
key: "findings",
|
||||
href: "/findings",
|
||||
title: "Findings",
|
||||
icon: "solar:pie-chart-2-outline",
|
||||
items: [
|
||||
{
|
||||
key: "shareholders",
|
||||
href: "#",
|
||||
title: "Shareholders",
|
||||
},
|
||||
{
|
||||
key: "note_holders",
|
||||
href: "#",
|
||||
title: "Note Holders",
|
||||
},
|
||||
{
|
||||
key: "transactions_log",
|
||||
href: "#",
|
||||
title: "Transactions Log",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "accounts",
|
||||
title: "Accounts",
|
||||
items: [
|
||||
{
|
||||
key: "cloud",
|
||||
href: "/cloud",
|
||||
icon: "solar:gift-linear",
|
||||
title: "Cloud",
|
||||
endContent: (
|
||||
<Chip size="sm" variant="flat">
|
||||
3
|
||||
</Chip>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "integration",
|
||||
href: "/integration",
|
||||
icon: "solar:bill-list-outline",
|
||||
title: "Integration",
|
||||
},
|
||||
{
|
||||
key: "settings",
|
||||
href: "/settings",
|
||||
icon: "solar:settings-outline",
|
||||
title: "Settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const sectionItemsWithTeams: SidebarItem[] = [
|
||||
...sectionItems,
|
||||
{
|
||||
key: "team",
|
||||
title: "Team",
|
||||
items: [
|
||||
{
|
||||
key: "users",
|
||||
href: "#",
|
||||
title: "Users",
|
||||
startContent: <TeamAvatar name="Users page" />,
|
||||
},
|
||||
{
|
||||
key: "roles",
|
||||
href: "#",
|
||||
title: "Roles",
|
||||
startContent: <TeamAvatar name="Roles page" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const brandItems: SidebarItem[] = [
|
||||
{
|
||||
key: "overview",
|
||||
title: "Overview",
|
||||
items: [
|
||||
{
|
||||
key: "home",
|
||||
href: "/",
|
||||
icon: "solar:home-2-linear",
|
||||
title: "Home",
|
||||
},
|
||||
{
|
||||
key: "projects",
|
||||
href: "#",
|
||||
icon: "solar:widget-2-outline",
|
||||
title: "Projects",
|
||||
endContent: (
|
||||
<Icon
|
||||
className="text-primary-foreground/60"
|
||||
icon="solar:add-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "tasks",
|
||||
href: "#",
|
||||
icon: "solar:checklist-minimalistic-outline",
|
||||
title: "Tasks",
|
||||
endContent: (
|
||||
<Icon
|
||||
className="text-primary-foreground/60"
|
||||
icon="solar:add-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "team",
|
||||
href: "#",
|
||||
icon: "solar:users-group-two-rounded-outline",
|
||||
title: "Team",
|
||||
},
|
||||
{
|
||||
key: "tracker",
|
||||
href: "#",
|
||||
icon: "solar:sort-by-time-linear",
|
||||
title: "Tracker",
|
||||
endContent: (
|
||||
<Chip
|
||||
className="bg-primary-foreground font-medium text-primary"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
New
|
||||
</Chip>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "your-teams",
|
||||
title: "Your Teams",
|
||||
items: [
|
||||
{
|
||||
key: "nextui",
|
||||
href: "#",
|
||||
title: "NextUI",
|
||||
startContent: (
|
||||
<TeamAvatar
|
||||
classNames={{
|
||||
base: "border-1 border-primary-foreground/20",
|
||||
name: "text-primary-foreground/80",
|
||||
}}
|
||||
name="Next UI"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "tailwind-variants",
|
||||
href: "#",
|
||||
title: "Tailwind Variants",
|
||||
startContent: (
|
||||
<TeamAvatar
|
||||
classNames={{
|
||||
base: "border-1 border-primary-foreground/20",
|
||||
name: "text-primary-foreground/80",
|
||||
}}
|
||||
name="Tailwind Variants"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "nextui-pro",
|
||||
href: "#",
|
||||
title: "NextUI Pro",
|
||||
startContent: (
|
||||
<TeamAvatar
|
||||
classNames={{
|
||||
base: "border-1 border-primary-foreground/20",
|
||||
name: "text-primary-foreground/80",
|
||||
}}
|
||||
name="NextUI Pro"
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const sectionLongList: SidebarItem[] = [
|
||||
...sectionItems,
|
||||
{
|
||||
key: "payments",
|
||||
title: "Payments",
|
||||
items: [
|
||||
{
|
||||
key: "payroll",
|
||||
href: "#",
|
||||
title: "Payroll",
|
||||
icon: "solar:dollar-minimalistic-linear",
|
||||
},
|
||||
{
|
||||
key: "invoices",
|
||||
href: "#",
|
||||
title: "Invoices",
|
||||
icon: "solar:file-text-linear",
|
||||
},
|
||||
{
|
||||
key: "billing",
|
||||
href: "#",
|
||||
title: "Billing",
|
||||
icon: "solar:card-outline",
|
||||
},
|
||||
{
|
||||
key: "payment-methods",
|
||||
href: "#",
|
||||
title: "Payment Methods",
|
||||
icon: "solar:wallet-money-outline",
|
||||
},
|
||||
{
|
||||
key: "payouts",
|
||||
href: "#",
|
||||
title: "Payouts",
|
||||
icon: "solar:card-transfer-outline",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "your-teams",
|
||||
title: "Your Teams",
|
||||
items: [
|
||||
{
|
||||
key: "nextui",
|
||||
href: "#",
|
||||
title: "NextUI",
|
||||
startContent: <TeamAvatar name="Next UI" />,
|
||||
},
|
||||
{
|
||||
key: "tailwind-variants",
|
||||
href: "#",
|
||||
title: "Tailwind Variants",
|
||||
startContent: <TeamAvatar name="Tailwind Variants" />,
|
||||
},
|
||||
{
|
||||
key: "nextui-pro",
|
||||
href: "#",
|
||||
title: "NextUI Pro",
|
||||
startContent: <TeamAvatar name="NextUI Pro" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const sectionNestedItems: SidebarItem[] = [
|
||||
{
|
||||
key: "home",
|
||||
href: "#",
|
||||
icon: "solar:home-2-linear",
|
||||
title: "Home",
|
||||
},
|
||||
{
|
||||
key: "projects",
|
||||
href: "#",
|
||||
icon: "solar:widget-2-outline",
|
||||
title: "Projects",
|
||||
endContent: (
|
||||
<Icon
|
||||
className="text-default-400"
|
||||
icon="solar:add-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "tasks",
|
||||
href: "#",
|
||||
icon: "solar:checklist-minimalistic-outline",
|
||||
title: "Tasks",
|
||||
endContent: (
|
||||
<Icon
|
||||
className="text-default-400"
|
||||
icon="solar:add-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "team",
|
||||
href: "#",
|
||||
icon: "solar:users-group-two-rounded-outline",
|
||||
title: "Team",
|
||||
},
|
||||
{
|
||||
key: "tracker",
|
||||
href: "#",
|
||||
icon: "solar:sort-by-time-linear",
|
||||
title: "Tracker",
|
||||
endContent: (
|
||||
<Chip size="sm" variant="flat">
|
||||
New
|
||||
</Chip>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "analytics",
|
||||
href: "#",
|
||||
icon: "solar:chart-outline",
|
||||
title: "Analytics",
|
||||
},
|
||||
{
|
||||
key: "perks",
|
||||
href: "#",
|
||||
icon: "solar:gift-linear",
|
||||
title: "Perks",
|
||||
endContent: (
|
||||
<Chip size="sm" variant="flat">
|
||||
3
|
||||
</Chip>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "cap_table",
|
||||
title: "Cap Table",
|
||||
icon: "solar:pie-chart-2-outline",
|
||||
type: SidebarItemType.Nest,
|
||||
items: [
|
||||
{
|
||||
key: "shareholders",
|
||||
icon: "solar:users-group-rounded-linear",
|
||||
href: "#",
|
||||
title: "Shareholders",
|
||||
},
|
||||
{
|
||||
key: "note_holders",
|
||||
icon: "solar:notes-outline",
|
||||
href: "#",
|
||||
title: "Note Holders",
|
||||
},
|
||||
{
|
||||
key: "transactions_log",
|
||||
icon: "solar:clipboard-list-linear",
|
||||
href: "#",
|
||||
title: "Transactions Log",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "expenses",
|
||||
href: "#",
|
||||
icon: "solar:bill-list-outline",
|
||||
title: "Expenses",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,191 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
import { AcmeIcon } from "../acme";
|
||||
import { ThemeSwitch } from "../ThemeSwitch";
|
||||
import Sidebar from "./Sidebar";
|
||||
import { sectionItemsWithTeams } from "./SidebarItems";
|
||||
import UserAvatar from "./UserAvatar";
|
||||
|
||||
export const SidebarWrap = () => {
|
||||
const [isCollapsed, setIsCollapsed] = React.useState(false);
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
const isCompact = isCollapsed || isMobile;
|
||||
|
||||
const onToggle = React.useCallback(() => {
|
||||
setIsCollapsed((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex h-screen w-72 flex-col !border-r-small border-divider p-6 transition-width",
|
||||
{
|
||||
"w-16 items-center px-2 py-6": isCompact,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3",
|
||||
|
||||
{
|
||||
"justify-center gap-0": isCompact,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-foreground">
|
||||
<AcmeIcon className="text-background" />
|
||||
</div>
|
||||
<span
|
||||
className={cn("text-small font-bold uppercase opacity-100", {
|
||||
"w-0 opacity-0": isCompact,
|
||||
})}
|
||||
>
|
||||
Prowler
|
||||
</span>
|
||||
</div>
|
||||
<Spacer y={8} />
|
||||
|
||||
<UserAvatar
|
||||
userName={"Pablo Lara"}
|
||||
position={"Software Engineer"}
|
||||
isCompact={isCompact}
|
||||
/>
|
||||
|
||||
<ScrollShadow className="-mr-6 h-full max-h-full py-6 pr-6">
|
||||
<Sidebar
|
||||
defaultSelectedKey="home"
|
||||
isCompact={isCompact}
|
||||
items={sectionItemsWithTeams}
|
||||
/>
|
||||
</ScrollShadow>
|
||||
<Spacer y={2} />
|
||||
<div
|
||||
className={cn("mt-auto flex flex-col", {
|
||||
"items-center": isCompact,
|
||||
})}
|
||||
>
|
||||
<Tooltip
|
||||
content="Help & Feedback"
|
||||
isDisabled={!isCompact}
|
||||
placement="right"
|
||||
>
|
||||
<Button
|
||||
fullWidth
|
||||
className={cn(
|
||||
"justify-start truncate text-default-500 data-[hover=true]:text-foreground",
|
||||
{
|
||||
"justify-center": isCompact,
|
||||
},
|
||||
)}
|
||||
isIconOnly={isCompact}
|
||||
startContent={
|
||||
isCompact ? null : (
|
||||
<Icon
|
||||
className="flex-none text-default-500"
|
||||
icon="solar:info-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
)
|
||||
}
|
||||
variant="light"
|
||||
>
|
||||
{isCompact ? (
|
||||
<Icon
|
||||
className="text-default-500"
|
||||
icon="solar:info-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
) : (
|
||||
"Help & Information"
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Log Out" isDisabled={!isCompact} placement="right">
|
||||
<Button
|
||||
className={cn(
|
||||
"justify-start text-default-500 data-[hover=true]:text-foreground",
|
||||
{
|
||||
"justify-center": isCompact,
|
||||
},
|
||||
)}
|
||||
isIconOnly={isCompact}
|
||||
startContent={
|
||||
isCompact ? null : (
|
||||
<Icon
|
||||
className="flex-none rotate-180 text-default-500"
|
||||
icon="solar:minus-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
)
|
||||
}
|
||||
variant="light"
|
||||
>
|
||||
{isCompact ? (
|
||||
<Icon
|
||||
className="rotate-180 text-default-500"
|
||||
icon="solar:minus-circle-line-duotone"
|
||||
width={24}
|
||||
/>
|
||||
) : (
|
||||
"Log Out"
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
className={cn("mt-auto flex justify-end gap-3 items-baseline", {
|
||||
"flex-col items-center": isCompact,
|
||||
})}
|
||||
>
|
||||
<Tooltip
|
||||
content="Light | Dark mode"
|
||||
placement={isCompact ? "right" : "top"}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"text-default-500 data-[hover=true]:text-foreground px-0",
|
||||
{
|
||||
"justify-center mt-3": isCompact,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content="Open | Close sidebar"
|
||||
placement={isCompact ? "right" : "top"}
|
||||
>
|
||||
<Button
|
||||
className={cn(
|
||||
"text-default-500 data-[hover=true]:text-foreground px-0",
|
||||
{
|
||||
"justify-center": isCompact,
|
||||
},
|
||||
)}
|
||||
isIconOnly
|
||||
size="sm"
|
||||
variant="light"
|
||||
onPress={onToggle}
|
||||
>
|
||||
<Icon
|
||||
className="text-default-500"
|
||||
height={24}
|
||||
icon="solar:sidebar-minimalistic-outline"
|
||||
width={24}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import type { AvatarProps } from "@nextui-org/react";
|
||||
import { Avatar } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
const TeamAvatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
|
||||
({ name, className, classNames = {}, ...props }, ref) => (
|
||||
<Avatar
|
||||
{...props}
|
||||
ref={ref}
|
||||
classNames={{
|
||||
...classNames,
|
||||
base: cn(
|
||||
"bg-transparent border border-divider",
|
||||
classNames?.base,
|
||||
className,
|
||||
),
|
||||
name: cn(
|
||||
"text-default-500 text-[0.6rem] font-semibold",
|
||||
classNames?.name,
|
||||
),
|
||||
}}
|
||||
getInitials={(name) =>
|
||||
(name[0] || "") + (name[name.lastIndexOf(" ") + 1] || "").toUpperCase()
|
||||
}
|
||||
name={name}
|
||||
radius="md"
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
TeamAvatar.displayName = "TeamAvatar";
|
||||
|
||||
export default TeamAvatar;
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { Avatar } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
interface UserAvatarProps {
|
||||
userName: string;
|
||||
position: string;
|
||||
isCompact: boolean;
|
||||
}
|
||||
const UserAvatar: React.FC<UserAvatarProps> = ({
|
||||
userName,
|
||||
position,
|
||||
isCompact = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center gap-3 px-3">
|
||||
<Avatar isBordered className="flex-none" size="sm" showFallback />
|
||||
<div
|
||||
className={cn("flex max-w-full flex-col", {
|
||||
hidden: isCompact,
|
||||
})}
|
||||
>
|
||||
<p className="truncate text-small font-medium text-default-600">
|
||||
{userName}
|
||||
</p>
|
||||
<p className="truncate text-tiny text-default-400">{position}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAvatar;
|
||||
+3
-1
@@ -16,6 +16,7 @@
|
||||
"swr": "^2.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/react": "^5.0.1",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
@@ -39,7 +40,8 @@
|
||||
"postcss": "8.4.38",
|
||||
"tailwind-variants": "0.1.20",
|
||||
"tailwindcss": "3.4.3",
|
||||
"typescript": "5.0.4"
|
||||
"typescript": "5.0.4",
|
||||
"usehooks-ts": "^3.1.0"
|
||||
},
|
||||
"name": "prowler-next-app",
|
||||
"private": true,
|
||||
|
||||
+8
-5
@@ -1,11 +1,11 @@
|
||||
import {nextui} from '@nextui-org/theme'
|
||||
import { nextui } from "@nextui-org/theme";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}'
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
@@ -13,8 +13,11 @@ module.exports = {
|
||||
sans: ["var(--font-sans)"],
|
||||
mono: ["var(--font-geist-mono)"],
|
||||
},
|
||||
height: {
|
||||
"dvh-minus-16": "calc(100dvh - 116px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
darkMode: "class",
|
||||
plugins: [nextui()],
|
||||
}
|
||||
};
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import type { ClassValue } from "clsx";
|
||||
import clsx from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
// Definition of custom classes you want to combine
|
||||
const customClasses = new Map<string, string>([
|
||||
["text-small", "text-small"],
|
||||
["text-default-500", "text-default-500"],
|
||||
// Add more custom classes as needed
|
||||
]);
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
// Filter and combine custom classes before passing them to twMerge
|
||||
const filteredInputs = inputs.map((input) => {
|
||||
if (typeof input === "string") {
|
||||
return customClasses.get(input) || input;
|
||||
}
|
||||
return input;
|
||||
});
|
||||
|
||||
return twMerge(clsx(filteredInputs));
|
||||
}
|
||||
Reference in New Issue
Block a user