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
26 changes: 12 additions & 14 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
@tailwind utilities;

:root {
--shell-background: #f4efe7;
--shell-background: #f8fafc;
--shell-surface: rgba(255, 255, 255, 0.9);
--shell-panel: #ffffff;
--shell-muted: #f8f4ed;
--shell-border: #d8cfc1;
--shell-ink: #16212b;
--shell-subtle: #5d6873;
--shell-accent: #0f5e5b;
--shell-accent-soft: #dce9e8;
--shell-warm: #8a6249;
--shell-muted: #eef2f7;
--shell-border: #d7dde5;
--shell-ink: #0f172a;
--shell-subtle: #475569;
--shell-accent: #0f766e;
--shell-accent-soft: #ccfbf1;
--shell-warm: #92400e;
}

html.dark,
Expand All @@ -38,17 +38,15 @@ html {
body {
min-height: 100vh;
color: var(--shell-ink);
background: radial-gradient(circle at top left, rgba(15, 94, 91, 0.08), transparent 32%),
radial-gradient(circle at top right, rgba(138, 98, 73, 0.08), transparent 28%),
linear-gradient(180deg, rgba(255, 255, 255, 0.42), transparent 22%), var(--shell-background);
background: var(--shell-background);
}

body,
input,
textarea,
button,
select {
font-family: "Satoshi", "Avenir Next", "Segoe UI", sans-serif;
font-family: Inter, "Segoe UI", Arial, sans-serif;
font-feature-settings: "ss01" on, "ss02" on;
}

Expand All @@ -58,8 +56,8 @@ h3,
h4,
h5,
h6 {
font-family: Georgia, "Times New Roman", serif;
letter-spacing: -0.03em;
font-family: inherit;
letter-spacing: 0;
}

::selection {
Expand Down
3 changes: 1 addition & 2 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import "@/css/satoshi.css";
import "@/css/style.css";
import "jsvectormap/dist/jsvectormap.css";
import "flatpickr/dist/flatpickr.min.css";
Expand Down Expand Up @@ -26,7 +25,7 @@ export default function RootLayout({
<TranscriptionProvider>
<HumanEvaluationProvider>
<WorkspaceProvider>
<div id="root" className="min-h-screen dark:text-bodydark">
<div id="root" className="min-h-screen text-slate-900 dark:text-slate-100">
{children}
</div>
</WorkspaceProvider>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/workspace/alumni/record/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const AlumniRecordPage = () => {
"Learner-record previews on the alumni home route come from the backend timeline contract rather than frontend fixtures.",
]}
/>
<div className="mt-8">
<div className="mt-6">
<WorkspaceLifelongNetworkPanel />
</div>
</DefaultLayout>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/workspace/principal/school-health/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const PrincipalSchoolHealthPage = () => {
"The goal is to anchor leadership work in school context, not to create a new detached reporting tool.",
]}
/>
<div className="mt-8">
<div className="mt-6">
<WorkspaceGovernedIntelligencePanel workspaceRole="principal" />
</div>
</DefaultLayout>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/workspace/supervisor/briefings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const SupervisorBriefingsPage = () => {
"Trust, freshness, and deep-link metadata now travel with the underlying report payloads as well.",
]}
/>
<div className="mt-8">
<div className="mt-6">
<WorkspaceGovernedIntelligencePanel workspaceRole="supervisor" />
</div>
</DefaultLayout>
Expand Down
8 changes: 5 additions & 3 deletions frontend/components/Header/ContextSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { useWorkspace } from "@/components/Workspace/WorkspaceProvider";
import { useId } from "react";

const inputClassName =
"mt-1 w-full rounded-xl border border-stone-200 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100";
"mt-1 min-h-9 w-full rounded-lg border border-stone-200 bg-white px-3 py-1.5 text-sm text-slate-900 shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100";

const ContextSwitcher = () => {
const id = useId();
const { currentContext, contextOptions, isLoading, setContext } = useWorkspace();

return (
<div className="min-w-[15rem]">
<div className="min-w-0 flex-1 sm:min-w-[14rem] lg:min-w-[15rem]">
<label
htmlFor={id}
className="block text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500"
Expand All @@ -31,7 +31,9 @@ const ContextSwitcher = () => {
</option>
))}
</select>
<p className="mt-1 text-xs text-slate-500 dark:text-slate-400">{currentContext.note}</p>
<p className="mt-1 max-w-full truncate text-xs text-slate-500 dark:text-slate-400">
{currentContext.note}
</p>
</div>
);
};
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/Header/RoleSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { useRouter } from "next/navigation";
import { startTransition, useId } from "react";

