From e6caf308b02076578e6074a6a856868a9ea82615 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 27 May 2026 22:51:38 +0200 Subject: [PATCH] =?UTF-8?q?refactor(words):=20refine=20drawer=20Editable?= =?UTF-8?q?=20=E2=80=94=20hover=20pencil=20+=20textarea=20+=20icon-only=20?= =?UTF-8?q?controls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback on the previous DRAWER-EDITABLE pass: the "Edit / Save / Cancel" labeled buttons were too heavy. The reference design is the Notion / Linear inline-edit pattern — resting state shows only the preview text, a small pencil reveals on hover, click opens a textarea with compact icon-only ✓ / ✕ controls beneath. Drawer refactor: - `activationMode="none"` + `submitMode="none"` — only the explicit triggers drive state. Removes accidental commits on blur / Enter (Enter must be a newline in a textarea anyway). - `Editable.Input` swapped to a textarea via the `child` snippet: `{#snippet child({ props })}`. ALT and CAPTION can run long; a single-line input was wrong. - `EditTrigger` / `SubmitTrigger` / `CancelTrigger` now hold only an icon (Pencil / Check / X) with `decorative` flag + `aria-label` on the trigger button for screen readers. - Wrapper attribute `data-words-drawer-editable` scopes all the custom CSS to these two Editables — other Editable instances in the app keep the canonical wrapper look. CSS (`words.css`, scoped to `[data-editable][data-words-drawer-editable]`): - Pencil hidden by default (opacity 0); revealed on `:hover` / `:focus-within` of the editable row. - Edit mode flips layout to column: textarea fills the row, the Control bar sits beneath, right-aligned. - Textarea has a real border + focus ring (was inheriting the ghost variant's transparent treatment, looked invisible against the drawer). - Submit chip is filled accent (✓ stands out), Cancel chip is outlined neutral. Both 28×28 square. - Preview placeholder ("Describe the image…") is italic + muted. Verified in browser: - Hover over ALT row → pencil appears at the right (opacity 0.7) - Click pencil → textarea visible with current value preselected - Below: purple ✓ + outlined ✕ - Type new value → click ✓ → `...` in the editor and the drawer preview both update; mode returns to preview. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/words/words-drawer.svelte | 56 ++++++-- src/uix/eidos/components/words/words.css | 130 ++++++++++++++++++ 2 files changed, 173 insertions(+), 13 deletions(-) diff --git a/src/uix/eidos/components/words/words-drawer.svelte b/src/uix/eidos/components/words/words-drawer.svelte index 593e71aa2..209dee25d 100644 --- a/src/uix/eidos/components/words/words-drawer.svelte +++ b/src/uix/eidos/components/words/words-drawer.svelte @@ -12,7 +12,15 @@ * Modes are derived in soma (`WordsDrawerProvider.modes`); eidos * just paints them. */ - import { ChevronRight, ChevronLeft, ChevronDown, ChevronUp } from '$uix/eidos/components/icon'; + import { + ChevronRight, + ChevronLeft, + ChevronDown, + ChevronUp, + Pencil, + Check, + X + } from '$uix/eidos/components/icon'; import { Editable } from '$uix/eidos/components/editable'; import * as Words from '$soma/components/words'; import type { @@ -557,10 +565,11 @@

{block.src}

Alt
- + + {#snippet child({ props })} + + {/snippet} + - Edit - Save - Cancel + + + + + + + + +
Caption
- + + {#snippet child({ props })} + + {/snippet} + - Edit - Save - Cancel + + + + + + + + + {#if block.status} diff --git a/src/uix/eidos/components/words/words.css b/src/uix/eidos/components/words/words.css index 1df3d8fcc..dba16f7b9 100644 --- a/src/uix/eidos/components/words/words.css +++ b/src/uix/eidos/components/words/words.css @@ -376,6 +376,136 @@ text-transform: uppercase; } +/* Drawer-scoped Editable — Notion/Linear-style inline edit. + Resting state shows the preview with a faded pencil to its right + (revealed on hover or focus-within). Edit mode swaps to a textarea + (alt/caption can run long) with icon-only Submit (✓) and Cancel (✕) + beneath, right-aligned. Per the user's reference design. */ +[data-editable][data-words-drawer-editable] { + display: flex; + inline-size: 100%; + max-inline-size: 100%; + align-items: center; + gap: 0.25rem; +} + +[data-editable][data-words-drawer-editable] [data-editable-area] { + flex: 1 1 auto; + min-inline-size: 0; +} + +[data-editable][data-words-drawer-editable] [data-editable-preview] { + inline-size: 100%; + min-block-size: auto; + white-space: normal; + line-height: 1.35; + color: var(--words-toolbar-color); +} + +[data-editable][data-words-drawer-editable] [data-editable-preview][data-placeholder-shown] { + color: var(--words-status-color); + font-style: italic; +} + +/* Pencil hides until the user signals intent (hover anywhere on the + editable row, focus-within, or focus on the preview itself). */ +[data-editable][data-words-drawer-editable] [data-editable-edit-trigger] { + flex: 0 0 auto; + inline-size: 1.5rem; + block-size: 1.5rem; + min-inline-size: 1.5rem; + padding: 0; + border: 0; + background: transparent; + color: var(--words-status-color); + opacity: 0; + transition: + opacity 120ms ease, + background-color 120ms ease, + color 120ms ease; +} + +[data-editable][data-words-drawer-editable]:hover [data-editable-edit-trigger], +[data-editable][data-words-drawer-editable]:focus-within [data-editable-edit-trigger] { + opacity: 0.7; +} + +[data-editable][data-words-drawer-editable] [data-editable-edit-trigger]:hover { + opacity: 1; + background: var(--words-command-hover-bg); + color: var(--words-command-active-color); +} + +/* Edit mode: stack vertically. Textarea fills the row, action chips + sit beneath right-aligned. */ +[data-editable][data-words-drawer-editable][data-editing] { + flex-direction: column; + align-items: stretch; +} + +[data-editable][data-words-drawer-editable][data-editing] [data-editable-area] { + inline-size: 100%; +} + +[data-editable][data-words-drawer-editable] textarea[data-editable-input] { + inline-size: 100%; + min-block-size: 4.5rem; + resize: vertical; + padding: 0.4rem 0.5rem; + border: var(--words-border-width) solid var(--words-toolbar-border); + border-radius: var(--words-command-radius); + background: var(--words-bg, var(--words-toolbar-bg)); + color: var(--words-toolbar-color); + font: inherit; + line-height: 1.35; + outline: none; +} + +[data-editable][data-words-drawer-editable] textarea[data-editable-input]:focus-visible { + border-color: var(--_words-accent-border); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent-solid) 22%, transparent); +} + +[data-editable][data-words-drawer-editable][data-editing] [data-editable-control] { + display: inline-flex; + justify-content: flex-end; + gap: 0.25rem; + margin-block-start: 0.35rem; +} + +/* Icon-only submit / cancel chips. Square, compact. */ +[data-editable][data-words-drawer-editable] [data-editable-submit-trigger], +[data-editable][data-words-drawer-editable] [data-editable-cancel-trigger] { + flex: 0 0 auto; + inline-size: 1.75rem; + block-size: 1.75rem; + min-inline-size: 1.75rem; + padding: 0; + border-radius: var(--words-command-radius); +} + +[data-editable][data-words-drawer-editable] [data-editable-submit-trigger] { + border: 0; + background: var(--_words-accent-solid); + color: var(--_words-accent-on); +} + +[data-editable][data-words-drawer-editable] [data-editable-submit-trigger]:hover { + background: color-mix(in srgb, var(--_words-accent-solid) 85%, white 0%); + filter: brightness(0.95); +} + +[data-editable][data-words-drawer-editable] [data-editable-cancel-trigger] { + border: var(--words-border-width) solid var(--words-toolbar-border); + background: transparent; + color: var(--words-status-color); +} + +[data-editable][data-words-drawer-editable] [data-editable-cancel-trigger]:hover { + border-color: var(--words-threat-text); + color: var(--words-threat-text); +} + /* Drawer header trail — shows the breadcrumb of stacked modes after the leaf so the user knows what scopes are stacked underneath. */ [data-words-drawer-trail] {