import { createSignal, onCleanup, onMount } from 'solid-js'; import { defineWebComponent } from './define'; import { PromptDock, type PromptDockAppearance, type PromptDockFrame } from '../ui/prompt-dock'; interface Props extends Record { /** How the tray frames the input — the SPATIAL inset axis: `inset` (default, * the classic recessed frame on every side) | `edge` (top/bottom inset only; * the input sits flush left/right so the lips span the full width) | `none` * (no inset; the lips attach directly as a plain stack). Attribute: `frame`. */ frame?: PromptDockFrame; /** How the tray surface looks — the VISUAL axis, orthogonal to `frame`: * `soft` (default, sunken surface + border + radius) | `outlined` * (transparent + border + radius) | `filled` (sunken, no border, + radius) | * `plain` (bare). Attribute: `appearance`. */ appearance?: PromptDockAppearance; } /** The named slots whose occupancy gates a dock lip. An empty `` is always * a truthy node, so the facade tracks which are actually filled and only passes * those regions to the primitive (which renders top/bottom only when truthy). */ const SLOT_NAMES = ['top', 'bottom'] as const; type SlotName = (typeof SLOT_NAMES)[number]; /** * `` — a recessed tray that frames a prompt input and can extend * with optional "lip" regions above and/or below it. The input is the raised card * that floats on the tray; the lips sit in a slightly darker recessed band sharing * the tray's rounding, so the whole thing reads as one cohesive control. * * Slots: the DEFAULT slot is the prompt input (the raised card). `slot="top"` is a * recessed band ABOVE the input (a notice, a hint); `slot="bottom"` is a band BELOW * it (a mode / control row). A lip renders only when its slot is filled — an empty * dock shows just the input, no stray band. * * Styling splits into two orthogonal variant attributes: `frame` (the spatial inset) * and `appearance` (the surface). `::part(tray)` / `::part(top)` / `::part(bottom)` * expose the three regions for outside styling; four `--kai-prompt-dock-*` tokens * tune the chrome (surface / border / radius / inset). * * ```html * *
Working in the docs branch
* *
Claude Opus 4.8 · 200k context
*
* ``` */ defineWebComponent('kai-prompt-dock', { frame: 'inset', appearance: 'soft', }, (props, { element }) => { // Track which named lip slots are filled. Re-read on child mutations so streamed // / late content lights up its band. An unfilled lip is never rendered (the // primitive gates on a truthy top/bottom, and a bare is always truthy). const [filled, setFilled] = createSignal>({ top: false, bottom: false }); onMount(() => { const read = () => { const next = {} as Record; for (const name of SLOT_NAMES) next[name] = !!element.querySelector(`:scope > [slot="${name}"]`); setFilled(next); }; read(); const observer = new MutationObserver(read); observer.observe(element, { childList: true }); onCleanup(() => observer.disconnect()); }); const lip = (name: SlotName) => (filled()[name] ? : undefined); return ( ); });