Files
alpenwerk-hr/components/ui/SlideOver.tsx
Maximilian Stubhan ef9852b09c Phase 1: project foundation for Alpenwerk HR
Scaffolds the Next.js 16 / TypeScript strict / Tailwind v3 app per
NEXTJS_REBUILD_SUPERPROMPT.md, and implements the Foundation slice from
the Phase 1 plan:

- Corrected Supabase schema (supabase/schema.sql): org units, employees,
  history, positions, hire drafts, saved reports, audit log, reorg
  scenarios, role-based profiles, salary-masking view, RLS policies,
  auto-derivation triggers, position-number generator.
- Seed script (supabase/seed.ts): ~800 realistic Austrian employees across
  9 divisions / 16 departments / 35 teams, history, 8 open positions, and
  hr_admin/manager test accounts.
- Supabase clients (lib/supabase/*), design tokens (tailwind.config.ts),
  format/color helpers (lib/format.ts, lib/colors.ts).
- Shared UI kit (components/ui): Avatar, StatusChip, Toast, Modal,
  SlideOver, SegmentedControl, Lookup.
- Auth (login page, Server Actions) and proxy.ts (Next 16's replacement
  for middleware) guarding the authenticated route group.
- Shell (Sidebar, Topbar, NewHireButton stub) and the Dashboard page,
  reading live data via employees_directory.

Employees list/detail, hire wizard, action panels, org chart, positions,
reports, and audit log are deferred to later phases per the plan.
2026-07-13 21:44:28 +02:00

56 lines
1.8 KiB
TypeScript

"use client";
import { X } from "lucide-react";
import { useEffect, type ReactNode } from "react";
type SlideOverProps = {
open: boolean;
onClose: () => void;
title: string;
subtitle?: string;
children: ReactNode;
footer?: ReactNode;
};
export function SlideOver({ open, onClose, title, subtitle, children, footer }: SlideOverProps) {
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);
return (
<div className={`fixed inset-0 z-50 ${open ? "pointer-events-auto" : "pointer-events-none"}`} aria-hidden={!open}>
<div
className={`absolute inset-0 bg-black/40 transition-opacity ${open ? "opacity-100" : "opacity-0"}`}
onClick={onClose}
/>
<div
className={`absolute right-0 top-0 flex h-full w-full max-w-md flex-col bg-white shadow-xl transition-transform duration-200 ${
open ? "translate-x-0" : "translate-x-full"
}`}
>
<div className="flex items-start justify-between border-b border-border px-6 py-4">
<div>
<h2 className="text-lg font-bold text-ink">{title}</h2>
{subtitle && <p className="text-sm text-ink-muted">{subtitle}</p>}
</div>
<button
type="button"
onClick={onClose}
aria-label="Schließen"
className="rounded p-1 text-ink-muted hover:bg-surface"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="flex-1 overflow-y-auto px-6 py-4">{children}</div>
{footer && <div className="flex items-center justify-end gap-2 border-t border-border px-6 py-4">{footer}</div>}
</div>
</div>
);
}