react-bannerkit

    Editing

    <BannerBuilder> is the admin half of the package. It fills its container, owns its own undo history, and hands you a document when the user saves.

    import { BannerBuilder } from 'react-bannerkit/builder'
    import 'react-bannerkit/builder.css'
     
    <div style={{ height: '100vh' }}>
      <BannerBuilder
        template={template}
        onChange={(t) => setDraft(t)}
        onSave={async (t) => { await api.save(t) }}
        onUploadImage={async (file) => (await upload(file)).url}
        theme="system"
      />
    </div>

    Props

    PropTypeNotes
    templateBannerTemplateOptional. Omitted, a default template is created. Repaired rather than trusted.
    onChange(t) => voidFires on a trailing debounce once edits settle, so dragging a slider calls you once rather than sixty times.
    onSave(t) => void | Promise<void>Fires only on Save, or Ctrl/Cmd+S. Return a promise and the button shows progress; throw and the message is shown and announced through aria-live.
    onUploadImage(file: File) => Promise<string>Resolve the URL to store. Without it, images become object URLs and the editor says they will not persist.
    theme'light' | 'dark' | 'system'A self-contained palette. It does not read your design tokens.
    debounceMsnumberDefault 300.
    makeIdIdFactoryInjected for deterministic ids in tests.
    className, styleMerged onto the root; the bnb-root scope class is always kept.

    template is not a controlled value. Changing it does not reset an in-progress edit — the editor takes it as a starting document. Remount with a key when you genuinely want to load a different one, which is what the playground does.

    Keyboard

    KeysDoes
    Ctrl/Cmd+Z, Shift+Ctrl/Cmd+ZUndo, redo. A whole drag collapses into one step.
    Ctrl/Cmd+SSave.
    EscapeDeselect.
    Arrow keysNudge a freely placed element; Shift for 10× steps.
    Arrow keys on a dividerResize the split.

    The listener is bound to the editor rather than the document, so an admin page's own shortcuts keep working.

    What the editor can do

    • Panels. Split any panel into columns or rows, drag the divider, delete a panel to give its space back to its sibling. A banner always keeps at least one panel, and the inspector explains that rather than just disabling a button.
    • Elements. Heading, text, button, link, image, overlay, spacer, icon. Drag one out of the stack to place it freely.
    • Carousels. Turn a panel into a carousel and every slide gets its own background and content. Converting copies what was already there onto slide one.
    • Three breakpoints, each with an independent layout, sizing, gutter, and frame colour. Copy one screen's layout to another as a starting point.
    • Preview renders the real <BannerRenderer>, not a lookalike.

    Uploads

    onUploadImage receives the File and resolves the URL to store in the document. There is no built-in storage: the document holds a string, and where that string points is your decision.

    Without the callback the editor falls back to URL.createObjectURL and says so in the UI — useful for a demo like this site's playground, useless for anything that has to survive a reload.

    Driving it yourself

    The state layer is exported with no React in it, so you can put the editor's chrome in your own shell, build a custom inspector, or test editor behaviour without a DOM:

    import { createEditorState, editorReducer, inspectorModel } from 'react-bannerkit/builder'
     
    let state = createEditorState(template)
    state = editorReducer(state, { type: 'splitPanel', panelId, dir: 'cols' })
    const fields = inspectorModel(state)

    Editor state API has the full action list and the selectors that go with it.