feat(gui-v2): port WorkspaceSwitcher to TypeScript

Ports crewli-starter WorkspaceSwitcher into the Crewli SPA as production
TypeScript: PrimeVue Popover replaces the manual click-outside listener,
data is derived from useAuthStore/useOrganisationStore (no new store), gradient
pairs are deterministic via a new pure util with full Vitest coverage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 19:48:50 +02:00
parent 8444ea7443
commit 3720e8c3d3
3 changed files with 360 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
/**
* computeOrgGradient — deterministic gradient pair for an organisation.
*
* Maps an org `id` string to one of 8 teal-adjacent colour pairs via a
* simple djb2-style character-code hash. Same id always returns the
* same pair; no external dependencies.
*
* Palette rationale: teal / cyan / emerald / sea-green family to stay
* on-brand with Crewli's teal primary. Each tuple is [from, to] for a
* 135° linear gradient (darker "to" keeps depth on the logo square).
*/
/** Exported so tests can assert membership without hard-coding values. */
export const GRADIENT_PALETTE: [string, string][] = [
['#0d9488', '#0f766e'], // teal-600 → teal-700
['#0891b2', '#0e7490'], // cyan-600 → cyan-700
['#059669', '#047857'], // emerald-600 → emerald-700
['#10b981', '#059669'], // emerald-500 → emerald-600
['#0284c7', '#0369a1'], // sky-600 → sky-700
['#14b8a6', '#0d9488'], // teal-500 → teal-600
['#06b6d4', '#0891b2'], // cyan-500 → cyan-600
['#34d399', '#10b981'], // emerald-400 → emerald-500
]
/**
* Returns a `[fromHex, toHex]` colour pair deterministically derived
* from `id`. Handles empty string (hash stays 0; maps to palette[0]).
*/
export function computeOrgGradient(id: string): [string, string] {
// djb2-style hash: accumulate across char codes
let hash = 5381
for (let i = 0; i < id.length; i++) {
// Equivalent to hash * 33 ^ charCode, kept 32-bit safe via >>> 0
hash = ((hash << 5) + hash + id.charCodeAt(i)) >>> 0
}
const index = hash % GRADIENT_PALETTE.length
return GRADIENT_PALETTE[index]
}