Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/frontend/public/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion apps/frontend/src/components/sidebar/ISidebar.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { RouteKey } from './SidebarEnum';
import { ElementType } from 'react';

Check warning on line 1 in apps/frontend/src/components/sidebar/ISidebar.ts

View workflow job for this annotation

GitHub Actions / quality

There should be at least one empty line between import groups
import { RouteKey } from './SidebarEnum';

export type NavItem = {
key: RouteKey;
label: string;
icon: ElementType;
sublabel?: string;
href?: string;
};

export type SidebarProps = {
active?: RouteKey;
onNavigate?: (key: RouteKey) => void;
logoUrl?: string; // default: /images/logo.png
user?: { name: string; role?: string };
};
156 changes: 66 additions & 90 deletions apps/frontend/src/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FC } from 'react';

Check warning on line 1 in apps/frontend/src/components/sidebar/sidebar.tsx

View workflow job for this annotation

GitHub Actions / quality

There should be at least one empty line between import groups
import { RouteKey } from './SidebarEnum';
import type { NavItem, SidebarProps } from './ISidebar';

import {

Check warning on line 5 in apps/frontend/src/components/sidebar/sidebar.tsx

View workflow job for this annotation

GitHub Actions / quality

`lucide-react` import should occur before import of `./SidebarEnum`
LayoutDashboard,
Info,
Users,
Expand All @@ -14,7 +14,7 @@
UserCircle2,
} from 'lucide-react';

