feat(web): make the whole no-workspace composer card the picker trigger

The pick target grows from the read-only textarea to the full capsule:
the card owns the click, its disabled controls let pointer events fall
through, and a dashed l4 stroke with a business-blue hover marks the
affordance (an SVG dash ring — native dashed has a fixed pattern). The
card contains pointerdown so the open picker's outside-close cannot
race the reopen, which flickered the chip's expansion echo.
This commit is contained in:
Yif
2026-08-07 17:06:01 +08:00
parent c44c6c93c8
commit 0a248bcea8
9 changed files with 71 additions and 14 deletions
@@ -102,6 +102,40 @@
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
}
/* No-workspace trigger state: dashed l4 stroke marks the card as a pick-a-
workspace affordance rather than a live composer; hover answers in the
business blue to invite the click. Native `dashed` has a fixed browser
pattern, so the stroke is an ::after overlay: theme-token background masked
by an SVG dash ring (stroke-width 2 centered on the box edge = 1px visible
inside), which keeps the 22px radius and both themes. */
.cardWorkspaceTrigger {
border-color: transparent;
cursor: pointer;
}
.cardWorkspaceTrigger::after {
content: '';
position: absolute;
inset: -1px;
border-radius: 22px;
background: var(--dsw-alias-border-l4);
transition: background-color 100ms ease;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='none' rx='22' ry='22' stroke='black' stroke-width='2' stroke-dasharray='4 4'/%3E%3C/svg%3E");
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='none' rx='22' ry='22' stroke='black' stroke-width='2' stroke-dasharray='4 4'/%3E%3C/svg%3E");
pointer-events: none;
}
/* Disabled toolbar controls neither receive nor swallow clicks in the trigger
state: pointer events fall through to the card's own click handler, making
the full capsule one pick target. */
.cardWorkspaceTrigger :disabled {
pointer-events: none;
}
.cardWorkspaceTrigger:hover::after {
background: var(--dsw-alias-state-business-primary);
}
.accessory {
display: flex;
align-items: center;
@@ -467,7 +467,17 @@ export function InputBar({
{notice.text}
</div>
)}
<div className={css.card} data-composer-card>
{/* Trigger clicks land on the card, not the textarea: the toolbar row's
disabled controls swallow clicks otherwise (the CSS state disarms
their pointer events), so the WHOLE capsule is the pick target.
pointerdown stops here so the Menu's outside-close cannot race the
click's reopen (close-then-open flickers the chip's open echo). */}
<div
className={clsx(css.card, workspaceTrigger && css.cardWorkspaceTrigger)}
data-composer-card
onClick={workspaceTrigger ? onRequestWorkspace : undefined}
onPointerDown={workspaceTrigger ? (e) => { e.stopPropagation() } : undefined}
>
{overlay !== undefined && <div className={css.overlayAnchor}>{overlay}</div>}
{accessory !== undefined && <div className={css.accessory}>{accessory}</div>}
{/* One scrollport, two text layers. The hidden mirror renders draft+'\n' and stretches the
@@ -496,7 +506,6 @@ export function InputBar({
rows={2}
onChange={onChange}
onKeyDown={onKeyDown}
onClick={workspaceTrigger ? onRequestWorkspace : undefined}
onSelect={onSelect}
onCopy={(e) => { onCopyOrCut(e, false) }}
onCut={(e) => { onCopyOrCut(e, true) }}