Skip to content

Dynamic values: TUI visual refresh (modal, SearchSelect picker, pills) - #89

Open
juliacanzani wants to merge 7 commits into
mainfrom
feat/dynamic-value-tui
Open

Dynamic values: TUI visual refresh (modal, SearchSelect picker, pills)#89
juliacanzani wants to merge 7 commits into
mainfrom
feat/dynamic-value-tui

Conversation

@juliacanzani

Copy link
Copy Markdown
Contributor

What

The dynamic-values UI gets its TUI facelift — reskin only: the ProseMirror editor mechanics (click-to-select, backspace deletion, kebab/double-click edit, replace-on-type), the settings state machine (parse-on-open, delimiter stripping, settingsRef), and the [[type::key=value]] token format are all untouched.

Pairs with @tangible/ui@0.2.9, which shipped SearchSelect (tangible-ui#81) — the transient-search picker built for exactly this dropdown.

The pieces

Settings modal — the react-aria overlay shell becomes TUI Modal (portalled into a context-classed container so tf-context-* theming still applies inside the portal, same trick as the old wrapper). The body — previously bespoke native radios and a raw text input with zero CSS — is now RadioGroup + Field/TextInput, and the value picker is SearchSelect: a select-shaped trigger showing the committed value as a bolt + Category + label pill, opening a searchable grouped panel (the design's "Choose dynamic value dropdown").

Inline chip (ProseMirror node view) — markup unchanged; styles tokenised onto the TUI primary scale (old indigo hex kept as fallbacks, so nothing shifts until a context remaps), the ⚡ emoji swapped for a currentColor SVG bolt, forced-colors support added. The pill styles moved to a shared partial — replace-mode fields never loaded the editor stylesheet they lived in.

Replace mode — the disabled-text-input-showing-a-label becomes a dynamic-value pill on a tinted input-shaped bar. The insert/clear icon buttons swap their baked-dark base64 bolt (invisible in dark contexts) and text-glyph × for mask-based TUI icons that follow currentColor.

Verified

  • Live storybook journeys screenshotted: insert flow (modal → SearchSelect → pill lands in the editor), replace flow (popover pick → tinted pill bar with real ×), picker search/grouping.
  • jest: 506 passing (the dynamic render tests updated for the TUI modal chrome — .tui-modal container, backdrop dismissal, SearchSelect picker).
  • Bundles rebuilt (assets/build/*).

🤖 Generated with Claude Code

juliacanzani and others added 2 commits August 15, 2026 13:50
Reskin only — the ProseMirror mechanics, settings state machine, and
token format are untouched.

- Settings modal: react-aria overlay shell -> TUI Modal (portalled into
  a context-classed container so tf-context-* theming still applies);
  bespoke unstyled radios/input -> RadioGroup + Field/TextInput; the
  value picker is TUI's new SearchSelect (button trigger showing the
  bolt + category + label pill, transient search panel, grouped list)
- Inline chip (ProseMirror node view): tokenised styles (TUI primary
  scale with the old indigo as fallbacks) and a currentColor SVG bolt
  instead of the emoji; forced-colors outline; pill styles moved to a
  shared partial (replace-mode fields never loaded the editor styles)
- Replace mode: the disabled-input-with-label becomes a dynamic-value
  pill on a tinted input-shaped bar; the insert/clear icon buttons use
  mask-based TUI icons that follow currentColor (the old base64 bolt
  was baked dark and vanished in dark contexts; the clear was a text
  glyph pseudo-element)
- TangibleUI: bump to ^0.2.9 (SearchSelect + shared listbox engine)
- jest: dynamic render tests updated for the TUI modal chrome
  (.tui-modal container, backdrop dismissal, SearchSelect picker) —
  506 passing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-ups from Julia's review of the first reskin pass:

- Insert/clear affordances are TUI IconButtons (ghost/secondary) sitting
  in tui-input-group suffix APPEND slots — field + affordances read as
  one control. The floating absolutely-positioned buttons, their mask
  pseudo-elements, and the editor's 25px padding compensations are gone.
- Replace mode no longer opens the popover-with-a-combobox (the 'weird
  double dropdown'): the bolt IconButton opens TUI SearchSelect directly,
  anchored under the whole field, via the new external-trigger mode
  (anchorRef/restoreFocusRef, @tangible/ui 0.2.10). Values that declare
  settings still route to the legacy settings popover after picking.
- BaseWrapper's ComboBox picking branch removed; the popover now exists
  solely for per-value settings forms.
- TangibleUI: bump to ^0.2.10.
- jest: role-based queries for the icon-button affordances, SearchSelect
  panel recognised as the picker container — 506 passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliacanzani

Copy link
Copy Markdown
Contributor Author

Second pass per Julia's review: insert/clear are now ghost/secondary TUI IconButtons in input-group suffix append slots (no more floating buttons or mask pseudo-elements), and replace mode's bolt opens the SearchSelect panel directly anchored under the field — the popover-with-a-combobox double dropdown is gone. Uses @tangible/ui@0.2.10's new SearchSelect external-trigger mode (anchorRef/restoreFocusRef). Values with settings still route to the legacy settings popover after picking. 506 jest tests passing; flows re-verified live in storybook.

…l; editable replace input

Three review notes from Julia:

- The bolt-plus now ALWAYS opens the SearchSelect insert panel — in the
  editor too, which previously jumped straight to the settings modal.
  The modal is reserved for editing (kebab/double-click) and for picked
  values that declare settings, where it opens prefilled with the picked
  value and its settings sub-form.
- The legacy inline settings popover ('the old modal' look) is gone
  entirely — BaseWrapper routes settings values to the same
  DynamicFieldSettings modal, and its react-aria overlay machinery,
  Control/Title rendering, and popover styles are deleted. The grouped
  choice list is now built once in dynamic/choices.ts, shared by the
  editor, BaseWrapper, and the modal.
- Replace mode's static input is editable until a dynamic value
  overrides it (was hard-coded readOnly).

506 jest tests passing; all flows re-verified live (insert→panel→inline
pill, settings value→prefilled modal in both modes, zero legacy popover,
static typing in replace mode).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliacanzani

Copy link
Copy Markdown
Contributor Author

Third pass: the bolt-plus always opens the SearchSelect insert panel (editor included — it previously jumped straight to the settings modal); the settings modal now only appears for editing and for picked values that declare settings, prefilled. The legacy inline settings popover is deleted entirely (BaseWrapper routes to the same modal), and replace mode's static input is editable until a dynamic value overrides it. Choice-list building is deduplicated into dynamic/choices.ts. 506 jest green, all flows verified live.

juliacanzani and others added 4 commits August 15, 2026 14:51
- Custom key input works: TUI TextInput is a native wrapper (onChange,
  not onValueChange — the handler was silently falling into rest and the
  controlled input never updated; fields has no tsc gate to catch it)
- The insert panel gains a 'Custom value…' row — with the bolt now always
  opening the panel there was no path to CREATE a custom key; the row
  opens the settings modal straight into custom mode (new defaultMode
  prop on DynamicFieldSettings)
- Number/color/date use the slotted icon-button affordances too:
  FieldWrapper defaults to buttonType 'inside', and the group renders
  chrome-less around controls that own their borders (border/tint kept
  only for the text-replace input and the dynamic pill bar)
- jest updated: clear affordance only shows once a dynamic value is set
  (was outside-mode's always-visible disabled Clear) — 506 passing

Verified live: custom flow end to end (panel row → custom-mode modal →
typing → token inserted), slotted bolts on number/color/date with no
double chrome.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three stacked height bugs, all measured live:

- Appended IconButtons scale WITH the host control: one visual step down
  (control minus group padding/border, 40 -> 30), clamped to the 24px
  WCAG target floor. Styled rather than a fixed t-shirt size so context
  control-height remaps (Elementor's 28px md) can't overflow the group —
  sm's 32px was stretching 40px groups to 42px.
- The pill display no longer stacks its own min-height/padding on the
  group's (40 -> 48); the group owns the control height.
- Single-line editor: .tf-dt-line stays display:block — forcing it inline
  made ProseMirror's trailing <br> a second line box, so an EMPTY editor
  was 48px and jumped to 24px on the first keystroke. Empty and typed
  both sit at 40px now.

Verified: static input group 40, dynamic pill bar 40, editor empty/typed
40, chrome-less composed groups 40, appended buttons 30. 506 jest green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pill margins for breathing room in the editor line, larger bolt glyph
(1.25em), selected-pill outline on the focus-ring tokens, append-slot
inline inset, tidier in-group display reset.
The plain paths sized correctly but dynamic: true silently locked fields
to md. Now:

- Text threads size into DynamicEditor; DynamicEditor accepts it,
  classes its input group (is-size-*), and forwards to FieldWrapper →
  BaseWrapper (which classes the tf-dynamic-group). Number/color/date
  already spread props into FieldWrapper, so their size flows for free.
- The appended-affordance scale follows the ladder via a per-size
  --tf-dynamic-control on the group (sm 36→26, md 40→30, lg 48→38),
  still clamped at the 24px target floor and still tracking context
  height remaps.
- Pill font/paddings ride the group's control font (em-based), so the
  replace bar scales with its tier.
- New 'Sizes (sm/md/lg)' story covering insert + replace; jest asserts
  is-size-* lands on the group in both modes — 508 passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant