Skip to content

docs(useInputContainer): add a search field wrapper example block - #9619

Merged
cixzhang merged 7 commits into
meta:mainfrom
MannXo:docs/9568-useinputcontainer-example
Nov 5, 2026
Merged

cixzhang merged 7 commits into
meta:mainfrom
MannXo:docs/9568-useinputcontainer-example

Conversation

@MannXo

@MannXo MannXo commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Reader impact

The useInputContainer docs page has no runnable example. After this change it shows a custom search field wrapper with a leading search icon, padding and a trailing Clear button. Clicking the icon or the padding puts focus in the input, and clicking Clear empties the field without the wrapper taking over the click. astryx template useInputContainerHookUsage <path> copies it.

Source of truth

  • useInputContainer and its HookDoc (packages/core/src/hooks/useInputContainer.ts, useInputContainer.doc.mjs), including its @example wrapper.
  • InputClearButton (packages/core/src/Field/InputClearButton.tsx), the clear button Core's inputs share, and TextInput's clear handler, which returns focus to the input after clearing. The example's Clear does the same.
  • architecture:knowledge-contracts INV28.

Correction

Adds blocks/components/Hooks/useInputContainerHookUsage (exampleFor: 'useInputContainer'). A div wrapper holds a search Icon, a text input and, while the field has text, Core's InputClearButton labelled "Clear search". useInputContainer({containerRef, inputRef}) returns onClick and onMouseUp, and both go on the wrapper. The styles are the minimum that makes the div read as one field: a flex row with padding, a border and a radius, a focus ring from focusVars drawn only while the input itself is :focus-visible (so a focused Clear shows just its own ring), and an unstyled input inside it.

Evidence

  • Real Chromium (Playwright against the docsite dev server, /components/useInputContainer, 1280x800), run before Clear moved to InputClearButton and not re-run since. With focus outside the example, a click on the search icon focuses the input. With focus outside again, a click 4px inside the wrapper's right edge (padding) focuses the input. After typing "running shoes", a click on Clear leaves the input empty and focused, and the Clear button is gone.
  • Test (packages/cli/api/template/use-input-container-hook-usage.test.tsx, 3 cases). Asserts focus after a click on the icon and on the wrapper, and that a click on Clear empties the field and calls the input's focus exactly once (Clear's own call). With the useInputContainer call removed, the first two fail. With a plain onClick={() => input.focus()} wrapper instead of the hook, the Clear case fails on the second focus call.

Scope

  • This PR changes documentation only, or names every necessary generated artifact. The generated artifacts are the new block's route in internal/shadcn-registry/routes.lock.json its sandbox route in apps/sandbox/scripts/next-export-routes.json, and the search icon, search icon set and search search goldens in packages/cli/test/__golden__/no-icon-set/.
  • Runtime, API, visual, and policy changes were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

  • vitest run --project node packages/cli/foundation/discovery packages/cli/api/template packages/cli/assets/templates internal/shadcn-registry apps/sandbox/src/route-manifest.test.ts packages/cli/api/build/tree.test.mjs: 1168 passed, 0 failed.
  • apps/docsite: pnpm generate, then vitest run src/__tests__/data-extraction.test.ts src/__tests__/example-coverage.test.ts: 135 passed, 0 failed. The route lock check is clean after UPDATE_SHADCN_ROUTE_LOCK=1 added the new route.
  • vitest run --project node packages/cli/test/experimental-isolation.test.mjs packages/cli/test/template-icon-declarations.test.mjs packages/cli/api/component/resolution-compat.test.mjs: passes. The block's search icon adds one match to the search icon, search icon set and search search goldens.
  • pnpm lint:strict: clean.
  • pnpm -F @astryxdesign/cli typecheck: clean.
  • node scripts/release/changeset-coverage.mjs pr --base upstream/main --head HEAD --released v0.7.8: passes. Changeset is @astryxdesign/cli patch, [docs].
  • Not run: the full pnpm test suite.

Fixes #9568

@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 5, 2026 6:57pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 5, 2026, 8:53 PM UTC

@github-actions github-actions Bot added community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge needs:design-review Affects visuals — Design should review labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 334ac76

Storybook · Sandbox · built from this PR's head

Package size: @astryxdesign/cli 3.38 MB packed (+624 B); no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 19 rows, core 23 rows; run pnpm api:report.

Not run on this PR: add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 2, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the useInputContainer example: the hook visibly drives it, and removing the hook call breaks the icon and padding behaviour as intended. Three things need fixing before it can merge:

  • Add icons: {MagnifyingGlassIcon: 'search', XMarkIcon: 'close'} to useInputContainerHookUsage.doc.mjs, because every bundled block declares the icons it imports and packages/cli/test/template-icon-declarations.test.mjs fails without it.
  • Refresh and commit the search-icon, search-search and search-icon-set goldens (.json and .txt) in packages/cli/test/__golden__/no-icon-set/, which the new block changes and which fail four tests in packages/cli/test/experimental-isolation.test.mjs.
  • Ring the field only while the input has keyboard focus (for example a :has(> input:focus-visible) outline built from focusVars), because focusOutlineStyles.focusWithin also rings the field while the Clear button shows its own ring.

