feat: add shared toolbox header and appearance

This commit is contained in:
2026-07-23 00:11:13 +02:00
parent 5e206baf42
commit a058858b50
17 changed files with 982 additions and 216 deletions

View File

@@ -0,0 +1,94 @@
import type { ToolboxCatalogTheme } from "./types.js";
export const TOOLBOX_PREFERENCES_KEY =
"@add-ideas/toolbox-portal:v1:preferences" as const;
export type ToolboxThemeMode = ToolboxCatalogTheme["mode"];
export interface ToolboxPreferences {
version: 1;
pinned: string[];
order: string[];
hidden: string[];
theme: ToolboxThemeMode;
}
export interface ToolboxPreferenceStorage {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
}
export const defaultToolboxPreferences: Readonly<ToolboxPreferences> = {
version: 1,
pinned: [],
order: [],
hidden: [],
theme: "system",
};
function freshDefaults(): ToolboxPreferences {
return {
...defaultToolboxPreferences,
pinned: [],
order: [],
hidden: [],
};
}
function uniqueStrings(value: unknown, field: string): string[] {
if (
!Array.isArray(value) ||
value.some((item) => typeof item !== "string" || item.length === 0)
) {
throw new Error(`${field} must be an array of non-empty strings.`);
}
return [...new Set(value)];
}
export function parseToolboxPreferences(value: unknown): ToolboxPreferences {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error("Preferences must be a JSON object.");
}
const candidate = value as Record<string, unknown>;
if (candidate.version !== 1) {
throw new Error("Unsupported preferences version.");
}
const theme = candidate.theme;
if (theme !== "light" && theme !== "dark" && theme !== "system") {
throw new Error("Invalid theme preference.");
}
return {
version: 1,
pinned: uniqueStrings(candidate.pinned, "pinned"),
order: uniqueStrings(candidate.order, "order"),
hidden: uniqueStrings(candidate.hidden, "hidden"),
theme,
};
}
export function readToolboxPreferences(
storage?: ToolboxPreferenceStorage,
): ToolboxPreferences {
if (!storage) return freshDefaults();
const value = storage.getItem(TOOLBOX_PREFERENCES_KEY);
if (!value) return freshDefaults();
try {
return parseToolboxPreferences(JSON.parse(value));
} catch {
return freshDefaults();
}
}
export function writeToolboxPreferences(
preferences: ToolboxPreferences,
storage: ToolboxPreferenceStorage,
): void {
storage.setItem(
TOOLBOX_PREFERENCES_KEY,
JSON.stringify(parseToolboxPreferences(preferences)),
);
}