const inputClassName =
"mt-1 w-full rounded-xl border border-stone-200 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100";
"mt-1 min-h-9 w-full rounded-lg border border-stone-200 bg-white px-3 py-1.5 text-sm text-slate-900 shadow-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100";

const RoleSwitcher = () => {
const id = useId();
const router = useRouter();
const { availableRoles, currentRole, isLoading, setRole } = useWorkspace();

return (
<div className="min-w-[12rem]">
<div className="min-w-[10rem]">
<label
htmlFor={id}
className="block text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500"
Expand Down
19 changes: 14 additions & 5 deletions frontend/components/Layouts/DefaultLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import type { Metadata } from "next";
import type React from "react";
import { useRef, useState } from "react";

const WORKSPACE_SHELL_METRICS_CLASS =
"[--workspace-header-offset:9.75rem] [--workspace-sidebar-width:15.5rem] sm:[--workspace-header-offset:8.75rem] lg:[--workspace-header-offset:4.5rem]";
const PUBLIC_SHELL_METRICS_CLASS = "[--workspace-header-offset:5rem]";

export default function DefaultLayout({
children,
metadata,
Expand All @@ -19,7 +23,12 @@ export default function DefaultLayout({
const isWorkspaceShell = variant === "workspace";

return (
<main id="main-content" className="min-h-screen text-slate-900 dark:text-slate-100">
<main
id="main-content"
className={`min-h-screen overflow-x-hidden text-slate-900 dark:text-slate-100 ${
isWorkspaceShell ? WORKSPACE_SHELL_METRICS_CLASS : PUBLIC_SHELL_METRICS_CLASS
}`}
>
<div className="fixed left-0 top-0 z-50 w-full">
<Header
sidebarOpen={sidebarOpen}
Expand All @@ -28,7 +37,7 @@ export default function DefaultLayout({
variant={variant}
/>
</div>
<div className="pt-[72px]">
<div className="pt-[var(--workspace-header-offset)]">
{isWorkspaceShell && (
<Sidebar
sidebarOpen={sidebarOpen}
Expand All @@ -37,14 +46,14 @@ export default function DefaultLayout({
/>
)}
<div
className={`min-h-[calc(100vh-72px)] transition-[margin] duration-200 ${
isWorkspaceShell && sidebarOpen ? "lg:ml-[18rem]" : ""
className={`min-h-[calc(100vh_-_var(--workspace-header-offset))] min-w-0 transition-[margin] duration-200 ${
isWorkspaceShell && sidebarOpen ? "lg:ml-[var(--workspace-sidebar-width)]" : ""
}`}
>
<div
className={
isWorkspaceShell
? "px-4 py-6 md:px-8 md:py-8"
? "mx-auto w-full max-w-[112rem] min-w-0 px-3 py-4 sm:px-4 md:px-5 md:py-5 xl:px-6"
: "mx-auto max-w-7xl px-6 py-8 md:px-8 md:py-10"
}
>
Expand Down
30 changes: 19 additions & 11 deletions frontend/components/Sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import type { WorkspaceNavItem } from "@/utils/workspace";
import type { WorkspaceNavItem, WorkspaceNavMatchStrategy } from "@/utils/workspace";
import Link from "next/link";
import { usePathname } from "next/navigation";
import React from "react";

function routeMatches(pathname: string, route: string, exactMatch = false) {
const DEFAULT_NAV_MATCH_STRATEGY: WorkspaceNavMatchStrategy = "exact";

function routeMatches(pathname: string, route: string, strategy: WorkspaceNavMatchStrategy) {
if (strategy === "none") {
return false;
}

if (route === "/") {
return pathname === route;
}

if (exactMatch) {
if (strategy === "exact") {
return pathname === route;
}

Expand All @@ -18,36 +24,38 @@ function routeMatches(pathname: string, route: string, exactMatch = false) {
const SidebarItem = ({ item }: { item: WorkspaceNavItem }) => {
const pathname = usePathname();
const isItemActive =
routeMatches(pathname, item.route, item.exactMatch) ||
(item.matchRoutes ?? []).some((route) => routeMatches(pathname, route));
routeMatches(pathname, item.route, item.matchStrategy ?? DEFAULT_NAV_MATCH_STRATEGY) ||
(item.matchRoutes ?? []).some((matchRoute) =>
routeMatches(pathname, matchRoute.route, matchRoute.strategy),
);
const Icon = item.icon;

return (
<li>
<Link
href={item.route}
aria-current={isItemActive ? "page" : undefined}
className={`group flex items-start gap-3 rounded-[1.25rem] border px-3 py-3 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 ${
className={`group flex min-h-11 items-start gap-2 rounded-lg border px-2.5 py-2 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 ${
isItemActive
? "border-teal-700 bg-teal-700 text-white shadow-sm"
: "border-transparent bg-transparent text-slate-700 hover:border-stone-200 hover:bg-white/80 dark:text-slate-200 dark:hover:border-slate-700 dark:hover:bg-slate-900/70"
}`}
>
<span
className={`mt-0.5 flex h-10 w-10 items-center justify-center rounded-xl border transition ${
className={`mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg border transition ${
isItemActive
? "border-white/20 bg-white/10 text-white"
: "border-stone-200 bg-white text-slate-700 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-200"
}`}
>
<Icon className="h-4.5 w-4.5" aria-hidden="true" />
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2">
<span className="font-semibold">{item.label}</span>
<span className="text-sm font-semibold leading-5">{item.label}</span>
{item.badge && (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] ${
className={`rounded-full px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.12em] ${
isItemActive
? "bg-white/10 text-white"
: "bg-stone-100 text-slate-500 dark:bg-slate-800 dark:text-slate-300"
Expand All @@ -58,7 +66,7 @@ const SidebarItem = ({ item }: { item: WorkspaceNavItem }) => {
)}
</span>
<span
className={`mt-1 block text-xs leading-6 ${
className={`mt-0.5 block break-words text-[11px] leading-4 ${
isItemActive ? "text-teal-50" : "text-slate-500 dark:text-slate-400"
}`}
>
Expand Down
20 changes: 10 additions & 10 deletions frontend/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ const SidebarHoverCard = ({
<div className="group/hover-card relative inline-flex">
<button
type="button"
className="inline-flex items-center rounded-full border border-stone-200 bg-white px-2.5 py-1 text-[11px] font-semibold text-slate-700 transition hover:border-teal-200 hover:bg-teal-50/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-200 dark:hover:border-teal-800 dark:hover:bg-slate-900"
className="inline-flex min-h-7 items-center rounded-full border border-stone-200 bg-white px-2.5 py-1 text-[11px] font-semibold text-slate-700 transition hover:border-teal-200 hover:bg-teal-50/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-200 dark:hover:border-teal-800 dark:hover:bg-slate-900"
>
<span>{triggerLabel}</span>
</button>

<div
className={`invisible absolute z-50 w-[16rem] scale-95 rounded-[1.25rem] border border-stone-200 bg-white/95 p-4 opacity-0 shadow-xl transition duration-200 group-hover/hover-card:visible group-hover/hover-card:scale-100 group-hover/hover-card:opacity-100 group-focus-within/hover-card:visible group-focus-within/hover-card:scale-100 group-focus-within/hover-card:opacity-100 dark:border-slate-700 dark:bg-slate-950/95 ${hoverCardPositionStyles[position]}`}
className={`invisible absolute z-50 w-[15rem] scale-95 rounded-lg border border-stone-200 bg-white/95 p-3 opacity-0 shadow-xl transition duration-200 group-hover/hover-card:visible group-hover/hover-card:scale-100 group-hover/hover-card:opacity-100 group-focus-within/hover-card:visible group-focus-within/hover-card:scale-100 group-focus-within/hover-card:opacity-100 dark:border-slate-700 dark:bg-slate-950/95 ${hoverCardPositionStyles[position]}`}
>
<p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500 dark:text-slate-400">
{eyebrow}
</p>
<p className="mt-2 text-base font-semibold text-slate-900 dark:text-slate-50">{title}</p>
<p className="mt-2 text-sm leading-7 text-slate-600 dark:text-slate-300">{description}</p>
<p className="mt-2 text-sm font-semibold text-slate-900 dark:text-slate-50">{title}</p>
<p className="mt-2 text-xs leading-5 text-slate-600 dark:text-slate-300">{description}</p>
{children ? <div className="mt-4">{children}</div> : null}
</div>
</div>
Expand All @@ -78,22 +78,22 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen, exceptionRef }: SidebarProps) =>

<aside
id="sidebar"
className={`fixed left-0 top-[72px] z-40 flex h-[calc(100vh-72px)] w-[18rem] flex-col border-r border-stone-200 bg-stone-50/95 px-4 py-5 shadow-sm transition-transform duration-200 dark:border-slate-800 dark:bg-slate-950/90 ${
className={`fixed left-0 top-[var(--workspace-header-offset)] z-40 flex h-[calc(100vh_-_var(--workspace-header-offset))] w-[var(--workspace-sidebar-width)] flex-col border-r border-stone-200 bg-stone-50/95 px-3 py-3 shadow-sm transition-transform duration-200 dark:border-slate-800 dark:bg-slate-950/90 ${
sidebarOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
<div className="rounded-[1.25rem] border border-stone-200 bg-white/90 p-3 shadow-sm dark:border-slate-700 dark:bg-slate-900/80">
<div className="rounded-lg border border-stone-200 bg-white/90 p-2.5 shadow-sm dark:border-slate-700 dark:bg-slate-900/80">
<p className="text-[10px] font-semibold uppercase tracking-[0.24em] text-slate-500 dark:text-slate-400">
{roleConfig.workspaceTitle}
</p>
<div className="mt-2 flex items-center gap-2">
<div className="min-w-0">
<h2 className="truncate text-[2rem] font-semibold leading-none text-slate-900 dark:text-slate-50">
<h2 className="truncate text-lg font-semibold leading-6 text-slate-900 dark:text-slate-50">
{roleConfig.label}
</h2>
</div>
</div>
<div className="mt-3 flex flex-wrap gap-1.5">
<div className="mt-2.5 flex flex-wrap gap-1.5">
<SidebarHoverCard
description={roleConfig.publicPitch}
eyebrow={roleConfig.workspaceTitle}
Expand Down Expand Up @@ -129,8 +129,8 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen, exceptionRef }: SidebarProps) =>
</div>
</div>

<nav aria-label={`${roleConfig.label} navigation`} className="mt-4 flex-1 overflow-y-auto">
<ul className="space-y-2">
<nav aria-label={`${roleConfig.label} navigation`} className="mt-3 flex-1 overflow-y-auto">
<ul className="space-y-1.5">
{roleConfig.navigation.map((item) => (
<SidebarItem key={`${roleConfig.key}-${item.label}`} item={item} />
))}
Expand Down
Loading
Loading