feat(ui): add workspace customization and simplify navigation
This commit is contained in:
@@ -5,7 +5,7 @@ import Database from "better-sqlite3";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import * as schema from "../server/db/schema";
|
||||
import { BrandingService, contrastRatio } from "../server/branding/service";
|
||||
import { BrandingService, contrastRatio, deriveAccentColor } from "../server/branding/service";
|
||||
import type { DomainActor } from "../server/domain/actor";
|
||||
import { DomainError } from "../server/domain/errors";
|
||||
import { resolveStoragePath } from "../server/files/paths";
|
||||
@@ -76,7 +76,6 @@ try {
|
||||
applicationName: "Studio Portal",
|
||||
shortName: "Studio",
|
||||
primaryColor: "#336699",
|
||||
accentColor: "#f0cc22",
|
||||
lightLogoFileId: logo.id,
|
||||
iconFileId: icon.id,
|
||||
defaultColorMode: "dark",
|
||||
@@ -84,6 +83,7 @@ try {
|
||||
});
|
||||
assert.equal(branding.applicationName, "Studio Portal");
|
||||
assert.equal(branding.primaryColor, "#336699");
|
||||
assert.equal(branding.accentColor, deriveAccentColor("#336699"), "Accent palette must derive from the single primary color");
|
||||
assert.equal(branding.darkLogoUrl, branding.lightLogoUrl, "Missing dark logo must fall back to light logo");
|
||||
assert.equal(fileService.readPublicBranding(logo.id).metadata.id, logo.id);
|
||||
assert.ok(contrastRatio(branding.primaryColor, branding.cssVariables["--poyraz-primary-foreground"]) >= 4.5);
|
||||
|
||||
@@ -6,6 +6,11 @@ const repoRoot = process.cwd();
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"));
|
||||
const globalsCss = fs.readFileSync(path.join(repoRoot, "app/globals.css"), "utf8");
|
||||
const appShell = fs.readFileSync(path.join(repoRoot, "components/layout/app-shell.tsx"), "utf8");
|
||||
const rootLayout = fs.readFileSync(path.join(repoRoot, "app/layout.tsx"), "utf8");
|
||||
const settingsPage = fs.readFileSync(
|
||||
path.join(repoRoot, "app/(dashboard)/settings/page.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.match(packageJson.dependencies["poyraz-ui"] ?? "", /^\^?3\./, "poyraz-ui must use major v3");
|
||||
assert.match(
|
||||
@@ -22,7 +27,6 @@ for (const component of [
|
||||
"SidebarProvider",
|
||||
"SidebarPanel",
|
||||
"SidebarHeader",
|
||||
"SidebarBranding",
|
||||
"SidebarContent",
|
||||
"SidebarMenu",
|
||||
"SidebarMenuItem",
|
||||
@@ -33,6 +37,58 @@ for (const component of [
|
||||
assert.ok(appShell.includes(component), `App shell must compose the Poyraz ${component} organism`);
|
||||
}
|
||||
|
||||
assert.ok(appShell.includes("WorkspaceLogo"), "Sidebar header must render the workspace logo");
|
||||
assert.ok(
|
||||
appShell.includes('<SidebarProvider variant="default">'),
|
||||
"Desktop sidebar must use the fixed-width Poyraz default variant",
|
||||
);
|
||||
assert.ok(
|
||||
!appShell.includes('<SidebarProvider variant="collapsible">'),
|
||||
"Desktop sidebar must not be collapsible",
|
||||
);
|
||||
assert.ok(!appShell.includes("SidebarRail"), "Desktop sidebar must not expose a collapse rail");
|
||||
|
||||
assert.doesNotMatch(
|
||||
appShell,
|
||||
/<DropdownMenuItem(?=[^>]*\basChild\b)(?=[^>]*\bmedia=)[^>]*>/s,
|
||||
"Poyraz DropdownMenuItem must not combine asChild with rich media because Radix cannot inject accessibility props into the generated Fragment",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
appShell,
|
||||
/<DropdownMenuItem[^>]*\binteractiveMotion=/s,
|
||||
"Poyraz UI 3.0.2 leaks DropdownMenuItem interactiveMotion to the DOM",
|
||||
);
|
||||
|
||||
assert.match(settingsPage, /\bRadioGroup\b/, "Settings must use the Poyraz RadioGroup for theme selection");
|
||||
for (const workspaceControl of [
|
||||
'name="workspaceName"',
|
||||
'name="logo"',
|
||||
'name="primaryColor"',
|
||||
]) {
|
||||
assert.ok(
|
||||
settingsPage.includes(workspaceControl),
|
||||
`Settings must expose workspace branding control ${workspaceControl}`,
|
||||
);
|
||||
}
|
||||
for (const colorMode of ["light", "dark", "system"]) {
|
||||
assert.ok(
|
||||
settingsPage.includes(`value: "${colorMode}"`),
|
||||
`Settings must expose the ${colorMode} color mode`,
|
||||
);
|
||||
}
|
||||
assert.ok(
|
||||
rootLayout.includes("COLOR_MODE_COOKIE"),
|
||||
"Root layout must resolve the persisted color mode before rendering",
|
||||
);
|
||||
assert.ok(
|
||||
appShell.includes("ColorModeSync"),
|
||||
"Authenticated shells must synchronize the database-backed color mode",
|
||||
);
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(repoRoot, "server/settings/preferences.ts")),
|
||||
"Missing database-backed user preferences service",
|
||||
);
|
||||
|
||||
const requiredSystemCompositions = [
|
||||
"components/system/page-header.tsx",
|
||||
"components/system/feedback-state.tsx",
|
||||
|
||||
Reference in New Issue
Block a user