Add app sidebar

This commit is contained in:
2026-05-05 22:32:49 +02:00
parent 663c1137dd
commit c8ec394774
13 changed files with 190 additions and 144 deletions
+23 -80
View File
@@ -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>
);
+26 -21
View File
@@ -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>
+15
View File
@@ -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 }
+1 -1
View File
@@ -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: {