Files
HealthLog/src/components/command-palette/palette-store.ts
T
Marc-André Bombeck 715b733164 feat(shell): add a command palette for pages, settings, modules and actions
Cmd/Ctrl+K or the search control at the end of the top bar opens a
combobox over a client-side index built from the lists the navigation
already renders: pages, Insights pages, every Settings section and card
(deep-linked by a new anchor prop on SettingsCardHeader), the Appearance
sub-pages, module switches and a few actions. Entries follow the module
map, the Coach capability, the admin role and a shared record's grant;
inside somebody else's record no cards, switches or backup are offered.

Ranking is prefix, then word start, then substring, then a small edit
distance, then subsequence, with a short multilingual synonym list. The
empty state shows recent places (per account, in localStorage) and the
main actions. The palette is its own lazily loaded chunk; the top bar
shows a search field where it has room and a magnifier otherwise. The
module switch mutation moves into a shared hook used by the Modules hub
and the palette.
2026-10-09 16:35:21 +02:00

43 lines
1.1 KiB
TypeScript

"use client";
import { useSyncExternalStore } from "react";
/**
* Whether the command palette is open, reachable from anywhere: the top-bar
* button, Cmd/Ctrl+K in the shortcut handler, and the palette itself. The
* palette's code is a separate chunk (`command-palette.lazy.tsx`); this file
* is the few lines every page carries.
*/
let open = false;
const listeners = new Set<() => void>();
export function setCommandPaletteOpen(next: boolean): void {
if (open === next) return;
open = next;
for (const listener of listeners) listener();
}
export function openCommandPalette(): void {
setCommandPaletteOpen(true);
}
function subscribe(listener: () => void): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}
export function useCommandPaletteOpen(): boolean {
return useSyncExternalStore(
subscribe,
() => open,
() => false,
);
}
/** Start loading the palette's chunk ahead of the click (hover, focus). */
export function preloadCommandPalette(): void {
void import("./command-palette").catch(() => {
// The click loads it again and reports a failure there.
});
}