react type declarations
The precise contract for @playspace/react.
/**
* @playspace/react — PlaySpace surfaces as React components
*
* Every component here is role-aware. It reads the role from the token in
* context and renders the clinician's side or the client's side accordingly.
* You never pass a role, and there is no prop that would let you pass the wrong
* one.
*
* React 18 and 19. Every component is a client component.
*
* @packageDocumentation
*/
// `JSX` is imported rather than assumed global: React 19's types removed the
// global JSX namespace, so a declaration file relying on it fails to compile
// against React 19 while compiling fine against React 18.
import type { ReactNode, CSSProperties, JSX } from 'react'
// ─── Shared vocabulary ───────────────────────────────────────────────────────
export type Surface =
| 'sandtray'
| 'dollhouse'
| 'whiteboard'
| 'games'
| 'playroom'
| 'storybooks'
| 'worksheets'
| 'forms'
| 'studio'
| 'models'
export type Role = 'clinician' | 'patient'
export type PlayroomType = 'child' | 'teen' | 'adult'
export type SessionItem =
| 'activityShelf'
| 'multiplayerGames'
| 'singlePlayerGames'
| 'wallPosters'
| 'whiteboard'
| 'sandTray'
| 'dollhouse'
export type ArtifactType =
| 'storybook'
| 'storybookCopy'
| 'worksheet'
| 'worksheetCopy'
| 'form'
| 'formResponse'
| 'sandtraySave'
| 'dollhouseSave'
| 'whiteboardSnapshot'
| 'generatedGame'
| 'model'
| 'sessionSummary'
/** A reference to a person by either identifier. Supply exactly one. */
export type Ref = { id: string } | { externalId: string }
export interface Reference {
id: string
externalId: string | null
}
// ─── Provider ────────────────────────────────────────────────────────────────
export interface PlaySpaceTheme {
/** Applied to chrome, controls and focus rings. Not to play content. */
accent?: string
radius?: 'none' | 'sm' | 'md' | 'lg'
/** `'inherit'` adopts the host page's font stack. */
font?: 'inherit' | string
colorScheme?: 'light' | 'dark' | 'system'
}
export interface PlaySpaceProviderProps {
/**
* Called on mount and again at eighty percent of the current token's lifetime.
* You never write expiry handling; a surface mid-interaction is not torn down
* by a renewal.
*/
fetchToken?: () => Promise<string>
/**
* A static token. Use instead of `fetchToken` for short-lived contexts only —
* it will not renew, and a clinician who leaves the tab open will be stranded.
*/
token?: string
onEvent?: (event: PlaySpaceEvent) => void
/** Must be idempotent — an error may fire more than once for one cause. */
onError?: (error: PlaySpaceEmbedError) => void
theme?: PlaySpaceTheme
/** Interface language. Falls back to the clinician's account setting, then `'en'`. */
locale?: string
/** Inferred from the token. Set explicitly only to assert. */
environment?: 'production' | 'sandbox'
children: ReactNode
}
/**
* The root. Everything else must be inside one.
*
* One provider per session — do not nest, and do not mount two for the same
* session. If you need surfaces in two places on a page, put the provider above
* both.
*/
export declare function PlaySpaceProvider(props: PlaySpaceProviderProps): JSX.Element
// ─── Common surface props ────────────────────────────────────────────────────
export interface SurfaceProps {
/**
* The surface owns its own height. Set width and an optional max-height on the
* container instead; a fixed container height clips.
*/
height?: number | string
className?: string
style?: CSSProperties
/** Rendered while the surface is establishing. Defaults to our own skeleton. */
fallback?: ReactNode
}
export interface Snapshot {
artifactId: string
type: ArtifactType
createdAt: string
}
// ─── Live play surfaces ──────────────────────────────────────────────────────
export interface SandtrayProps extends SurfaceProps {
/** Resume a saved tray. Omit for a fresh one. */
saveId?: string
/** Persists on change, debounced. Clinician token only. Default `true`. */
autosave?: boolean
/** Restrict the figure library, e.g. `['core', 'animals', 'family']`. */
figureSets?: string[]
onSnapshot?: (snapshot: Snapshot) => void
}
/**
* The flagship surface. A three-dimensional tray, a categorised figure library,
* and every participant's cursor.
*
* A clinician gets the library, sand tools, camera controls, save and reset, and
* the ability to hand control to the client. A client gets the tray, their
* cursor, and the figures the clinician has made available.
*
* Needs 640 by 480 to be usable; below that it renders a message asking the
* person to enlarge the window rather than an unplayable tray.
*/
export declare function Sandtray(props: SandtrayProps): JSX.Element
export interface DollhouseProps extends SandtrayProps {
layout?: 'house' | 'apartment' | 'classroom'
rooms?: string[]
}
export declare function Dollhouse(props: DollhouseProps): JSX.Element
export type WhiteboardTool = 'pen' | 'highlighter' | 'shapes' | 'text' | 'stamps' | 'eraser'
export interface WhiteboardProps extends SurfaceProps {
/** An image reference puts a worksheet or photograph under the drawing layer. */
background?: 'blank' | 'grid' | 'lined' | { imageUrl: string }
tools?: WhiteboardTool[]
onSnapshot?: (snapshot: Snapshot) => void
}
/**
* Collaborative drawing. A clinician can clear the board, change the background,
* and restrict the client's tools live. A client draws.
*/
export declare function Whiteboard(props: WhiteboardProps): JSX.Element
export interface GameSummary {
slug: string
title: string
minPlayers: number
maxPlayers: number
skills: string[]
thumbnailUrl: string | null
}
export interface GameLibraryProps extends SurfaceProps {
filter?: {
players?: number
skills?: string[]
ageRange?: [number, number]
category?: string
}
onLaunch?: (game: GameSummary) => void
}
/**
* The licensed catalog, filterable.
*
* Clinician-only. A patient token renders nothing and emits `error` with
* `role_not_permitted` — branch on `role` from `usePlaySpace()` to avoid it.
*/
export declare function GameLibrary(props: GameLibraryProps): JSX.Element
export interface GameProps extends SurfaceProps {
slug: string
}
export declare function Game(props: GameProps): JSX.Element
export interface PlayroomProps extends SurfaceProps {
/** Defaults to the session's playroom type. */
type?: PlayroomType
theme?: string
items?: SessionItem[]
}
/**
* Every live surface in one themed environment, with the clinician steering and
* the client following. When the clinician opens the dollhouse, the client's
* view follows.
*/
export declare function Playroom(props: PlayroomProps): JSX.Element
/**
* `Playroom` plus the session frame — video panel, participant presence, the
* waiting-to-join notice, and session controls. The complete hosted experience,
* inside your application.
*/
export declare function PlayroomSession(props: PlayroomProps): JSX.Element
// ─── Content surfaces ────────────────────────────────────────────────────────
export interface StorybookCreatorProps extends SurfaceProps {
/** Set it and the storybook is personal. Omit it and it is library content. */
subject?: Ref
/** Fires the moment the row exists, BEFORE any page is written. */
onCreated?: (event: { storybookId: string }) => void
/** Fires when generation finished and the book is readable. */
onReady?: (event: { storybookId: string; title: string | null; pageCount: number }) => void
}
/**
* `onCreated` firing before any page is written is deliberate: a host can record
* the identifier even if the clinician closes the panel mid-generation.
*/
export declare function StorybookCreator(props: StorybookCreatorProps): JSX.Element
export interface StorybookReaderProps extends SurfaceProps {
storybookId: string
/** Controlled paging, if you want your own navigation. */
page?: number
onPageChange?: (page: number) => void
}
/** A client's reading position persists across sessions. */
export declare function StorybookReader(props: StorybookReaderProps): JSX.Element
export interface StorybookLibraryProps extends SurfaceProps {
filter?: { subject?: Ref; createdBy?: Ref }
allowCreate?: boolean
onSelect?: (storybook: { id: string; title: string | null }) => void
}
export declare function StorybookLibrary(props: StorybookLibraryProps): JSX.Element
export interface WorksheetCanvasProps extends SurfaceProps {
worksheetId: string
/** `'collaborative'` requires a live session. `'review'` is read-only for both roles. */
mode?: 'collaborative' | 'solo' | 'review'
subject?: Ref
page?: number
onPageChange?: (page: number) => void
onComplete?: (event: { copyId: string }) => void
}
/**
* A clinician can annotate, add pages and mark complete. A client fills in.
*/
export declare function WorksheetCanvas(props: WorksheetCanvasProps): JSX.Element
export interface WorksheetLibraryProps extends SurfaceProps {
filter?: { subject?: Ref; createdBy?: Ref }
allowCreate?: boolean
onSelect?: (worksheet: { id: string; title: string }) => void
}
export declare function WorksheetLibrary(props: WorksheetLibraryProps): JSX.Element
export interface FormFillProps extends SurfaceProps {
formId: string
subject?: Ref
/**
* Carries the response identifier and, for scored instruments, the score. The
* answers themselves are fetched with the server SDK — they are never handed to
* a browser you do not control.
*/
onSubmit?: (event: { responseId: string; score: number | null }) => void
}
export declare function FormFill(props: FormFillProps): JSX.Element
export interface FormBuilderProps extends SurfaceProps {
formId?: string
onSaved?: (event: { formId: string }) => void
}
/** Clinician-only. Emits `role_not_permitted` on a patient token. */
export declare function FormBuilder(props: FormBuilderProps): JSX.Element
// ─── Generation surfaces ─────────────────────────────────────────────────────
export interface PlayStudioProps extends SurfaceProps {
subject?: Ref
onGenerated?: (event: { gameId: string; playUrl: string }) => void
}
/** A clinician describes a game and gets a playable one. Renders its own progress state. */
export declare function PlayStudio(props: PlayStudioProps): JSX.Element
export interface ModelStudioProps extends SurfaceProps {
source?: 'text' | 'image'
onGenerated?: (event: { modelId: string; previewUrl: string | null }) => void
}
/**
* A figure that is not in the sandtray library, generated from a description or a
* photograph. The photograph is discarded after generation.
*/
export declare function ModelStudio(props: ModelStudioProps): JSX.Element
export interface ContentStudioProps extends SurfaceProps {
subject?: Ref
/** Restrict the tabs. Defaults to every licensed generation surface. */
tabs?: Array<'storybook' | 'worksheet' | 'form' | 'game' | 'model'>
onGenerated?: (event: { artifactId: string; type: ArtifactType }) => void
}
/** Every generation surface behind one tabbed interface. */
export declare function ContentStudio(props: ContentStudioProps): JSX.Element
// ─── Hooks ───────────────────────────────────────────────────────────────────
export interface PlaySpaceContextValue {
ready: boolean
role: Role | null
subject: Reference | null
session: {
id: string
externalId: string | null
status: 'pending' | 'live' | 'complete' | 'cancelled'
participants: Array<{ role: Role; present: boolean }>
} | null
/** What this token permits. Render your own navigation from it. */
surfaces: Surface[]
/** Clinician only. A patient token throws. */
open: (surface: Surface) => void
close: (surface: Surface) => void
end: () => Promise<void>
}
export declare function usePlaySpace(): PlaySpaceContextValue
export interface ArtifactSummary {
id: string
type: ArtifactType
title: string | null
createdBy: Reference
subject: Reference | null
createdAt: string
}
export declare function useArtifacts(filter?: {
subject?: Ref
createdBy?: Ref
type?: ArtifactType
}): {
artifacts: ArtifactSummary[]
isLoading: boolean
error: PlaySpaceEmbedError | null
refresh: () => Promise<void>
}
export declare function useGenerationJob(jobId: string | null): {
job: { id: string; status: string; progress: number; reason: string | null } | null
isLoading: boolean
}
export declare function useUsage(): {
usage: {
period: string
resources: Record<string, { used: number; limit: number }>
resetsAt: string
} | null
isLoading: boolean
}
// ─── Events ──────────────────────────────────────────────────────────────────
/**
* At-most-once, and never carrying clinical content — identifiers, counts,
* durations and enumerated values only.
*/
export interface PlaySpaceEventMap {
/** The surface mounted and is interactive. */
ready: { role: Role; surfaces: Surface[] }
'surface.opened': { surface: Surface }
'surface.closed': { surface: Surface; durationMs: number }
'participant.joined': { role: Role }
'participant.left': { role: Role }
/** Something now exists. It may not be finished. */
'artifact.created': { artifactId: string; type: ArtifactType }
/** It is finished and usable. */
'artifact.ready': { artifactId: string; type: ArtifactType }
'job.progress': { jobId: string; percent: number }
'session.ended': { durationMs: number; surfacesUsed: Surface[] }
}
export type PlaySpaceEventType = keyof PlaySpaceEventMap
export type PlaySpaceEvent = {
[K in PlaySpaceEventType]: { type: K; payload: PlaySpaceEventMap[K] }
}[PlaySpaceEventType]
// ─── Errors ──────────────────────────────────────────────────────────────────
/**
* A fixed taxonomy, so a host can tell our failure from its own and from the
* browser's.
*/
export type PlaySpaceErrorCode =
/** The token lapsed and renewal did not run. Usually a static `token` prop. */
| 'token_expired'
/** Malformed, revoked, or issued for another organisation. */
| 'token_invalid'
/** `fetchToken` threw three times. Your token endpoint is down. */
| 'token_refresh_failed'
/** This page's origin is not on the token. Add it at mint. */
| 'origin_not_allowed'
/** The token does not permit this surface. Widen at mint, within the session's ceiling. */
| 'capability_missing'
/** The organisation is not licensed. Commercial, not technical. */
| 'entitlement_denied'
/** A patient token rendered a clinician-only component. */
| 'role_not_permitted'
/** Temporarily down. The surface renders its own notice. */
| 'surface_unavailable'
/** Transport failure. Surfaces recover their own state on reconnect. */
| 'network_error'
/** A browser extension or storage restriction blocked the frame. Not your bug. */
| 'render_blocked'
export interface PlaySpaceEmbedError {
code: PlaySpaceErrorCode
/** Safe to show a user. Never carries clinical content. */
message: string
/** Present when the failure reached our servers. Log it. */
requestId: string | null
surface: Surface | null
recoverable: boolean
}
// ─── Low-level escape hatch ──────────────────────────────────────────────────
export interface MountOptions {
surface: Surface
fetchToken: () => Promise<string>
onEvent?: (event: PlaySpaceEvent) => void
onError?: (error: PlaySpaceEmbedError) => void
theme?: PlaySpaceTheme
locale?: string
props?: Record<string, unknown>
}
/**
* Mount a surface without React. Re-exported from `@playspace/embed`, which is
* the transport this package is built on.
*/
export declare function mount(
element: HTMLElement,
options: MountOptions
): { unmount: () => void; update: (props: Record<string, unknown>) => void }