mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 19:25:44 +00:00
Init Commit
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { colorOptions } from "@/lib/colors";
|
||||
import { Button, Popover, PopoverContent, PopoverTrigger } from "@heroui/react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
interface ColorPickerProps {
|
||||
defaultColor?: string;
|
||||
onChange?: (color: string) => void
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ColorPicker: React.FC<ColorPickerProps> = ({ defaultColor, onChange }) => {
|
||||
const [color , setColor] = useState<string>( defaultColor as string ) ;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const onSelect = useCallback((color: string) => {
|
||||
setIsOpen(false);
|
||||
onChange && onChange(color) ;
|
||||
setColor ( color)
|
||||
}, [onChange])
|
||||
|
||||
return (
|
||||
<Popover placement="bottom" radius="sm" isOpen={isOpen} onOpenChange={(open) => setIsOpen(open)}>
|
||||
<PopoverTrigger>
|
||||
<Button size="sm"
|
||||
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
|
||||
isIconOnly
|
||||
style={{
|
||||
backgroundColor : color
|
||||
}}
|
||||
>
|
||||
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{colorOptions.map(color => (
|
||||
<div
|
||||
key={color}
|
||||
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
|
||||
style={{
|
||||
backgroundColor: color
|
||||
}}
|
||||
onClick={() => onSelect(color)}
|
||||
>
|
||||
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default ColorPicker;
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Dropdown, DropdownMenu as HeroDropdownMenu, DropdownItem, DropdownTrigger, Button, useDisclosure, Popover, PopoverTrigger, PopoverContent, Divider, DropdownSection } from "@heroui/react";
|
||||
import { useMemo, useState } from "react";
|
||||
import SubmenuDropdown from "./submenu-dropdown";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export interface MenuDropdownProps {
|
||||
title?: string;
|
||||
children?: MenuDropdownProps[];
|
||||
|
||||
theme?: "default" | "danger",
|
||||
isDisabled?: boolean,
|
||||
divide?: boolean,
|
||||
shortcut?: string,
|
||||
isOpen?: boolean
|
||||
}
|
||||
|
||||
|
||||
|
||||
const DropdownMenu: React.FC<MenuDropdownProps> = ({ title, children, isOpen }) => {
|
||||
|
||||
|
||||
|
||||
const disabledChilds: string[] = useMemo(() => {
|
||||
return children ? children?.filter((child: MenuDropdownProps) => child.isDisabled && child.title).map((child: MenuDropdownProps) => child.title as string) : []
|
||||
}, [children]);
|
||||
|
||||
|
||||
return <Dropdown radius="sm" >
|
||||
{
|
||||
title &&
|
||||
<DropdownTrigger >
|
||||
<Button size="sm" variant="light" >
|
||||
<span className=" text-left flex justify-between text-small font-semibold">
|
||||
{title}
|
||||
</span>
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
}
|
||||
<HeroDropdownMenu disabledKeys={disabledChilds} >
|
||||
|
||||
{
|
||||
children ? children?.map((child: MenuDropdownProps) => (
|
||||
|
||||
<DropdownItem
|
||||
key={child.title as string}
|
||||
className={child.theme == "danger" ? "text-danger" : ""}
|
||||
color={child.theme}
|
||||
shortcut={child.shortcut}
|
||||
>
|
||||
{
|
||||
!child.children ?
|
||||
<div>
|
||||
{child.title}
|
||||
{
|
||||
child.divide &&
|
||||
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-default-200"></div>
|
||||
}
|
||||
</div>
|
||||
:
|
||||
<div >
|
||||
<SubmenuDropdown {...child} />
|
||||
{
|
||||
child.divide &&
|
||||
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-default-200"></div>
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
</DropdownItem>
|
||||
|
||||
)) : []
|
||||
}
|
||||
|
||||
</HeroDropdownMenu>
|
||||
</Dropdown>
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default DropdownMenu;
|
||||
@@ -0,0 +1,117 @@
|
||||
|
||||
import DropdownMenu, { MenuDropdownProps } from "./menu-dropdown";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const menu: MenuDropdownProps[] = [
|
||||
{
|
||||
title: "File",
|
||||
|
||||
children: [{
|
||||
title: "New"
|
||||
},
|
||||
{
|
||||
title: "Open",
|
||||
shortcut: "Ctnl + O"
|
||||
}, {
|
||||
title: "Save",
|
||||
shortcut: "Ctnl + S",
|
||||
divide: true
|
||||
}, {
|
||||
title: "Import",
|
||||
divide: true,
|
||||
children: [{
|
||||
title: ".json",
|
||||
}, {
|
||||
title: ".dbml"
|
||||
}, {
|
||||
title: "MySql"
|
||||
}, {
|
||||
title: "Postgresql"
|
||||
}]
|
||||
}, {
|
||||
title: "Export SQL",
|
||||
children: [{
|
||||
title: "Generic",
|
||||
}, {
|
||||
title: "MySql"
|
||||
}, {
|
||||
title: "Postgresql"
|
||||
}]
|
||||
}, {
|
||||
title: "Export ORM Models",
|
||||
divide: true
|
||||
}, {
|
||||
title: "Delete Project",
|
||||
theme: "danger"
|
||||
}]
|
||||
}, {
|
||||
title: "Edit",
|
||||
|
||||
children: [{
|
||||
title: "Undo",
|
||||
isDisabled: true,
|
||||
},
|
||||
{
|
||||
title: "Redo",
|
||||
|
||||
isDisabled: true,
|
||||
}, {
|
||||
title: "Clear",
|
||||
|
||||
}]
|
||||
} , {
|
||||
title : "View" ,
|
||||
children : [{
|
||||
title : "Hide Controller" ,
|
||||
shortcut : "Ctnl + B" ,
|
||||
divide : true
|
||||
} , {
|
||||
title : "Zoom on scroll" ,
|
||||
divide : true ,
|
||||
children : [{
|
||||
title : "On"
|
||||
} , {
|
||||
title : "Off"
|
||||
}] ,
|
||||
|
||||
} , {
|
||||
title : "Theme" ,
|
||||
|
||||
children : [{
|
||||
title : "Light"
|
||||
} , {
|
||||
title : "Dark"
|
||||
}]
|
||||
}]
|
||||
} , {
|
||||
title : "Help" ,
|
||||
children : [{
|
||||
title : "Show Docs" ,
|
||||
} , {
|
||||
title : "Join Discord"
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
interface MenuProps {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Menu: React.FC<MenuProps> = ({ }) => {
|
||||
return menu.map(menuItem => (
|
||||
<DropdownMenu {...menuItem} />
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
export default Menu;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Button, Popover, PopoverContent, PopoverTrigger, useDisclosure } from "@heroui/react";
|
||||
import { MenuDropdownProps } from "./menu-dropdown";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
|
||||
import DropdownMenu from "./menu-dropdown";
|
||||
|
||||
|
||||
|
||||
const SubmenuDropdown: React.FC<MenuDropdownProps> = ({ title, children }) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<div onMouseEnter={onOpen} onMouseLeave={onClose} >
|
||||
<Popover placement="right" isOpen={isOpen} radius="sm" offset={90} >
|
||||
<PopoverTrigger>
|
||||
<Button className="w-full h-6 bg-transparent p-0" size="sm" value={"light"}>
|
||||
<span className="w-full text-left flex justify-between text-small font-normal">
|
||||
{title}
|
||||
<ChevronRight className="size-4 text-icon" />
|
||||
</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<DropdownMenu
|
||||
|
||||
children={children}
|
||||
/>
|
||||
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default SubmenuDropdown;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NavbarBrand, NavbarContent, Navbar as NavbarContainer, Image, Dropdown, DropdownTrigger, Button, DropdownMenu, DropdownItem } from "@heroui/react";
|
||||
import Menu from "../menu/menu";
|
||||
|
||||
interface Props {
|
||||
|
||||
}
|
||||
|
||||
|
||||
const Navbar: React.FC<Props> = ({ }) => {
|
||||
return (
|
||||
|
||||
<nav className="h-12 fixed z-50 bg-background w-full flex items-center p-4 border-b border-default-200">
|
||||
<img
|
||||
src="/stackrender.png"
|
||||
width={22}
|
||||
alt="logo"
|
||||
|
||||
|
||||
/>
|
||||
<h3 className="font-semibold ml-2 text-slate-900 text-sm">StackRender</h3>
|
||||
<div className="ml-4">
|
||||
<Menu/>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default Navbar;
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip";
|
||||
import { Divider } from "@heroui/react";
|
||||
import React from "react";
|
||||
|
||||
|
||||
|
||||
export interface SidebarItemProps {
|
||||
type?: "item" | "divider"
|
||||
title?: string;
|
||||
icon?: React.ReactNode;
|
||||
href?: string;
|
||||
isActive? : boolean
|
||||
}
|
||||
|
||||
|
||||
const SidebarItem: React.FC<SidebarItemProps> = ({ title, icon, href , type = "item" , isActive}) => {
|
||||
|
||||
if (type == "item")
|
||||
|
||||
|
||||
return (
|
||||
<Tooltip delayDuration={0} >
|
||||
<TooltipTrigger asChild>
|
||||
<Link to={href as string} className="p-2 rounded-md hover:bg-default-200 sidebar-item" data-active={isActive}>
|
||||
{icon}
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
align="center"
|
||||
>
|
||||
{title}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
)
|
||||
else {
|
||||
return <Divider className="bg-default-200" />
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default SidebarItem
|
||||
@@ -0,0 +1,116 @@
|
||||
import { BookOpen, EarthLock, GitCommitHorizontal, Github, Settings, Sparkles, Table, TableProperties, Waypoints, Workflow, X, Zap } from "lucide-react";
|
||||
import SidebarItem, { SidebarItemProps } from "./sidebar-item";
|
||||
import { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface SidebarProps {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const sidebarItemClass : string = "text-gray-700 size-4 data-[active=true]:text-primary" ;
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ }) => {
|
||||
const { t , i18n } = useTranslation() ;
|
||||
console.log ( i18n.language)
|
||||
|
||||
console.log ()
|
||||
const location = useLocation() ;
|
||||
|
||||
const sidebarItems: SidebarItemProps[] = useMemo(() => [
|
||||
{
|
||||
title: t("sidebar.tables"),
|
||||
icon: <TableProperties className={sidebarItemClass}></TableProperties>,
|
||||
href: "/database/tables",
|
||||
isActive : location.pathname.endsWith("/database/tables")
|
||||
},
|
||||
{
|
||||
title: t("sidebar.relationships"),
|
||||
icon: <Workflow className={sidebarItemClass}></Workflow>,
|
||||
href: "/database/relationships",
|
||||
isActive : location.pathname.endsWith("/database/relationships")
|
||||
},
|
||||
|
||||
{
|
||||
title: "AI",
|
||||
icon: <Sparkles className={sidebarItemClass}></Sparkles>,
|
||||
href: "/database/bot",
|
||||
isActive : location.pathname.endsWith("/database/bot")
|
||||
},
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
title: "API",
|
||||
icon: <Zap className={sidebarItemClass}></Zap>,
|
||||
href: "/database/ai-bot",
|
||||
},
|
||||
{
|
||||
title: "Authentication",
|
||||
icon: <EarthLock className={sidebarItemClass}></EarthLock>,
|
||||
href: "/database/ai-bot",
|
||||
},
|
||||
{
|
||||
title: "Controllers",
|
||||
icon: <Waypoints className={sidebarItemClass}></Waypoints>,
|
||||
href: "/database/ai-bot",
|
||||
},
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
title: "Git",
|
||||
icon: <GitCommitHorizontal className={sidebarItemClass}></GitCommitHorizontal>,
|
||||
href: "/database/ai-bot",
|
||||
},
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
title: "Project Setting",
|
||||
icon: <Settings className={sidebarItemClass}></Settings>,
|
||||
href: "/database/ai-bot",
|
||||
},
|
||||
], [location])
|
||||
|
||||
const bottomSidebarItems: SidebarItemProps[] = useMemo(() => [
|
||||
{
|
||||
title: "Github",
|
||||
icon: <Github className={sidebarItemClass}></Github>,
|
||||
href: "/database/ai-bot",
|
||||
},
|
||||
{
|
||||
title: "Docs",
|
||||
icon: <BookOpen className={sidebarItemClass}></BookOpen>,
|
||||
href: "/database/ai-bot",
|
||||
},
|
||||
], []) ;
|
||||
|
||||
return (
|
||||
|
||||
<aside className="h-full z-[20] flex flex-col items-between py-2 justify-between sticky top-0 duration-500 w-12 bg-sidebar border-r pt-[56px]">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
{
|
||||
sidebarItems.map((item: SidebarItemProps) => (
|
||||
<SidebarItem
|
||||
{...item}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2 ">
|
||||
{
|
||||
bottomSidebarItems.map((item: SidebarItemProps) => (
|
||||
<SidebarItem
|
||||
{...item}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default Sidebar;
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
import { cn } from '@heroui/react';
|
||||
|
||||
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
|
||||
className={cn(
|
||||
'z-50 overflow-hidden rounded-md bg-foreground px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
Reference in New Issue
Block a user