Add app sidebar
This commit is contained in:
@@ -8,55 +8,40 @@ import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
} from '@/components/ui/sidebar';
|
||||
import {
|
||||
Home,
|
||||
Library,
|
||||
PanelsTopLeft,
|
||||
PanelTop,
|
||||
Search,
|
||||
Users as UsersIcon,
|
||||
Code2,
|
||||
} from 'lucide-react';
|
||||
import { PanelsTopLeft, Users as UsersIcon, Code2 } from 'lucide-react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
||||
import { useTheme } from './theme-provider';
|
||||
import { getEffectiveTheme } from '../utils/effectiveTheme';
|
||||
import { NavUser } from './NavUser';
|
||||
import { Logo } from './logo';
|
||||
import { Separator } from './ui/separator';
|
||||
|
||||
const AppSidebar = () => {
|
||||
interface AppSidebarProps {
|
||||
userName?: string;
|
||||
profilePictureUrl?: string;
|
||||
}
|
||||
|
||||
const AppSidebar = ({ userName, profilePictureUrl }: AppSidebarProps) => {
|
||||
const location = useLocation();
|
||||
const { theme } = useTheme();
|
||||
const effectiveTheme = getEffectiveTheme(theme);
|
||||
|
||||
return (
|
||||
<Sidebar variant="sidebar" collapsible="icon">
|
||||
<Sidebar variant="floating" collapsible="icon">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="cursor-default hover:bg-transparent active:bg-transparent"
|
||||
className="hover:bg-transparent active:bg-transparent"
|
||||
asChild
|
||||
>
|
||||
<Avatar className="h-8 w-8 p-1 rounded-lg">
|
||||
<AvatarImage
|
||||
src={effectiveTheme === 'dark' ? '/logo.svg' : '/logo-dark.svg'}
|
||||
alt={'Quay logo'}
|
||||
/>
|
||||
<AvatarFallback className="rounded-lg">{'Q'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">Quay</span>
|
||||
<span className="truncate text-xs font-normal text-muted-foreground">
|
||||
Static site deployment
|
||||
</span>
|
||||
</div>
|
||||
<Link to="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<Separator className="mb-2" />
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
{/* <SidebarGroupLabel>Idk</SidebarGroupLabel> */}
|
||||
{/* <SidebarGroupLabel>Sites</SidebarGroupLabel> */}
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
@@ -66,37 +51,6 @@ const AppSidebar = () => {
|
||||
Sites
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<SidebarMenuSub>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link to={`/library?library=tensuramap`}>
|
||||
TensuraMap
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link to={`/library?library=tensuramap`}>bifrost</Link>
|
||||
</SidebarMenuButton>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link to={`/library?library=tensuramap`}>
|
||||
spaltoon3api
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link to={`/library?library=tensuramap`}>homepage</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenuSub>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={location.pathname === '/users'}
|
||||
>
|
||||
<Link to={'/users'}>
|
||||
<UsersIcon />
|
||||
Users
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
@@ -109,34 +63,23 @@ const AppSidebar = () => {
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
{/* <SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={location.pathname === '/library'}
|
||||
>
|
||||
<Link to={'/library'}>
|
||||
<Library />
|
||||
Library
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={location.pathname === '/search'}
|
||||
isActive={location.pathname === '/users'}
|
||||
>
|
||||
<Link to={'/search'}>
|
||||
<Search />
|
||||
Search
|
||||
<Link to={'/users'}>
|
||||
<UsersIcon />
|
||||
Users
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem> */}
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser />
|
||||
<NavUser userName={userName} profilePictureUrl={profilePictureUrl} />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
);
|
||||
|
||||
@@ -10,14 +10,17 @@ import {
|
||||
} from './ui/dropdown-menu';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
||||
import { ChevronsUpDown, Laptop, LogOut, Moon, Sun } from 'lucide-react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
export function NavUser() {
|
||||
interface NavUserProps {
|
||||
userName?: string;
|
||||
profilePictureUrl?: string;
|
||||
}
|
||||
|
||||
export function NavUser({ userName, profilePictureUrl }: NavUserProps) {
|
||||
const { isMobile } = useSidebar();
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const userName = 'Jan';
|
||||
const profilePictureUrl = '/api/v1/users/me/profile-image';
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
@@ -25,7 +28,7 @@ export function NavUser() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
className="data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage
|
||||
@@ -34,10 +37,12 @@ export function NavUser() {
|
||||
/>
|
||||
<AvatarFallback className="rounded-lg">
|
||||
{userName
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()}
|
||||
? userName
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
: 'T'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-md leading-tight">
|
||||
@@ -61,10 +66,12 @@ export function NavUser() {
|
||||
/>
|
||||
<AvatarFallback className="rounded-lg">
|
||||
{userName
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()}
|
||||
? userName
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
: 'T'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-md leading-tight">
|
||||
@@ -75,7 +82,7 @@ export function NavUser() {
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
||||
<SidebarMenuButton className="data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground">
|
||||
{theme === 'light' ? (
|
||||
<Sun className="text-muted-foreground" />
|
||||
) : theme === 'dark' ? (
|
||||
@@ -107,13 +114,11 @@ export function NavUser() {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
// TODO logout logic
|
||||
}}
|
||||
>
|
||||
<LogOut />
|
||||
Logout
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/logout">
|
||||
<LogOut />
|
||||
Logout
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
interface TopBarProps {
|
||||
title: string;
|
||||
button?: React.ReactNode;
|
||||
}
|
||||
|
||||
const TopBar = ({ title, button }: TopBarProps) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between my-4">
|
||||
<h1 className="text-2xl font-semibold">{title}</h1>
|
||||
{button}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopBar;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
|
||||
|
||||
function Collapsible({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
||||
}
|
||||
|
||||
function CollapsibleTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
||||
return (
|
||||
<CollapsiblePrimitive.CollapsibleTrigger
|
||||
data-slot="collapsible-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CollapsibleContent({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
||||
return (
|
||||
<CollapsiblePrimitive.CollapsibleContent
|
||||
data-slot="collapsible-content"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||
@@ -446,7 +446,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
|
||||
}
|
||||
|
||||
const sidebarMenuButtonVariants = cva(
|
||||
'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-[active=true]:bg-primary data-[active=true]:font-medium data-[active=true]:text-primary-foreground data-[active=true]:shadow-none data-[active=true]:hover:bg-primary data-[active=true]:hover:text-primary-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate',
|
||||
'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[active=true]:shadow-none data-[active=true]:hover:bg-sidebar-accent data-[active=true]:hover:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
Reference in New Issue
Block a user