[Preview – do not merge] Prompt field: AI treatment + swc-buttons + pixel loader - #6635
[Preview – do not merge] Prompt field: AI treatment + swc-buttons + pixel loader#6635rubencarvalho wants to merge 102 commits into
Conversation
…ition Adds an `expanded` boolean property to `swc-prompt-field`, matching the Figma design's collapsed/expanded variant naming. Defaults to the single-line collapsed pill (send button inline with the textarea); `expanded` renders the taller layout with a separate action bar (upload button + send/stop button on their own row). Also fixes a rendering bug uncovered while implementing this: the collapsed pill's border-radius used `corner-radius-full` (50%), which renders an ellipse on a non-square box instead of a stadium shape. Radius is now derived from half of the pill's own fixed block-size, mirroring the existing fix in linear-progress-base.css. The toggle between layouts is animated: box shape (border-radius, padding, gap) transitions directly, and the action bar's height animates via the calc-size() progressive-enhancement pattern already used in accordion-item.css. The send/stop button crossfades between its inline and action-bar positions; the inactive copy is marked inert + aria-hidden, following the same pattern as AccordionItem's collapsed content.
…duplicated+hidden Replaces the duplicate-button crossfade (one inline copy, one action-bar copy, inactive one marked inert/aria-hidden) with a single conditionally rendered button per layout. Simpler, and removes the class of bugs the duplication caused: ambiguous .swc-PromptField-send/-stop queries, inert-vs-focus interactions, and the compensating negative-margin hack needed to cancel the row's gap around the crossfading copy. The box shape and action bar height still animate on expand/collapse; only the button itself now appears/disappears instantly rather than fading, since there's no second instance to fade into.
…om property Adds --swc-prompt-field-max-block-size (default 40vh) to the textarea's max-block-size, combined with the existing max-rows cap via min() so whichever is smaller wins. Guards against max-rows worth of text overflowing the viewport on small/embedded surfaces; textareas already scroll internally once content exceeds this, so no extra overflow rule is needed.
Flips the boolean property from expanded (opt-in) to collapsed (opt-in): the taller multiline card with the action bar is now the default, unmarked layout, and the single-line row is the opt-in collapsed layout via the new collapsed attribute. All CSS state selectors, template conditionals, and animations are inverted accordingly, along with tests, stories, and docs. Verified live: the default (no attribute) now renders the expanded tree/layout, collapsed opts into the single-line row, and the animated transition still works in both directions. BREAKING CHANGE: the expanded attribute is removed; consumers relying on the previous expanded-opt-in, collapsed-by-default behavior must add the collapsed attribute to keep the single-line layout.
…t verbosity Extracts the action-bar block into _renderActionBar(), matching the existing _renderStatusIcon() pattern, with a one-line comment explaining why it stays always-mounted (calc-size height animation) instead of conditionally rendered like the send/stop button. Also trims several multi-line JSDoc/CSS comments down to one line each; the removed detail was better suited to prose than inline code comments.
Matches the Figma reference, which uses the same gap in both the collapsed and expanded states; the input row previously widened to 16px by default, a leftover from an earlier iteration.
…ing API Restructure controls into a shared icon+text-group wrapper used by both collapsed and expanded layouts for animation consistency, fix collapsed mode to scroll horizontally like a single-line input instead of clipping wrapped lines, bump textarea type scale to font-size-200/line-height-200, and drop the min-rows/max-rows API in favor of the 40vh max-height cap, matching the reference implementation's sizing model.
…d-expanded' into ruben/feat-prompt-field-collapsed-expanded
…radius The box-height and upload-button-width transitions animated independently of the discrete single-line/two-row layout switch, which is not interpolatable without flicker. The result was a visible desync: content snapped to its final position instantly while the box/button sizes lagged behind, producing an empty gap or a missing button mid-transition. Drop the transitions so the layout switch is instant and consistent, and use a fixed 24px radius (no matching token exists) for both layouts.
…tion Explore a CSS-only alternative to the FLIP branch. calc-size(auto, size) does not smoothly animate a flex-wrap-driven auto height in this engine (verified in isolation with a minimal repro outside the component), so switch .swc-PromptField-controls from flex-wrap to a 2-row CSS grid and animate grid-template-rows between 0fr and 1fr instead, which does interpolate smoothly. The box itself needs no height transition: it's a plain flex column sized to the grid's content, so it naturally follows every frame. This also lets the send/stop button span both grid rows and center itself on their combined height, so it re-centers automatically as row 2 grows/shrinks instead of needing a discrete row reassignment or a JS-driven position correction. Removes the now-unnecessary line-break marker element and the max-inline-size-based hiding on the upload button, since the row's own height animation handles both. Gotcha found along the way: padding-block-start on the row-2 grid item (an overflow: hidden flex/grid item) still counts toward its own minimum size and prevents the track from ever reaching a true 0fr; moving that spacing to a margin on the child fixed it.
…transition Row 1's min-block-size (24px) was shorter than the send/stop button (32px) spanning into it, forcing the grid to reserve a few invisible px in row 2 to satisfy that minimum even when collapsed, pushing row 1's content off its own vertical center. Match row 1's min-block-size to the button's own height instead, and move the row-1/row-2 gap from a margin on the upload button to .controls's own row-gap so it collapses to 0 in sync with the grid-template-rows animation rather than leaving a fixed leftover gap. Also switch the send/stop button from centering across the full span to aligning with its bottom edge, matching the upload button's own position when expanded instead of sitting visibly higher. Bumps the shared spike transition duration from 160ms to 240ms; 160ms read as too quick.
Row 1 was auto-sized; an auto grid track adjacent to one animating via grid-template-rows fr briefly overshoots its resolved size mid-transition in this engine, confirmed via an isolated repro completely independent of the send/stop button's grid placement (ruled that out first, since it was the earlier suspect). The overshoot stretched row 1's content taller than its final 32px and back, which read as the icon/text bobbing down and back up. A fixed 32px track has nothing to recompute mid-transition.
…ments
Replace the custom --swc-prompt-field-spike-duration property with
token("animation-duration-500") (250ms, closest match to the 240ms this
was tuned to), and tie row 1's fixed height and the send/stop button's
size together via the existing --swc-prompt-field-send-block-size custom
property instead of two separate hardcoded 32px literals. No matching
token exists for the cubic-bezier(0.8, 0, 0.2, 1) easing (a deliberate,
steeper curve than animation-ease-in-out), so it stays a literal. Also
condense several multi-line comments down to one line each.
…edent animation-duration-500 (250ms) doesn't match any existing block-size/ height reveal transition in the codebase: accordion-item.css uses animation-duration-100 (130ms) and message-sources.css/response-status.css use animation-duration-200 (160ms) for the same kind of animation. Switch to animation-duration-200 to match the closer, pattern-level precedent.
- Re-add min-rows/max-rows as optional properties (no default): unset, only --swc-prompt-field-max-block-size caps textarea growth. - max-block-size now resolves against a sized ancestor's container query block size (40cqb) when one exists (e.g. a chat panel with a fixed height), falling back to 40vh otherwise. Fixes growth capping against an unrelated viewport (verified against the Storybook preview iframe). - Fix a regression from the collapse/expand animation work: text-group was locked to a fixed height to stop icon/text jitter during the transition, which also silently clipped multi-line textarea growth instead of growing the card. Restructured so text-group stays in plain flex flow (grows freely) while only leading-actions' own reveal animates, in an isolated single-row grid; the send/stop button is anchored via absolute positioning instead of spanning grid rows. - Fix leading-actions' 0fr grid row not collapsing to a true 0 height: overflow/min-block-size need to live on the grid item (a new leading-actions-row wrapper), not the grid container itself. - Pin the status icon to the top of text-group so it doesn't drift toward the vertical center as the textarea grows to multiple lines. - Swap the placeholder three-dots icon for swc-ui-icon's asterisk, which reads more like an actual AI indicator; remove the now-unused ThreeDotsIcon export and its orphaned three-dots.svg reference asset.
Clarify that 40cqb resolves against a consumer-provided ancestor with container-type: size, not the prompt field's own box (which would be a circular reference), and show the wrapper needed to opt in.
Pair leading-actions' existing height reveal with an opacity transition on the same timing, so the upload button fades out/in alongside the height collapse instead of just being clipped away.
Give opacity a shorter duration than grid-template-rows, so it reaches 0 before the height finishes collapsing instead of exactly matching it.
Collapsed no longer clamps to a single line with horizontal scroll; it now wraps and grows like the expanded layout, just starting more compact. Also clarifies why the container-query max-height fallback is safe (plain size query, not a style() query).
…ument 40cqb via custom property The @container block only flipped the max-block-size default to 40cqb when a consumer wrapped the field in a container-type: size ancestor. Nothing in the repo triggered it, and it duplicated the --swc-prompt-field-max-block-size escape hatch that already lets a consumer set 40cqb themselves. Remove the block and document the preferred container-relative cap via the custom property.
…-variant colors Adds a variant property plus the outer ring, hue-sweep, gloss, and inset-shadow layers behind the card, with real brand-derived OKLCH colors wired for variant="subtle" across idle/hover/focus/generating states. Balanced/prominent remain placeholder colors pending their own real-color pass.
The VRT meta sets `component` but supplies no argTypes, so Storybook's default extractor generates controls from the CEM and, unlike getStorybookHelpers, does not drop private members. That surfaced the component's private @State in the dev-build Controls tab. VRT stories are snapshot-only, so disable the controls panel.
The leading-actions row uses overflow to clip the collapse animation, and Firefox clips the upload button's focus ring along with it. Switch to overflow: clip with an overflow-clip-margin sized to the focus ring so the ring paints in full; the collapsed state is already opacity: 0, so nothing bleeds during the animation.
The card hard-sets cursor: text for the click-to-focus affordance, so a disabled field still showed the text cursor over the card area. Override it to default when disabled, matching native disabled input behavior.
Hovering a disabled field still animated the outer ring, since the hover rules are not gated on disabled. Set pointer-events: none on the card so no hover treatment fires, and move the default cursor to the host (the card no longer receives pointer input). Matches native disabled input behavior. The legal footer sits outside the card, so its links stay interactive.
Drives coverage through createPermutations grouped into rows (per the VRT skill). One Permutations story now covers, in light/ltr and dark/rtl: variant x generating, text length x expanded/collapsed layout, disabled, and the card/media artifact strips including an overflowing set that exercises the scroll chevrons. Fields render at a roomy width, with long prompts and the overflowing strip constrained so wrapping and scrolling still trigger. Adds two forced-state rows (hover, focus-visible) that put every action button, upload, send, and both artifact-scroll chevrons, into the same state at once via a forcePseudoState play, so all their colors can be compared ahead of the swc-button swap. The strip overflows so the chevrons render, and the play waits for them before forcing.
The card wash, outer ring, and ambient hue-sweep are anchored to the physical bottom-right, so in RTL the glow stays pinned while the composer layout flips. Hoist the horizontal anchor of the wash and ring into custom properties and flip them (plus scaleX(-1) on the decorative hue-sweep ::before) under :host(:dir(rtl)), so the glow follows the mirrored layout.
… solid overlay The edge fades painted a background-layer-2-color gradient over the tiles, which read as a grey smudge on the branded card wash. Mask the scroll container so the tiles fade to transparent at each scrollable edge and the wash shows through, adapting to any background for free. The fade stays fully transparent across the chevron zone (a plateau, so no tile peeks under the button), then softens to opaque over the remaining distance. - Drop the two overlay fade <div>s, the fade-solid var, and their LTR/RTL rules - Drop the chevrons' opaque ::before mask (tiles now fade before reaching them) - Edges driven by the existing has-scroll-prev/next classes; RTL flips the physical mask direction
…tifact buttons Replace the five hand-rolled <button>s with swc-action-button: upload, send, stop, the artifact scroll chevrons, and the upload-artifact close button. Icons move to the icon slot, aria-label becomes accessible-label, and the bespoke button appearance is dropped in favor of the component's, re-theming only what the design needs via its custom properties: - send/stop: full corner radius (circle), neutral / gray-900 fill, white icon - upload: quiet default with the AI auto-contrast tint on hover/focus/down - send disabled: auto-contrast tint instead of the flat disabled fill - chevrons: default gray fill, kept aria-disabled + tabindex roving focus - dismiss: 20px round gray circle sized via min-block-size + padding Chevron focus-management querySelector types change from HTMLButtonElement to HTMLElement since the elements are now custom elements.
…-focus Addresses review feedback: the click-anywhere-to-focus guard hardcoded its own focusable list. Reuse focusableSelector from core utils (keeping the extra `slot` match), so it stays in sync with the standard focusability rules and also excludes disabled/inert elements.
Addresses review feedback: variant (subtle, balanced, prominent) drives the AI brand treatment but was undocumented. Add an Options > Variant story and MDX section describing the three intensity levels and the single-hue retheme.
… element The artifact edge fade became a mask on the scroll container toggled by the has-scroll-next class; the pagination test still queried the dropped .swc-PromptField-artifacts-fade--end element and failed. Assert the class instead.
The leading-actions row clipped its overflow for the collapse animation, cutting the upload button's focus ring. The earlier overflow-clip-margin escape hatch works in Chromium/Firefox but Safari doesn't support it, so the ring stayed clipped (a WCAG focus-visibility failure). Only clip while collapsed and during the collapse animation; expanded reverts to overflow: visible so the ring paints in full in every engine (collapsed still hides the button via opacity: 0 and a zero-height grid track). Also give the upload button an explicit focus ring like the other action buttons, so its size is bounded rather than the oversized native ring.
…tion gap The text group used align-items: center, so the status icon floated centered instead of aligning with the text, and its min-block-size reserved the inline send button's height even when expanded, adding slack below the text before the action row. Align to baseline (matching the reference), and scope the min-block-size to the collapsed layout where the send actually sits inline, so expanded is content-height and the text-to-actions gap is the intended 16px.
… swc-action-button + pixel loader)
Preview only (do not merge). Brings in swc-pixel-loader (PR #6605) and wires it into the status slot: animates while generating, static otherwise. Sizes it to 24px so its center lines up with the upload button's icon, and centers the inline send button on the content-height collapsed row so the loader, text, and button stay aligned in both layouts with no shift when toggling collapsed.
… review Preview only. Disables the eight accessibility label argTypes so the designer-facing Controls panel surfaces just the meaningful options: variant, generating, collapsed, disabled, placeholder, and the brand-color property.
|
📚 Branch Preview Links🔍 Gen1 Visual Regression Test ResultsWhen a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:
Deployed to Azure Blob Storage: If the changes are expected, update the |
…ch it The previous inset-block: 0 stretched the action button's host to the content-height row, so its 32px circle overflowed downward and read as bottom-aligned. Anchor it at 50% and pull up half its height instead, so the natural-size button centers on the row alongside the text and loader (no transform, to avoid the :active transform conflict).
Preview only. Wraps the Playground in a small stateful demo element so the + button opens a real file picker and slots the chosen files in as media artifacts (dismiss removes them), while the visual args still drive from Controls. Lets designers try the full attach flow in the preview.
… migration Preview only. The dismiss button became an swc-action-button, which moves the accessible name to `accessible-label`, sizes from a single `--swc-upload-artifact-dismiss-visual-size`, and delegates focus (host is tabindex -1). Update the three stale assertions accordingly. These belong on ruben/refactor-prompt-field-swc-buttons; applied here to green the preview.
Preview only. Syncs geometry.ts with aramos-adobe/feat-pixel-loader-swc-2477: corners now stay square when a diagonal neighbor is filled, so diagonally touching cells read as connected. No API change.
Switch the upload button to the static-color variant, resolved from the inherited color-scheme so it flips black/white with the themed card, and match the status-icon loader color to the static content color.
…ibute Drop the JS color-scheme observer and static-color attribute; set the upload button's static content/hover colors with light-dark() directly, matching the loader. Same theme-adaptive result with no runtime overhead.
A design-review preview stacking three in-flight pieces so designers can see the full prompt-field vision in one live Storybook, plus a couple of layout fixes found along the way. It intentionally depends on unmerged work and must not be merged.
Stacks on:
ruben/feat-prompt-field-ai-branding-v3(base, PR feat(prompt-field): AI brand treatment #6610) — the AI brand treatmentruben/refactor-prompt-field-swc-buttons— upload/send/stop →swc-action-buttonswc-pixel-loaderfrom feat(pixel-loader): productionize swc-pixel-loader #6605 — brought in for the status indicatorWhat's here (beyond the base)
collapsed.For reviewers (designers)
Open the Gen2 Storybook (dev) preview link CI posts below and try the Prompt field → Playground: toggle
variant(subtle / balanced / prominent),generating,collapsed,disabled, and edit the brand color.Not for merge
Once #6605 and the buttons branch land, the small integration + layout deltas here will be re-applied cleanly on the real branches.