Skip to content

Scripture text picker prototype + LanguageMultipicker (design exploration)#2282

Draft
merchako wants to merge 3 commits into
pt-3976-shared-resource-picker-uifrom
pt-3976-scripture-text-picker
Draft

Scripture text picker prototype + LanguageMultipicker (design exploration)#2282
merchako wants to merge 3 commits into
pt-3976-shared-resource-picker-uifrom
pt-3976-scripture-text-picker

Conversation

@merchako
Copy link
Copy Markdown
Contributor

@merchako merchako commented May 15, 2026

Summary

Design-exploration prototype on top of pt-3976-shared-resource-picker-ui. Sketches a redesigned scripture-text picker and extracts the language multi-select filter as its own component.

Not for merge as-is. Draft so reviewers can poke at the Storybook variants and feed back. The eventual outcome may be folding the best ideas into the existing ResourcePickerDialog rather than landing this folder verbatim.

Screenshots

In context — single-select, populated project (display-switcher mode)

Default view = Included only. Footer toggles to "Browse all texts (4)". Currently-displayed item gets a Check; locked item gets a Lock icon.

Trigger · single · populated

In context — single-select, sparse project (add-mode)

Same component, but the initial state has only one included text, so the picker opens in "Browse all" view. Footer reads "← Show only included". Status icons distinguish Lock / HardDrive / Cloud.

Trigger · single · sparse

Bare component — single populated

The picker without the surrounding mock viewer.

Single populated

Container-query responsive collapse — narrow (340 px)

Full names collapse to abbreviations (NIV, RVR60, ESV), language chips collapse to ISO codes. Tooltips carry the full info on hover.

Narrow width

LanguageMultipicker popover

Standalone component, popover open. Each row shows English/ISO name + autonym (Español, Français, Deutsch, Português, עברית, Ἑλληνικά) + 3-letter code.

Language multipicker popover

What's in here

New: LanguageMultipicker (lib/platform-bible-react/src/components/advanced/language-multipicker/)

Tertiary, compact multi-select language filter. Designed to sit next to a search input and stay visually quiet until interacted with.

  • Trigger label degrades by width / count: AnyPreferred → single ISO code → space-separated codes → N langs.
  • value / options / preferred / onChange API.
  • Bundled LanguageInfo shape matches ui-language-selector.component.tsx (autonym, otherNames).
  • Storybook coverage of the label-degradation states.

Known gaps for promotion to a system-wide content-language picker: should accept LanguageInfo[] (or an async loader) instead of just names; needs virtualization + in-popover search to handle thousands of languages; needs RTL autonym handling. Called out in the component's TSDoc.

New: ScriptureTextPicker (lib/platform-bible-react/src/components/advanced/scripture-text-picker/)

Single-component scripture-text picker exploring:

  • Dual purpose — both a project-membership manager and a "what's currently displayed on the calling surface" switcher. The host owns displayedIds: string[]; same picker serves single-select (host keeps ≤ 1 id) and multi-select (host toggles in a set).
  • Three statuses — Included / Installed / Available to download. Click an Included row → toggleDisplay; click Installed/Available → include. Available items get a download spinner. Locked-included items show a Lock icon with explanatory tooltip and cannot be removed.
  • Footer view toggle — "Browse all texts (N)" / "Show only included". Initial view is captured at open time based on whether the project is sparse (≤ 1 included → start in Browse all) or populated (start in Included only). After open, only the footer button or active search changes the view.
  • Listbox keyboard navigation — Arrow keys, Home/End, Enter/Space activate, Delete removes a non-locked included row. Focus is restored after re-render via a pendingFocusIdRef + useLayoutEffect, so activating an Installed row doesn't drop focus when the row remounts into the Included group.
  • Responsive collapse via container queries — full name → abbreviation at <360px; language name → monospace ISO code at <480px. Tooltips carry full info plus autonym.
  • Clickable language chip — clicks the chip on any row sets the language filter to that one language.
  • "Maybe you meant…" — when a search returns few in-filter results, surfaces out-of-filter matches in a muted section below.
  • Three distinct row visual states — selected (bg-accent), focused (inset ring, no background change), hovered (bg-muted). Selected + focused at the same time stays distinguishable.

Storybook

  • Advanced/ScriptureTextPicker — pure-component stories (Single/Multi × sparse/populated, narrow-width, short-height) all rendered in a resizable shell (drag the bottom-right corner). Three trigger stories show the picker in a popover from a mocked scripture viewer.
  • Advanced/LanguageMultipicker — minimal coverage of trigger-label states.

Background

