adad46fab1
* 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>
63 lines
1.7 KiB
TypeScript
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;
|
|
}
|