@nyuro/app-switcher
v0.3.0
Published
The shared Apps switcher and not-entitled view (spec A4 §6): React, no framework CSS, themed by --nyuro-switcher-* custom properties.
Readme
@nyuro/app-switcher
The shared Apps switcher and the not-entitled view (spec
A4 §6). React 18 or 19, no framework CSS: one
plain stylesheet themed by --nyuro-switcher-* custom properties. Both components render from
props only; they never call an API.
import { AppSwitcher, NotEntitledView, type SwitcherProduct } from '@nyuro/app-switcher'
import '@nyuro/app-switcher/styles.css'Product data
Components take products as SwitcherProduct values:
interface SwitcherProduct {
slug: string // as in the token's `products` claim
name: string // "AI App Builder"
url: string // where the app opens
pricingUrl: string // https://nyuro.ai/pricing?product=<slug>
status: 'live' | 'early_access' | 'coming_later'
icon: 'router' | 'flow' | 'employees' | 'biab'
}Apps map their @nyuro/products catalog entries to SwitcherProduct (one small function in the
app). This package does not depend on the catalog, so a catalog release never forces a switcher
release.
<AppSwitcher>
<AppSwitcher current="router" owned={claims.products} products={switcherProducts} />
<AppSwitcher current="router" owned={claims.products} products={switcherProducts} variant="compact" />ownedis the app's own verifiedproductsclaim. Owned apps are links under "Your apps"; the current one hasaria-current="page", and one on another origin shows an "opens " mark.- "Add more" lists the rest: Live shows Add and Early access shows Early access, both linking
to
pricingUrl; Coming later shows Later and is not a link. variant="sidebar"(default) is the desktop side nav (40 px rows);variant="compact"is the phone menu drawer (48 px rows). It renders<nav aria-label="Apps">; tab order follows the list.
<NotEntitledView>
Render it inside the page's <main> when /exchange?aud=<product> refuses. It holds the <h1>.
<NotEntitledView
product="flow"
reason={refusal.code} // not_entitled | org_required | no_seat | entitlements_unavailable
canActivate={refusal.activate} // from the not_entitled body
isOwner={member.role === 'owner'}
owned={claims.products}
products={switcherProducts}
orgName={org.name}
userEmail={user.email}
onActivate={startFree} // or activateAction="<form POST target>"
/>| Reason / status | Primary action | Also |
|---|---|---|
| not_entitled, Live, owner with canActivate | Start free | See plans |
| not_entitled, Early access, owner | Request early access (Start free once granted) | See plans |
| not_entitled as non-owner, Coming later, org_required, no_seat | none | See plans |
| entitlements_unavailable | Try again (onRetry, else reloads the product URL) | nothing else: never an upsell |
Every case shows the "Your apps" row and, with orgName, "Only an owner of can add apps"
(owner only, decision F2).
Theming
Override any --nyuro-switcher-* property on an ancestor. Fonts are inherited from the host app.
Public and auth pages that use the "Mix A" display type set:
.auth-page {
--nyuro-switcher-display-font: 'Syncopate', sans-serif;
--nyuro-switcher-display-transform: uppercase;
--nyuro-switcher-display-tracking: 0.04em;
--nyuro-switcher-button-font: 'Michroma', sans-serif;
--nyuro-switcher-button-transform: uppercase;
--nyuro-switcher-button-tracking: 0.14em;
}