Files
kandev/apps/web/lib/plugins/icons.ts
T
João Salavisa adad46fab1 feat(plugins): richer frontend SDK — page chrome, nav icons, more host UI (#1759)
* feat(plugins): richer frontend SDK — page chrome, nav icons, more host UI

Give native UI plugins the primitives to render kandev-native pages while
keeping everything configurable:

- registerRoute gains an options arg; plugin routes get a first-party
  PageTopbar title bar by default (title/icon derived from the nav item for
  the same path, or set via { topbar: { title, subtitle, icon, backHref,
  backLabel, actions } }). Pass { topbar: false } to render full-bleed and
  own the chrome.
- NavItem.icon now resolves against a curated name map (lib/plugins/icons.ts);
  unknown/missing names fall back to the puzzle glyph.
- host.ui expands to the primitives a native-feeling page needs (Dialog,
  DropdownMenu, Pagination, Sheet, Select, Tabs, Table, Tooltip, ...) plus
  first-party PageTopbar, Combobox, and TaskCreateDialog. Plugins must use
  these host instances — a bundled second copy of any Radix/portal component
  splits React context.
- host.navigate(href, { replace? }) for soft SPA navigation into native
  routes, and host.api.baseUrl for reaching first-party REST without
  re-deriving the split-origin backend URL.

The registry records each plugin's display name for derived titles and
getRoutes() entries now carry pluginId + options. Contract doc
(PLUGIN-API.md) updated to match.

* fix(plugins): expose full Sheet subcomponent family on host.ui

host.ui advertised Sheet but only exposed Sheet/Content/Header/Title, so
plugins (which must use host instances for portal/context components)
couldn't build an uncontrolled sheet — no Trigger/Close/Footer/Description.
Add the missing four for parity with the Dialog family.

* fix(plugins): clear plugin display name on unregisterPlugin

unregisterPlugin bulk-revoked every other registration but left the entry
in pluginNames, so getPluginName returned a stale name after disable and
names leaked across enable/disable cycles. Delete the name too, with a
regression test.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-07-17 18:10:49 +01:00

63 lines
1.7 KiB
TypeScript

/**
* Curated icon-name → Tabler icon map for plugin registrations.
*
* The frozen plugin contract (docs/plans/plugins/PLUGIN-API.md) only carries
* opaque string icon names (`NavItem.icon`, `PluginPageChrome.icon`) — plugins
* never hold host component values. The host maps known names onto first-party
* glyphs here; unknown or missing names fall back per surface (puzzle piece in
* the sidebar, no icon in the page topbar).
*/
import {
IconBell,
IconBolt,
IconBook,
IconBug,
IconCalendar,
IconChartBar,
IconChecklist,
IconCloud,
IconDatabase,
IconFlask,
IconGlobe,
IconMessage,
IconPuzzle,
IconRobot,
IconRocket,
IconSettings,
IconTicket,
IconUsers,
} from "@tabler/icons-react";
import type { Icon as TablerIcon } from "@tabler/icons-react";
/** Icon names a plugin may reference from `NavItem.icon` / `PluginPageChrome.icon`. */
export const PLUGIN_ICONS: Record<string, TablerIcon> = {
bell: IconBell,
bolt: IconBolt,
book: IconBook,
bug: IconBug,
calendar: IconCalendar,
chart: IconChartBar,
checklist: IconChecklist,
cloud: IconCloud,
database: IconDatabase,
flask: IconFlask,
globe: IconGlobe,
message: IconMessage,
puzzle: IconPuzzle,
robot: IconRobot,
rocket: IconRocket,
settings: IconSettings,
ticket: IconTicket,
users: IconUsers,
};
/** Strict lookup: the named icon, or undefined when the name is unknown/missing. */
export function lookupPluginIcon(name?: string): TablerIcon | undefined {
return name ? PLUGIN_ICONS[name] : undefined;
}
/** Sidebar lookup: always renders something — unknown/missing names get the puzzle glyph. */
export function resolvePluginIcon(name?: string): TablerIcon {
return lookupPluginIcon(name) ?? IconPuzzle;
}