Story Composer
alphav0.4.0Instagram-style story creation surface — a locked 9:16 wrapper around the media editor.
Context
Third and final component in the story-system trilogy alongside story-rail (discovery) and story-viewer (consumption). v0.2.0 delegates the capture + edit surface (camera, multi-layer Konva editor, all six tools, discard guard, history) to @ilinxa/media-editor (v0.1.1+), then layers the story-specific publish pipeline on top: ComposerPublishBar in the renderTopBar slot, PublishingProgressOverlay during upload, and a 14-method handle whose publish/exportBlob methods bridge ExportMetadata → PublishMetadata → PublishedStory. Camera-first defaults: rear camera on mobile, front on desktop, microphone for video. Public API 100% preserved across the v0.1.5 → v0.2.0 boundary; the 73-name export snapshot at docs/procomps/media-editor-procomp/story-composer-v0.1.5-exports.snapshot.txt resolves through the v0.2.0 barrel without omission.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/story-composerAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/story-composer-fixturesPreview
All three modes (photo / video / text), 36 built-in emoji stickers. Wired to a fake uploader for the docs site — consumers pass `uploadUrl` or a real `uploader` for prod.
Demo source
Usage
When to use
StoryComposer is the creation surface for Instagram-style stories. It composes with StoryRail (discovery) and StoryViewer(consumption) to form the full story system, but doesn't depend on them. Use it whenever you need camera-first capture + on-canvas editing + one-tap publish.
Quick start
import { useState } from "react"
import { StoryComposer } from "@/components/story-composer"
export function Example() {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Create story</button>
<StoryComposer
isOpen={open}
onClose={() => setOpen(false)}
uploadUrl="/api/stories/upload"
onPublished={(story) => {
// story.items[0].src = the uploaded media URL
console.log("uploaded:", story)
setOpen(false)
}}
/>
</>
)
}Capture modes
- Photo — camera tap → instant editor with the default toolbar (Text / Draw / Stickers / Filters / Adjust). Crop is opt-in via
enabledTools={[…, "crop"]}— stories are 9:16-locked, so default flows don't need it. - Video — long-press hold OR tap-to-toggle shutter. Auto-stop at
maxVideoDuration(default 30s). Edit stage shows a two-handle trim bar; overlays bake into the final video on publish. - Text — 8 gradient backgrounds + centered text + font + color picker. Renders to PNG on publish.
Hide modes you don't want via hideModes={["video","text"]}.
Publish
Two paths — pick one:
uploadUrl="/api/upload"— composer POSTs FormData ({ file, metadata }) with progress events.uploader={async (blob, meta) => …}— custom uploader for signed-URL flows (S3 PUT, Cloudinary, Mux). Must return{ url, thumbnailUrl? }.
Pan + zoom
Drag (single pointer — mouse or 1 finger) to pan; a drag that starts on a text/sticker overlay moves that overlay instead, and a tap never pans. Zoom 1× → 4× via 2-finger pinch (touch) or mouse wheel anchored to the cursor (desktop — native non-passive, beats the browser's Ctrl+wheel page-zoom). Arrow keys pan in the arrow direction; + / - / 0 zoom in / out / reset. Disabled while drawing or cropping.
Imperative handle
Pass a ref to drive the composer programmatically: takePhoto, switchCamera, startRecording, addText, addSticker, applyFilter, publish, exportBlob, and more (14 methods total).
More
Full reference + slot extension points + accessibility notes live in docs/procomps/story-composer-procomp/story-composer-procomp-guide.md.
Features
- Camera capture (photo + video + text-only modes) via getUserMedia + MediaRecorder
- Gallery picker fallback when camera is denied or unavailable
- Multi-layer Konva editor: image / drawing / stickers / text / UI
- Six edit tools — Text, Draw (vector + eraser), Stickers (36 built-in emoji + extensible), Filters (10 Instagram-style presets with pre-rendered thumbs), Adjust (brightness/contrast/saturation/blur), and opt-in Crop (9:16 / 1:1 / 4:5). Default `enabledTools` ships the first five; consumers add `"crop"` when their flow isn't 9:16-locked.
- Pan + zoom on the editor canvas (1×–4×): single-pointer drag-to-pan (mouse or 1-finger; yields to draggable text/sticker overlays), 2-finger touch pinch, mouse wheel anchored to cursor (native non-passive — beats browser page-zoom), keyboard arrows pan in image direction, +/-/0 zoom/reset
- Video record: long-press hold OR tap-to-toggle shutter; auto-stop at maxVideoDuration; two-handle trim bar; overlays bake into final video via canvas.captureStream + MediaRecorder pipeline
- Built-in upload via XHR POST FormData with progress, plus `uploader` escape hatch for signed-URL flows (S3 / Cloudinary / Mux)
- Responsive: mobile-fullscreen / desktop-modal sized to a viewport-relative 9:16 with a min/max clamp (can't collapse or overflow) and safe-area awareness; top bar follows the IG chrome model (X swaps to a back arrow in the edit stage; Publish hidden during photo/video capture)
- Undo/redo on every overlay + stroke command (Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z; 50-deep stack)
- Permission-denied auto-retry via navigator.permissions.query
- Discard-confirm guard for unsaved edits (opt-out via confirmOnDiscard: false)
- Live-region announcer for screen readers (Konva canvas is opaque to SR)
- Five exported sealed-folder parts + three exported hooks for advanced compositions
- 14-method imperative handle (open/close/reset, switchCamera/takePhoto/startRecording/stopRecording/importFromGallery, addText/addSticker/setAdjustments/applyFilter, publish/exportBlob)
- v0.2.0 architecture: thin wrapper around @ilinxa/media-editor — capture + edit surface delegated; story-shaped publish flow + ComposerPublishBar layered on top
- v0.4.0 — P3 feature-slicing lockstep: camera capture now flows through media-editor's `capture={mediaCapture}` extension (`@ilinxa/media-editor-capture`) instead of a static import; the v0.1.5 backward-compat re-exports scheduled for v0.3.0 removal (ComposerCamera/ComposerEditor/ComposerToolbar/ColorSwatchPicker + their Props types, and useMediaCapture/validateGalleryFile/suggestedVideoFilename/CapturedPhoto/CapturedVideo/CaptureStatus/FacingMode + their option types) are removed — breaking for any consumer still on the v0.1.5 names.