Composer
A live playground: drive a set of props and watch the generated source follow. Assembled from the kit’s own controls, so anything awkward about Select, Switch or NumberInput surfaces here first.
src/components/ui/composer.tsxAbout
From the component's own source, where it was written.
A live playground: drive a set of props and watch generated source follow.
Assembled entirely from the kit's own controls, which is the point rather than a flourish — this is the densest form on the site, so anything awkward about Select, Switch or NumberInput surfaces here before it reaches anyone else's form.
It owns no knowledge of the documentation registry. controls describes the inputs, render draws the result and code writes the snippet; a composer works the same in a README page, a design review or a prop explorer.
State can be controlled. The uncontrolled default is what a docs page wants, but a page that needs two composers in sync — a light and a dark preview of one configuration — has to be able to lift it.
Install
npx astralyx-ui add composer
Usage
import { Composer } from '@/components/ui/composer'
<Composer
controls={[
{ type: 'select', prop: 'variant', label: 'variant', options: ['default', 'secondary'], default: 'default' },
{ type: 'boolean', prop: 'disabled', label: 'disabled', default: false },
]}
render={(state) => <Button variant={state.variant} disabled={state.disabled}>Click</Button>}
code={(state) => `<Button variant="${state.variant}" />`}
/>Composer
Change the props and the source below follows.
<Button variant="default" disabled={false} />Examples
Every control type
<Composer controls={controls} render={render} code={code} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
Composer
Extends Omit<ComponentProps<'div'>, 'onChange'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| controls | ComposerControl[] | required | One per prop: select, boolean, text, number or color. Each names the prop it drives and its default. |
| render | (state: ComposerState) => ReactNode | required | Draws the live result from the current state. |
| code | (state: ComposerState) => string | — | The snippet shown beneath the preview. Omit it for a preview with no source. |
| language | Language | 'tsx' | — |
| tall | boolean | false | A taller preview box, for anything with real height. |
| panelLabel | ReactNode | 'Props' | — |
| state | ComposerState | — | Controlled mode, for keeping two composers in sync — a light and a dark preview of one configuration. |
| onStateChange | (state: ComposerState) => void | — | Controlled mode, for keeping two composers in sync — a light and a dark preview of one configuration. |
| resetLabel | ReactNode | 'Reset' | — |
| fullscreen | boolean | true | The expand button in the panel header. A composer driving something small does not need it — the room was never the constraint there. |
| fullscreenLabel | string | 'Expand to full screen' | — |
| exitFullscreenLabel | string | 'Exit full screen' | — |
Functions
| Function | Signature | Description |
|---|---|---|
| composerInitialState | (controls: ComposerControl[]) => ComposerState | — |
Types
| Type | Definition | Description |
|---|---|---|
ComposerValue | string | boolean | number | A live playground: drive a set of props and watch generated source follow. |
ComposerControl | | {
type: 'select'
prop: string
label: string
options: readonly string[]
default: string
}
| { type: 'boolean'; prop: string; label: string; default: boolean }
| {
type: 'text'
prop: string
label: string
default: string
placeholder?: string
}
| {
type: 'number'
prop: string
label: string
default: number
min?: number
max?: number
step?: number
}
| { type: 'color'; prop: string; label: string; default: string } | — |
ComposerState | Record<string, ComposerValue> | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| filling the screen | Fullscreen API, then a fallback | The real thing where the browser allows it, so the page chrome goes too and Escape is handled for us. iPhone Safari exposes no element fullscreen and a request can simply be refused, so a refusal covers the viewport instead rather than leaving a button that does nothing. Leaving it is read back from fullscreenchange, not assumed from the click that started it. |
| independence | no registry coupling | The component knows nothing about the documentation registry; the registry describes its specs in terms of these types, not the reverse. |