Skip to content

feat(input): Input primitive (shadcn v4) + gallery showcase#51

Merged
SiphoChris merged 6 commits into
mainfrom
feat/flutterbits-input
Jun 15, 2026
Merged

feat(input): Input primitive (shadcn v4) + gallery showcase#51
SiphoChris merged 6 commits into
mainfrom
feat/flutterbits-input

Conversation

@SiphoChris

Copy link
Copy Markdown
Owner

Input — the form-cluster keystone (4th primitive)

Ships Input at apps/gallery/lib/components/ui/input.dart — a Material-free, themeable, shadcn-v4-faithful single-line text field, the repo's first text input. shadcn's Input is a styled <input>; the Flutter-faithful equivalent is a styled EditableText (the widgets.dart text primitive — TextField is Material and off-limits) wrapped in one .tw box, with a managed controller/focusNode, a placeholder overlay, a focus ring, and invalid/disabled states.

shadcn v4 class mapping

Base <input>: h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base outline-none placeholder:text-muted-foreground disabled:opacity-50 disabled:pointer-events-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 …

shadcn flutterbits
h-9 w-full rounded-md border-input bg-transparent px-3 .tw.bg(transparent).border(1,color:input).roundedMd.px(3).py(2).minH(9).wFull
text-base placeholder:text-muted-foreground EditableText @ FwFontSize.base.px (16); Text overlay in mutedForeground when empty
focus-visible:border-ring ring-[3px] ring-ring/50 focused ⇒ border ring + .ring(3, color: ring @0.5)
aria-invalid:border-destructive ring-destructive/20 invalid ⇒ border destructive (+ destructive @0.2 ring when focused)
disabled:opacity-50 pointer-events-none .opacity(0.5) + IgnorePointer + ExcludeFocus + EditableText.readOnly

Design decisions

  • No variant/size enums (shadcn's Input has neither). State is boolean props: enabled, invalid.
  • Managed controller/focusNode — pass your own or omit them; the widget creates and disposes only its own internal ones (an external controller/focusNode is never disposed; covered by a swap-in/out regression test that asserts the external survives).
  • Block-level (w-full) — requires a bounded-width parent (documented), like Card.
  • Disabled is truly non-focusableExcludeFocus removes it from keyboard traversal (not just IgnorePointer, which blocks pointer only) without mutating a caller-owned focusNode; assert(!autofocus || enabled) guards the misuse.
  • AccessibilityMergeSemantics + Semantics(label:) collapses the field's name + value + text-field role into one node, so a screen reader announces "name, edit text, value" (deliberately not excludeSemantics — unlike Button/Badge, an input's value must announce alongside its name; covered by a test asserting both the label and the value are reachable).

Faithful deviations (documented, not bugs)

shadow-xs, md:text-sm, and dark:bg-input/30 are dropped (an input's depth is its border+ring; 16px is the mobile-first default; the dark fill needs a brightness-only token knob we don't have — same precedent as Badge's dropped dark:bg-destructive/60). Selected text keeps its foreground color. Not yet built (feasible, §11b): a custom selection toolbar/handles (TextSelectionControls) and a multi-line Textarea sibling — v1 ships keyboard + basic selection.

Tests

  • 13 behavior tests: text entry; placeholder toggle; border token per state; focus ring + border-ring; disabled (read-only + IgnorePointer + opacity) and non-focusable (ExcludeFocus); obscure; onChanged/onSubmitted; semantics (name + value both reachable); managed-controller lifecycle (self-managed + external swap, asserting the external survives); RTL; dark reskin.
  • Golden grid (placeholder / typed / focused-with-ring / invalid / disabled × light/dark/RTL), with EditableText.debugDeterministicCursor for a stable caret.
  • Rendered in apps/gallery (Inputs section) + smoke-tested. Charter §8 progress note (no-drift).

Goldens are provisional (Windows); they'll be re-baselined from the CI Linux gallery-golden-failures artifact if the gallery job's golden step diffs (Badge needed none; the focused row's ring/caret may differ).

Executed via writing-plans → subagent-driven-development (implementer + spec-review + code-quality-review; review findings triaged and applied — ExcludeFocus, MergeSemantics, autofocus assert, lifecycle/value test guards, per-keystroke setState guard).

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flutterbits Ready Ready Preview, Comment Jun 15, 2026 12:37pm

@SiphoChris SiphoChris merged commit 73bd7f6 into main Jun 15, 2026
8 checks passed
@SiphoChris SiphoChris deleted the feat/flutterbits-input branch June 15, 2026 12:39
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