const items: NavItem[] = [
const navItems: NavItem[] = [
{ key: RouteKey.Dashboard, label: 'Dashboard', icon: LayoutDashboard },
{ key: RouteKey.Daily, label: 'Daily', icon: Info },
{ key: RouteKey.Users, label: 'User', icon: Users },
Expand All @@ -24,104 +24,80 @@
{ key: RouteKey.Sections, label: 'Edit Sections', icon: SlidersHorizontal },
];

const Sidebar: FC<SidebarProps> = ({ active, onNavigate }) => {
const baseItem =
'w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-[background,color] duration-150 focus:outline-none ' +
'focus-visible:ring-2 focus-visible:ring-[var(--ring-color)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ring-offset)]';

const labelShow =
'pointer-events-none origin-left scale-0 opacity-0 transition-all duration-200 group-hover:scale-100 group-hover:opacity-100';

const Sidebar: FC<SidebarProps> = ({ active, onNavigate, logoUrl = '/images/logo.png', user }) => {
const userName = user?.name ?? 'Gianluca Barbieri';
const userRole = user?.role ?? 'Lernender';

const renderItem = ({ key, label, icon: Icon, sublabel }: NavItem) => {
const isActive = active === key;
return (
<button
key={key}
onClick={() => onNavigate?.(key)}
title={label}
aria-current={isActive ? 'page' : undefined}
className={
baseItem +
' ' +
(isActive
? 'bg-[var(--primary)] text-white'
: 'bg-transparent text-white/90 hover:bg-[var(--primary-hover)] hover:text-white')
}
>
<Icon className="h-5 w-5 shrink-0" />
<div className={`${labelShow} text-left`}>
<div>{label}</div>
{sublabel && <div className="text-xs text-white/80">{sublabel}</div>}
</div>
</button>
);
};

return (
<aside
className="group fixed left-0 top-0 z-50 h-screen w-16
bg-[var(--accent)] text-[var(--card-foreground)]
border-r border-[var(--border)]
transition-[width] duration-200 hover:w-64"
aria-label="Sesh Sidebar"
aria-expanded={false}
bg-[var(--primary-background)]
text-[rgb(var(--card-foreground-rgb))]
border-r border-[rgb(var(--border-rgb))]
transition-[width] duration-200 hover:w-64"
>
<div className="flex h-full flex-col">
<div className="flex h-full flex-col text-white">
{/* Brand */}
<div className="flex items-center gap-3 px-3 py-4">
<div className="grid h-10 w-10 place-items-center rounded-lg border border-[var(--border)] bg-[var(--card)]">
<span className="text-lg font-bold text-[var(--primary)]">S</span>
<button className={baseItem + ' px-3 py-4'}>
<div className="grid h-10 w-10 place-items-center rounded-lg overflow-hidden shrink-0">
<img
src={logoUrl} // /images/logo.png aus /public
alt="Sesh Logo"
className="block h-10 w-10 object-contain select-none pointer-events-none"
/>
</div>
<span
className="pointer-events-none origin-left scale-0 opacity-0
transition-all duration-200
group-hover:scale-100 group-hover:opacity-100
text-xl font-semibold"
>
Sesh
</span>
</div>

<div className="mx-3 mb-2 hidden h-px bg-[var(--border)] group-hover:block" />
<div className={`${labelShow} text-left text-xl font-bold`}>
<div>Sesh</div>
</div>
</button>

{/* Nav */}
<nav className="flex-1 space-y-1 px-2">
{items.map(({ key, label, icon: Icon }) => {
const isActive = active === key;
return (
<button
key={key}
onClick={() => onNavigate?.(key)}
className={[
'w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm',
'transition-[background,color] duration-150 focus:outline-none',
'focus-visible:ring-2 focus-visible:ring-[var(--ring-color)]',
'focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ring-offset)]',
isActive
? 'bg-[var(--primary)] text-white'
: 'bg-transparent text-[var(--card-foreground)] hover:bg-[var(--primary-hover)] hover:text-white',
].join(' ')}
aria-current={isActive ? 'page' : undefined}
aria-label={label}
>
<Icon className="h-5 w-5 shrink-0" />
<span
className="pointer-events-none origin-left scale-0 opacity-0
transition-all duration-200
group-hover:scale-100 group-hover:opacity-100"
>
{label}
</span>
</button>
);
})}
</nav>
{/* Main nav */}
<nav className="flex-1 space-y-1 px-2">{navItems.map((it) => renderItem(it))}</nav>

{/* Bottom area */}
<div className="px-2 pb-3 pt-2 space-y-2">
<button
onClick={() => onNavigate?.(RouteKey.Settings)}
className="w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm
transition-[background,color] duration-150
hover:bg-[var(--overlay-12)]
focus:outline-none focus-visible:ring-2
focus-visible:ring-[var(--ring-color)]
focus-visible:ring-offset-2
focus-visible:ring-offset-[var(--ring-offset)]"
aria-label="Settings"
>
<Settings className="h-5 w-5" />
<span
className="pointer-events-none origin-left scale-0 opacity-0
transition-all duration-200
group-hover:scale-100 group-hover:opacity-100"
>
Settings
</span>
</button>
{/* Bottom actions */}
<div className="px-2 pb-3 pt-2 space-y-1">
{/* Settings as selectable item */}
{renderItem({ key: RouteKey.Settings, label: 'Settings', icon: Settings })}

<div
className="flex items-center gap-3 rounded-lg border border-[var(--border)]
bg-[var(--card)] px-3 py-2"
aria-label="Profile"
>
<UserCircle2 className="h-8 w-8 text-[var(--accent)]" />
<div
className="pointer-events-none origin-left hidden flex-col text-left text-xs leading-tight
group-hover:flex"
>
<span className="font-medium text-[var(--card-foreground)]">Gianluca Barbieri</span>
<span className="text-[var(--muted-foreground)]">Lernender</span>
</div>
</div>
{/* Profile as normal item (kein Card) */}
{renderItem({
key: RouteKey.Profile,
label: userName,
sublabel: userRole,
icon: UserCircle2,
})}
</div>
</div>
</aside>
Expand Down
14 changes: 8 additions & 6 deletions apps/frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
@tailwind components;
@tailwind utilities;

/* Theme + Tokens mit Hex UND RGB-Kanälen, State- und Surface-Varianten */
@layer base {
/* Default = Light (per :root) + optional über data-theme steuerbar */
:root,
[data-theme='light'] {
/* Core Hex */
Expand Down Expand Up @@ -46,6 +44,10 @@
--primary-active: color-mix(in srgb, var(--primary) 85%, black);
--border-strong: color-mix(in srgb, var(--border) 80%, black);

--primary-background: rgb(
calc(34 + (255 - 34) * 0.2) calc(45 + (255 - 45) * 0.2) calc(67 + (255 - 67) * 0.2)
);

/* Surfaces (semantische Ebenen) */
--surface-1: var(--card);
--surface-2: color-mix(in srgb, var(--surface-1) 92%, var(--accent));
Expand Down Expand Up @@ -98,6 +100,10 @@
--primary-active: color-mix(in srgb, var(--primary) 80%, black);
--border-strong: color-mix(in srgb, var(--border) 80%, white);

--primary-background: rgb(
calc(20 + (255 - 20) * 0.15) calc(25 + (255 - 25) * 0.15) calc(35 + (255 - 35) * 0.15)
);

--surface-1: var(--card);
--surface-2: color-mix(in srgb, var(--surface-1) 92%, white);
--surface-3: color-mix(in srgb, var(--surface-1) 85%, white);
Expand All @@ -109,17 +115,14 @@
--overlay-12: rgb(var(--accent-rgb) / 0.12);
}

/* Basis-Anwendung */
html {
--bg: var(--background);
--fg: var(--foreground);
@apply bg-[var(--bg)] text-[var(--fg)];
}
}

/* Optionale Komponenten/Utilities */
@layer components {
/* Wiederverwendbarer Fokus-Ring */
.focus-ring {
@apply focus:outline-none focus-visible:ring-2
focus-visible:ring-[var(--ring-color)]
Expand All @@ -129,7 +132,6 @@
}

@layer utilities {
/* Bewegung reduzieren respektieren */
@media (prefers-reduced-motion: reduce) {
.no-motion {
transition: none !important;
Expand Down
Loading