@ebay-dt/bonfire-react
v0.1.0
Published
React components for Bonfire.
Keywords
Readme
@ebay-dt/bonfire-react
Opinionated React components for Bonfire.
Application color tokens
AppHeader, GitHubCliLoginPage, and PrototypeDashboard share one inherited
application color-token contract. Their CSS Modules consume those tokens;
applications should import the package defaults once from their root layout or
stylesheet entry point:
import "@ebay-dt/bonfire-react/styles/defaults.css";The defaults use light-dark() without imposing a global color scheme. A host
that consumes the tokens outside the packaged components should establish the
schemes it supports, for example:
:root {
color-scheme: light dark;
}Token contract
The namespaces are default, danger, and information. Each namespace has
the same 12 semantic roles:
| Group | Role | Default namespace | Danger namespace |
| ---------- | ---------- | ------------------------------------------ | ----------------------------------------- |
| Background | primary | --app-color-default-background-primary | --app-color-danger-background-primary |
| Background | secondary | --app-color-default-background-secondary | --app-color-danger-background-secondary |
| Background | tertiary | --app-color-default-background-tertiary | --app-color-danger-background-tertiary |
| Foreground | primary | --app-color-default-foreground-primary | --app-color-danger-foreground-primary |
| Foreground | secondary | --app-color-default-foreground-secondary | --app-color-danger-foreground-secondary |
| Foreground | tertiary | --app-color-default-foreground-tertiary | --app-color-danger-foreground-tertiary |
| Border | default | --app-color-default-border | --app-color-danger-border |
| Border | muted | --app-color-default-border-muted | --app-color-danger-border-muted |
| Border | strong | --app-color-default-border-strong | --app-color-danger-border-strong |
| Action | background | --app-color-default-action-background | --app-color-danger-action-background |
| Action | foreground | --app-color-default-action-foreground | --app-color-danger-action-foreground |
| Action | border | --app-color-default-action-border | --app-color-danger-action-border |
Use the roles according to their semantics rather than according to a particular component selector:
- Background primary is a canvas, secondary is an elevated surface, and tertiary is an inset or subtle surface.
- Foreground primary is principal content, secondary is supporting content, and tertiary is disabled or deliberately low-emphasis content. The package defaults for tertiary foregrounds are not intended for normal body text.
- The unsuffixed border is a standard structural boundary, muted is a subtle separator, and strong is an emphasized boundary.
- Action colors are for interactive or accent treatment. Action borders do not replace ordinary panel or separator borders.
The information namespace uses the same role suffixes listed above, prefixed
with --app-color-information- (for example,
--app-color-information-background-primary). Its blue defaults style
informational banners, including the duplicate-source context and deployment
notes, using primary background, primary foreground, and strong border tokens.
The default, danger, and information segments communicate semantic intent;
they are not light/dark theme selectors.
Overriding tokens
The package defaults are in a low-priority bonfire.defaults cascade layer
and use a zero-specificity :where(:root) selector. Ordinary unlayered consumer
CSS can override them without !important or unusually specific selectors:
:root {
--app-color-default-background-primary: light-dark(#f7f3ff, #17121f);
--app-color-default-foreground-primary: light-dark(#24172f, #f8efff);
--app-color-default-border: light-dark(#ded2e8, #493d54);
--app-color-default-action-background: light-dark(#6f2da8, #d8a7ff);
--app-color-default-action-foreground: light-dark(#ffffff, #24172f);
--app-color-default-action-border: var(--app-color-default-action-background);
}Custom properties inherit, so a non-portaled component can receive a local variant from an ordinary wrapper:
.loginTheme {
--app-color-default-background-secondary: light-dark(#fffdf7, #211e17);
--app-color-danger-background-primary: light-dark(#fff1ef, #381d20);
}<div className="loginTheme">
<GitHubCliLoginPage {...model} />
</div>AppHeader renders its account popover through a portal. When it is inside a
PrototypeEnvironment, the popover uses the provider-owned portal container so
it inherits the active prototype scheme and variables. Outside that environment,
the popover is portaled to body; overrides intended to style both the header
and popover must then be placed on a shared ancestor such as html or body.
Importing @ebay-dt/bonfire-react/styles/defaults.css explicitly is the supported
way to establish the defaults for packaged components and host-owned UI.
Migrating legacy component tokens
The component-specific properties have been removed and are not compatibility aliases. Existing consumers can migrate them as follows:
| Legacy property | Shared replacement |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| --bf-header-background | --app-color-default-background-primary |
| --bf-header-surface | --app-color-default-background-secondary |
| --bf-header-foreground | --app-color-default-foreground-primary |
| --bf-header-muted | --app-color-default-foreground-secondary |
| --bf-header-border | --app-color-default-border |
| --bf-header-focus | --app-color-default-action-border |
| --bf-auth-background | --app-color-default-background-primary |
| --bf-auth-surface | --app-color-default-background-secondary |
| --bf-auth-foreground | --app-color-default-foreground-primary |
| --bf-auth-muted | --app-color-default-foreground-secondary |
| --bf-auth-border | --app-color-default-border |
| --bf-auth-accent | --app-color-default-action-background and --app-color-default-action-border |
| --bf-auth-accent-foreground | --app-color-default-action-foreground |
| --bf-auth-danger | --app-color-danger-foreground-primary, --app-color-danger-border, and --app-color-danger-background-tertiary |
| --bf-auth-danger-surface | --app-color-danger-background-primary |
| --bf-auth-code-background | --app-color-default-background-tertiary |
The login status surface now uses
--app-color-default-background-tertiary. Consumers that previously relied on
--bf-auth-background for both the page canvas and its derived status surface
can override the two roles independently.
App header
import { AppHeader } from "@ebay-dt/bonfire-react";
<AppHeader applicationName="My App" user={user} />;AppHeader renders an authenticated dashboard header with an account popover
and POST sign-out form. Passing user={null} renders nothing. The home and
logout destinations default to / and /auth/logout; use homeUrl and
logoutUrl to override them.
The component imports its CSS Module internally. Its block size follows the
package layout contract, with a 2.5rem component fallback. Next.js consumers
can apply that contract without knowing its internal attribute or CSS property
names:
const { htmlProps, headerProps } = await bonfire.auth.getAppLayoutModel();
return (
<html lang="en" {...htmlProps}>
<body>
<AppHeader {...headerProps} />
{children}
</body>
</html>
);AppHeader owns the user: null rendering decision. Consumers using another
server integration can pass an identity directly; the header's normal document
flow generally requires no content-spacing adjustment. htmlProps belongs on
the root <html> element and headerProps belongs on <AppHeader>; keep both
values from the same getAppLayoutModel() result. It uses the shared
application color-token contract above.
Prototype themes
PrototypeEnvironment owns the generic theme boundary for a prototype. It
sets data-theme and data-color-scheme on the boundary, and exposes the
selection through usePrototypeEnvironment or usePrototypeTheme. Next hosts
should pass the validated prototype.themeRegistry from the route model rather
than importing application configuration into client code:
import {
PrototypeEnvironment,
usePrototypeTheme,
} from "@ebay-dt/bonfire-react";
function PrototypeContent() {
const { theme, colorScheme } = usePrototypeTheme();
return <p>{`${theme} / ${colorScheme}`}</p>;
}
<PrototypeEnvironment
prototypeUrl="/alice/example"
theme={prototype.theme}
themeRegistry={prototype.themeRegistry}
>
<PrototypeContent />
</PrototypeEnvironment>;Instance CSS owns the values and targets the public attributes, for example:
[data-theme="alpha"][data-color-scheme="light"] {
--prototype-background: #f5f9ff;
}
[data-theme="alpha"][data-color-scheme="dark"] {
--prototype-background: #10243d;
}
[data-theme="alpha"][data-color-scheme="system"] {
--prototype-background: #f5f9ff;
}
@media (prefers-color-scheme: dark) {
[data-theme="alpha"][data-color-scheme="system"] {
--prototype-background: #10243d;
}
}system remains the value of data-color-scheme; the boundary establishes
color-scheme: light dark so native controls and light-dark() resolve from
the visitor's system preference. The provider exposes both colorScheme
(which may be system) and resolvedColorScheme (always light or dark).
PrototypeThemeSwitcher is the generic registry-driven switcher used by
AppHeader. It lists registered themes and System/Light/Dark options, disabling
values that the active prototype policy or active theme does not support. Hosts
can build custom controls with the same hooks. usePrototypePortalContainer
allows portaled host UI to remain inside the themed boundary.
Explicit Light/Dark visitor choices are stored in localStorage, scoped by
prototype URL and invalidated when the theme policy or registry fingerprint
changes. System is an in-memory selection and clears the stored override; after
a reload, the configured prototype default is restored. The provider includes a
nonce-aware pre-hydration restoration script to apply a stored explicit choice
before React hydrates. Hosts using a Content Security Policy should pass the
server-provided nonce to PrototypeEnvironment so the inline restoration script
is permitted.
GitHub CLI login page
import { GitHubCliLoginPage } from "@ebay-dt/bonfire-react";
<GitHubCliLoginPage {...model} />;GitHubCliLoginPage includes accessible device-login status polling, activation
instructions, and failure guidance. It imports its CSS Module internally, so
consumers do not need a global stylesheet. It uses the shared default and danger
color roles described above.
Prototype dashboard
import { PrototypeDashboard } from "@ebay-dt/bonfire-react";
<PrototypeDashboard {...dashboardModel} user={user} />;PrototypeDashboard renders a PrototypeDashboardModel from
@ebay-dt/bonfire-core. It provides labeled native GET filters, a responsive catalog
of prototype cards, deterministic dates, internal prototype links, and safe
external preview links. Filter controls submit their native GET form when
changed. When rendered with an authenticated user inside LifecycleProvider,
it also renders the lifecycle-owned create and duplicate controls. Edit and
delete actions appear in a card's overflow menu only when the authenticated
user owns that prototype. Without lifecycle context it remains read-only. Import
@ebay-dt/bonfire-react/styles/defaults.css once from the application root
before rendering it. It owns its scoped CSS Module with support for narrow
layouts, light/dark color preferences, visible focus, and reduced motion.
Agent controls
PrototypeDashboard accepts optional agentActions bindings. When supplied,
owner cards render icon buttons with Cursor and Claude Code tooltips; other
users' cards and unauthenticated cards remain free of agent controls.
AppHeader accepts the same bindings when rendered inside LifecycleProvider
and renders text menu rows for an active owned prototype. It uses the lifecycle
session's validated reference and closes the account popover when an agent action
is clicked.
The bindings are framework-neutral functions, so a Next host can pass Server Action references without making this package depend on Next.js:
import {
PrototypeAgentControls,
PrototypeDashboard,
type PrototypeAgentActionBindings,
} from "@ebay-dt/bonfire-react";
const agentActions: PrototypeAgentActionBindings = {
openInCursor,
openInClaudeCode,
};
<PrototypeDashboard
{...dashboardModel}
user={user}
agentActions={agentActions}
/>;
<AppHeader applicationName="My App" user={user} agentActions={agentActions} />;For a custom surface, render PrototypeAgentControls directly with a
validated prototypeRef. PrototypeOnboardingCard provides the same agent
launch behavior in the generated new-prototype surface:
import { PrototypeOnboardingCard } from "@ebay-dt/bonfire-react";
<PrototypeOnboardingCard
title="Checkout prototype"
description={null}
prototypeRef={{ login: "alice", slug: "checkout-prototype" }}
/>;The card reads optional agent bindings from PrototypeEnvironment; they can
also be supplied directly through its actions prop. PrototypeEnvironment
accepts the same agentActions bindings as AppHeader, which lets a host wire
the generated prototype without embedding application-specific imports in the
scaffold.
PrototypeAgentControls and the card own pending state, command-install help,
Claude Code terminal selection, launch-failure retry/change-terminal flows, and
safe typed error messaging. They never access the filesystem or process APIs
and do not write preferences from the browser. Hosts provide
the action functions; the Next and Node documentation describes their
server-only composition.
Lifecycle controller
The framework-neutral lifecycle controller and its React adapter are available
from @ebay-dt/bonfire-react/lifecycle. When the lifecycle provider is mounted,
AppHeader renders the active prototype indicator and compact save/discard
actions in the global popover. When the active prototype has a persisted preview
URL, it also renders a View deployment link. When deployment capability is
configured, it renders the owner-only Deploy prototype action alongside the
same lifecycle controls:
import { AppHeader } from "@ebay-dt/bonfire-react";
<AppHeader applicationName="My App" user={user} />;Without a mounted lifecycle provider, AppHeader renders the regular account
menu without lifecycle controls.
import {
LifecycleProvider,
createLifecycleController,
} from "@ebay-dt/bonfire-react/lifecycle";
const controller = createLifecycleController({ actions, navigation });
<LifecycleProvider controller={controller}>{children}</LifecycleProvider>;The dashboard and header consume lifecycle context directly. For an active
owned prototype, render PrototypeEditingControls inside the provider to
expose the package-owned saved/unsaved status and save/discard workflow. Save
creates a local path-scoped checkpoint; discard restores tracked files and
removes untracked files within that prototype path:
import { PrototypeEditingControls } from "@ebay-dt/bonfire-react/lifecycle";
<PrototypeEditingControls />;Deployment availability is optional; without a mounted lifecycle provider or without a persisted preview URL, the regular header remains unchanged and no view link is rendered. The deployment dialog shows provider ready state and partial-success/failure copy, but does not poll deployment IDs.
The controller accepts typed create, duplicate, edit, and delete command ports
and exposes derived view state through useLifecycleViewState() or
useLifecycleState(). Use useOptionalLifecycle() for components that can
render with or without the provider. Supply a route adapter, the authenticated
user, and a dirty-observation adapter to track an owned prototype's clean/dirty
state. The browser adapter provides injectable EventSource parsing, bounded
retry, and cleanup without importing React or Next.js. Navigation is requested
through the supplied adapter; the controller does not perform browser
navigation itself. Its optional refresh() capability is host-provided and is
invoked once after successful edit/delete mutations. Stop the controller when
its host lifecycle ends.
PrototypeFormDialog supports create, duplicate, and edit modes. Edit is
prefilled from the supplied summary and preserves the existing URL/slug. The
form validates title and description locally, presents typed failures without
closing, prevents duplicate submissions while an operation is running, and
closes only after matching success. PrototypeDeleteDialog requires an
explicit destructive confirmation and warns that uncommitted files are also
removed. The provider also installs the package-owned navigation guard: dirty
internal links, browser history, and reload/close attempts are protected, while
leave choices are resolved through the controller. The controller and forms
are safe to restart after development effect cleanup; the React package owns
the XState implementation while the public command and view-state contracts
remain framework-neutral.