Running the packages/cli tests locally catches the first two.

[Automated review]

'A custom search field wrapper with a leading search icon, padding and a trailing Clear button. useInputContainer puts focus in the input when the icon or the padding is clicked, while a click on Clear empties the field without the wrapper taking it over.',
isReady: true,
aspectRatio: 4 / 3,
componentsUsed: ['Button', 'Icon'],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declare the two Heroicons here, so a project's icon set can replace them and packages/cli/test/template-icon-declarations.test.mjs passes:

icons: {MagnifyingGlassIcon: 'search', XMarkIcon: 'close'},

ref={containerRef}
onClick={onClick}
onMouseUp={onMouseUp}
{...stylex.props(styles.field, focusOutlineStyles.focusWithin)}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focusOutlineStyles.focusWithin matches any focused descendant, so when Clear has keyboard focus the field and Clear both show a ring. Scope the field's ring to the input instead, for example:

field: {
  // ...
  outlineWidth: {default: 0, ':has(> input:focus-visible)': focusVars['--focus-outline-width']},
  outlineStyle: {default: 'none', ':has(> input:focus-visible)': focusVars['--focus-outline-style']},
  outlineColor: {default: null, ':has(> input:focus-visible)': focusVars['--focus-outline-color']},
  outlineOffset: {default: 0, ':has(> input:focus-visible)': focusVars['--focus-outline-offset']},
},

focusVars comes from @astryxdesign/core/theme/tokens.stylex.

… only for the input

The field's ring now keys off the input's focus-visible, so a focused Clear button shows only its own ring. The search goldens count the new block.
# Conflicts:
#	apps/sandbox/scripts/next-export-routes.json
#	internal/shadcn-registry/routes.lock.json
#	packages/cli/test/__golden__/no-icon-set/search-icon-set.json
#	packages/cli/test/__golden__/no-icon-set/search-icon-set.txt

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround: the icon declaration, the refreshed goldens and the input-only focus ring are all in and the example behaves as described. One change is needed before it can merge:

  • Miss: render Clear with InputClearButton from @astryxdesign/core/Field instead of the hand-built ghost Button in useInputContainerHookUsage.tsx (lines 83-93), because the copy loses the shared clear button's "Clear" tooltip, its theme targets and the press handling that keeps focus in the input; then drop the Button and XMarkIcon imports and the XMarkIcon: 'close' entry in icons.

Separately, the brace-only edit to internal/interaction-harness/src/busy.ts is unrelated to the example and can be dropped. CI has not run on this head yet; a maintainer adds the ci:run label to run it.

[Automated review]

Comment on lines +83 to +93
<Button
label="Clear"
variant="ghost"
size="sm"
icon={<Icon icon={XMarkIcon} />}
isIconOnly
onClick={() => {
setQuery('');
inputRef.current?.focus();
}}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core already ships this control: InputClearButton from @astryxdesign/core/Field is the clear button every Core input uses, with the localized "Clear" tooltip, the astryx-input-clear-* theme targets and a press that keeps focus in the input. Suggest:

<InputClearButton
  label="Clear search"
  onClick={() => {
    setQuery('');
    inputRef.current?.focus();
  }}
/>

Then drop the Button and XMarkIcon imports and the XMarkIcon: 'close' entry in icons (and update componentsUsed).

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved pending CI: CI has not run on this head yet, so a maintainer adds the ci:run label to run it.

Thanks for switching Clear to InputClearButton: the example now uses the same clear control as every Core input, and the tests show the hook drives the icon and padding clicks while Clear keeps its own.

[Automated review]

@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 5, 2026
@cixzhang cixzhang closed this Nov 5, 2026
@cixzhang cixzhang reopened this Nov 5, 2026

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved: the useInputContainer search field example meets the hook-example rule, its tests show the hook drives the icon and padding clicks while Clear keeps its own, and CI passed on this head.

Thanks for moving Clear to InputClearButton and keeping the field's ring on the input; in Chromium only Clear rings once it has keyboard focus.

[Automated review]

@github-actions github-actions Bot removed the needs:code-review High-risk change (new package/component/API) — needs human code review before merge label Nov 5, 2026
@cixzhang
cixzhang merged commit 7b87e12 into meta:main Nov 5, 2026
65 checks passed

This branch was successfully deployed

1 active deployment
Preview — 334ac768 Deployed Nov 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot. community Authored by a community contributor (not on the eng/design team) needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(useInputContainer): add a minimal live example

2 participants