Iterated on three variants (unified scroll, tabs, progressive disclosure) and converged on progressive disclosure with a footer view toggle. The collapsing inline expander was dropped in favor of the toggle because it was visually confusing when expanded.

The existing ResourcePickerDialog on the parent branch is untouched.

Test plan

  • Open Storybook (npm run storybook:platform-bible-react) and visit the new Advanced/ScriptureTextPicker stories.
  • Verify the resizable shell exercises container-query collapse (drag wider/narrower past 480px and 360px).
  • Verify trigger stories: popover does not close on include/toggleDisplay/remove — only on outside click or Esc.
  • Verify keyboard navigation: Arrow, Home/End, Enter, Space, Delete. Focus stays in the list when activating items that change group.
  • Verify focus vs selected styling stays distinct on rows that are both currently displayed and keyboard-focused.
  • Verify language chip hover/active states feel clickable.
  • Confirm Lock icon tooltip explains locked items can't be removed.
  • Confirm "Browse all" / "Show only included" toggle behavior, and that adding items doesn't auto-flip the view mid-session.

Follow-ups (not in this PR)

  • Decide whether to fold these patterns into the existing ResourcePickerDialog rather than land a parallel component.
  • Promote LanguageMultipicker toward a system-wide content-language picker (large-list handling, async metadata, RTL).
  • Convert mock language metadata in language-info.ts to a real source (IETF/ISO + autonym dataset).
  • Decide policy on include auto-display behavior — the host stories auto-display when nothing is displayed; that's host policy not picker policy.

This change is Reviewable

merchako added 3 commits May 15, 2026 18:22
Design-exploration prototype for replacing the current ResourcePickerDialog with
a Scripture-text-focused picker. Pulls the language multi-select filter out as
its own component (LanguageMultipicker) so it can later evolve into a system-
wide content-language picker.

Picker behavior: progressive disclosure with a footer toggle between
"Included only" and "Browse all". Single- and multi-select via host-owned
displayedIds. Listbox keyboard navigation (Arrow/Home/End/Enter/Space/Delete)
with focus restoration after action-driven re-renders. Container-query
responsive collapse for name and language. Click a language chip to filter by
it. "Maybe you meant…" out-of-filter results when in-filter is sparse. Lock
icon (with tooltip) for non-removable items.

Leaving dist/ and package-lock.json out of this commit — they were touched by
the local build workaround for the stale-dist issue that origin already
fixed.
Wraps the prototype with documentation and authoring affordances meant to
preserve the reasoning behind design choices once this thread ends.

Component docs
- Rich TSDoc on ScriptureTextPickerProgressive covering: what it does, the
  two-job (membership vs display) framing, why it's presentational, the
  keyboard model, sizing/responsive behavior, variants, examples.
- LanguageMultipicker TSDoc rewritten for newcomers: explains "Preferred"
  preset, references BCP-47, calls out gaps before promotion to system-wide.

Localization
- New *.strings.ts modules colocated with each component. Each string key has
  a `// meaning:` comment describing intent and constraints. English defaults
  serve as the reference implementation; components accept an optional
  `localizedStrings` prop that falls back to defaults. Mirrors the
  existing ResourcePickerDialog pattern.

Storybook
- ScriptureTextPicker gets a `Playground` story with full controls (dataset,
  mode, autoDisplayOnInclude, shell size).
- Every story has a `parameters.docs.description.story` explaining why that
  variant exists.
- LanguageMultipicker stories use `autodocs` and describe each label
  degradation state.
- Removed the "Drag the bottom-right corner ↘ to resize" hint bar from the
  resizable shell. Replaced with a small `↘` glyph over the native CSS
  resize grip plus a `title="Drag ↘ to resize"` tooltip on the container.

Sizing
- Picker shrinks to fit its content (no `flex-1` to fill the host). List
  area capped at `max-h-[24rem]`. When the user is actively typing in the
  search, the list area takes `min-h-[14rem]` to prevent jitter as results
  narrow.

Design patterns
- New DESIGN.md co-located with the scripture text picker, capturing 17
  patterns we landed on along the way — each as
  pattern/chosen/rejected. Includes a "Where this doc should live" section
  suggesting alternative locations (`lib/platform-bible-react/decisions/`,
  `docs/design-system/`, ADRs, external wiki).
- Doc lives next to the component because `lib/platform-bible-react/docs/`
  is gitignored (typedoc target).

BCP-47 alignment
- Mock language data changed from 3-letter ISO 639-3 codes (`eng`, `spa`)
  to BCP-47 conventional forms (`en`, `es`, `de`, etc; `grc` retained for
  Koine Greek which has no 639-1). Aligns with the rest of platform-bible.

Screenshots regenerated to reflect new shrink-to-fit behavior.
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