feat(ui): separate focus states for SearchForm input and button - #461
Conversation
Rework SearchForm so the input and button are composed side by side as two independent controls instead of being wrapped in a single bordered control with one shared focus-within ring. - Drop the unified control border/background/overflow and shared focus-within outline. Each control now uses its own default focus ring (input ring on the input, button ring on the button), so focusing one no longer highlights the whole group. - Strip the touching corners with logical radius utilities (input rounded-e-none, button rounded-s-none) so the joined state reads as one unit while keeping each control's own border. - Add a `gapped` boolean prop: when true, inserts an 8px gap between the input and button (new --spacing-search-form-gapped token) and restores the full rounded corners on both controls. - Pin the button to the shared form-control height per size so it always matches the input height across sm/md/lg (the Button atom sizes lg by padding alone). - Update stories (gapped control + Gapped story) and Figma Code Connect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JvsHgQRBmsL5otpYzsL2Q8
Changed Files
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
🧰 Additional context used📓 Path-based instructions (5)libs/ui/**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
libs/ui/stories/**/*.stories.{tsx,ts}📄 CodeRabbit inference engine (libs/ui/AGENTS.md)
Files:
libs/**📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
libs/ui/src/**/*.{tsx,ts}📄 CodeRabbit inference engine (libs/ui/AGENTS.md)
Files:
🧠 Learnings (6)📚 Learning: 2025-12-16T19:45:17.746ZApplied to files:
📚 Learning: 2025-12-29T13:25:52.634ZApplied to files:
📚 Learning: 2026-06-12T05:11:34.288ZApplied to files:
📚 Learning: 2026-03-26T09:04:02.783ZApplied to files:
📚 Learning: 2026-05-11T12:36:23.341ZApplied to files:
📚 Learning: 2026-06-15T07:26:29.844ZApplied to files:
🔇 Additional comments (2)
WalkthroughAdds a ChangesSearchForm
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
libs/ui/src/molecules/search-form.tsxOops! Something went wrong! :( ESLint: 9.39.1 TypeError: Key "rules": Key "tailwindcss/enforces-negative-arbitrary-values": Could not find "enforces-negative-arbitrary-values" in plugin "tailwindcss". libs/ui/stories/overview/component-comparison.stories.tsxOops! Something went wrong! :( ESLint: 9.39.1 TypeError: Key "rules": Key "tailwindcss/enforces-negative-arbitrary-values": Could not find "enforces-negative-arbitrary-values" in plugin "tailwindcss". Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Storybook A11y Report
LightNo a11y report JSON found for light. Storybook test-runner likely failed before reporting. DarkNo a11y report JSON found for dark. Storybook test-runner likely failed before reporting. |
Greptile SummaryReworks
Confidence Score: 5/5Safe to merge; the architectural rework is sound and all variant paths are covered. The gapped prop, portal-based clear button, and independent focus rings are all correctly wired through context and variants. The only finding is a one-frame layout flash when the form is initialized with a non-empty value and a ClearButton is composed — a cosmetic edge-case that does not break functionality. libs/ui/src/molecules/search-form.tsx — the useEffect / useLayoutEffect distinction for hasClearButton registration. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
SF["SearchForm\nroot context: size, gapped, clearSlot, hasClearButton"]
SF --> SFC["SearchForm.Control\nflex items-stretch"]
SFC --> IW["inputWrapper div\nposition relative, flex-1\nfocus-within z-10\nref sets clearSlot"]
SFC --> SFB["SearchForm.Button\nButton atom\nfocus-visible z-10\nh-form-control-size\ngapped false: rounded-s-none"]
IW --> IN["SearchForm.Input\nInput atom, w-full\ngapped false: rounded-e-none\nwithButtonInside when hasClearButton and hasValue"]
IW -.->|createPortal| CB["SearchForm.ClearButton\nabsolute inset-y-0\nend-padding-input-size"]
CB -->|useEffect sets| HC["hasClearButton = true"]
HC --> IN
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
SF["SearchForm\nroot context: size, gapped, clearSlot, hasClearButton"]
SF --> SFC["SearchForm.Control\nflex items-stretch"]
SFC --> IW["inputWrapper div\nposition relative, flex-1\nfocus-within z-10\nref sets clearSlot"]
SFC --> SFB["SearchForm.Button\nButton atom\nfocus-visible z-10\nh-form-control-size\ngapped false: rounded-s-none"]
IW --> IN["SearchForm.Input\nInput atom, w-full\ngapped false: rounded-e-none\nwithButtonInside when hasClearButton and hasValue"]
IW -.->|createPortal| CB["SearchForm.ClearButton\nabsolute inset-y-0\nend-padding-input-size"]
CB -->|useEffect sets| HC["hasClearButton = true"]
HC --> IN
Reviews (6): Last reviewed commit: "Merge branch 'master' into claude/search..." | Re-trigger Greptile |
- ClearButton corners now follow the gapped variant (square when joined, rounded when detached) instead of being hard-coded square in the base slot - Gapped story uses fn() for onSubmit per the Storybook style guide - Point Code Connect at the rebuilt SearchForm Figma node - Add SearchForm (joined + gapped) to the component-comparison overview Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… z-index - Clear button is now absolutely positioned inside the input at its trailing edge (= input padding-inline-end) via a portal into the input wrapper, instead of sitting as a flex sibling between the input and the button. The input reserves trailing padding (withButtonInside) only when a clear button is composed. - Focused control now wins the stacking order (focus-within/focus-visible z-10 on the input wrapper and button), so the focus ring is never painted underneath the adjacent control. - Drop the now-unused clearButton rounded-none joined-corner handling. - component-comparison: SearchForm has an integrated search button, so remove the extra standalone Button placed next to it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@libs/ui/stories/overview/component-comparison.stories.tsx`:
- Around line 450-451: Replace the hardcoded Tailwind width utility class w-64
with the project's semantic or layout token utility class in the bodyClassName
attributes. Update both instances where w-64 appears (in the bodyClassName at
line 450 and line 464) to use the appropriate semantic token class from the
design system instead. Consult the project's design token documentation or
existing story files to identify the correct semantic/layout token class name
that provides the equivalent width sizing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 49cdc916-4886-408e-b255-bea615a4c9f9
📒 Files selected for processing (2)
libs/ui/src/molecules/search-form.tsxlibs/ui/stories/overview/component-comparison.stories.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: storybook-a11y / storybook-a11y
- GitHub Check: Greptile Review
- GitHub Check: main
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (5)
libs/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Implement UI library components using Zag.js for React and Tailwind CSS for styling
libs/ui/**/*.{ts,tsx}: UI library must use Zag.js for React components with Tailwind CSS styling
Organize UI library components into Atoms, Molecules, and Tokens directories following atomic design pattern
Files:
libs/ui/stories/overview/component-comparison.stories.tsxlibs/ui/src/molecules/search-form.tsx
libs/ui/stories/**/*.stories.{tsx,ts}
📄 CodeRabbit inference engine (libs/ui/AGENTS.md)
libs/ui/stories/**/*.stories.{tsx,ts}: Define Storybook stories using CSF3 format with autodocs support
Use components fromsrc/instead of native HTML elements in Storybook stories
Use semantic and layout tokens in storyclassNameattributes instead of hardcoded Tailwind values
Define Storybook controls only for props that change appearance or behavior; excludeid,ref, internal callbacks
Order Storybook stories as: Playground, Variants (if exist), Sizes (if exist), States (if exist), Component-specific stories
UseVariantContainerandVariantGroupfor visual matrices in Storybook stories
Usefn()fromstorybook/testfor event handlers in Storybook stories
Files:
libs/ui/stories/overview/component-comparison.stories.tsx
libs/**
📄 CodeRabbit inference engine (AGENTS.md)
Use RSLib for building libraries in the monorepo
Files:
libs/ui/stories/overview/component-comparison.stories.tsxlibs/ui/src/molecules/search-form.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Run Biome linting and formatting only on changed files using 'bunx biome check --write path/to/file'
Files:
libs/ui/stories/overview/component-comparison.stories.tsxlibs/ui/src/molecules/search-form.tsx
libs/ui/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (libs/ui/AGENTS.md)
libs/ui/src/**/*.{tsx,ts}: Usetv()for styling components instead of manual class concatenation
Never useforwardReforuseCallbackfor handlers in components (React 19 doesn't need them)
Usetypefor type definitions instead ofinterface
Never use arbitrary Tailwind values (e.g.,bg-[#ff0000],p-[1rem]); use tokens instead
Never use default exports or barrel files (index.ts) unless the framework requires them
Use data attributes for state styling in Tailwind (e.g.,data-disabled:...,data-[state=open]:...)
Use component-specific token classes in component implementations, not direct semantic tokens likebg-primaryortext-fg
Implement compound components usingComponent.Subcomponent = function ...syntax instead of object exports
Files:
libs/ui/src/molecules/search-form.tsx
🧠 Learnings (6)
📚 Learning: 2025-12-16T19:45:17.746Z
Learnt from: BleedingDev
Repo: NMIT-WR/new-engine PR: 207
File: libs/ui/src/molecules/select.tsx:50-50
Timestamp: 2025-12-16T19:45:17.746Z
Learning: When reviewing Tailwind classes in TSX/TS files, prefer using square brackets for arbitrary CSS values and complex expressions. Specifically: - Do not use the parentheses syntax (z-(--z-index)) for anything beyond simple CSS variable references; this syntax auto-wraps in var() and cannot handle calc or complex functions. - Use the square brackets syntax (e.g., h-[calc(var(--available-height)-var(--spacing-content))]) for calc expressions, var with calc, and any complex CSS expressions. This rule applies broadly to Tailwind v4 usage in TSX code across the project.
Applied to files:
libs/ui/stories/overview/component-comparison.stories.tsxlibs/ui/src/molecules/search-form.tsx
📚 Learning: 2025-12-29T13:25:52.634Z
Learnt from: KaiUweCZE
Repo: NMIT-WR/new-engine PR: 250
File: libs/ui/src/molecules/slider.tsx:277-285
Timestamp: 2025-12-29T13:25:52.634Z
Learning: In the libs/ui project, when a child component defines a default value for a prop (for example StatusText has status = 'default'), do not override this value from the parent with a nullish coalescing operator (??) or other fallbacks. Centralize defaults in the child component (via default parameter or defaultProps) to ensure a single source of truth. Apply this pattern to TSX components under libs/ui (e.g., any files in libs/ui/src/**/*.tsx).
Applied to files:
libs/ui/stories/overview/component-comparison.stories.tsxlibs/ui/src/molecules/search-form.tsx
📚 Learning: 2026-06-12T05:11:34.288Z
Learnt from: Luko248
Repo: TechsioCZ/new-engine PR: 439
File: libs/ui/src/molecules/combobox.tsx:96-98
Timestamp: 2026-06-12T05:11:34.288Z
Learning: When reviewing Tailwind `text-*` class usage in `libs/ui` components (including `libs/ui/src/molecules/combobox.tsx`), note that `text-*` may map to Figma tokens that are intentionally namespace-ambiguous: e.g. `text-combobox-trigger` may resolve to a **font-size** token (CSS `font-size`), while `text-combobox-trigger-fg-base` may resolve to a **color** token (CSS `color`). Because they set different CSS properties, both classes may coexist on the same element without overriding each other. Do not flag these as duplicate/dead classes; instead, verify whether the referenced token is a size vs. color (and thus whether they affect `font-size` vs `color`) before raising any concern.
Applied to files:
libs/ui/stories/overview/component-comparison.stories.tsxlibs/ui/src/molecules/search-form.tsx
📚 Learning: 2026-03-26T09:04:02.783Z
Learnt from: KaiUweCZE
Repo: TechsioCZ/new-engine PR: 358
File: libs/ui/src/molecules/radio-card.tsx:337-401
Timestamp: 2026-03-26T09:04:02.783Z
Learning: In libs/ui compound components (e.g., Select/TreeView/RadioCard under libs/ui/src/molecules/), the established pattern is to implement the main component as a plain exported function and attach sub-components and metadata directly as static properties (e.g., `Select.Label = ...`, `Select.displayName = "Select"`). Do not require `Object.assign` or a typed compound-component interface for these components; if this static-property pattern is used, do not flag it as a TypeScript/type-declaration issue as long as declaration emit/type checking passes.
Applied to files:
libs/ui/src/molecules/search-form.tsx
📚 Learning: 2026-05-11T12:36:23.341Z
Learnt from: BleedingDev
Repo: TechsioCZ/new-engine PR: 404
File: libs/ui/src/molecules/figma/tree-view.figma.tsx:2-2
Timestamp: 2026-05-11T12:36:23.341Z
Learning: Inside the `libs/ui` library source (e.g., under `libs/ui/src/`), require component imports to use relative paths (such as `../tree-view`) instead of the `libs/ui` alias. Treat `libs/ui/...` imports as reserved for external consumers of the published library; do not flag them or suggest replacing them with relative imports when they appear in consumer code or in contexts outside the library source.
Applied to files:
libs/ui/src/molecules/search-form.tsx
📚 Learning: 2026-06-15T07:26:29.844Z
Learnt from: Luko248
Repo: TechsioCZ/new-engine PR: 448
File: libs/ui/src/organisms/footer.tsx:0-0
Timestamp: 2026-06-15T07:26:29.844Z
Learning: In `libs/ui` components, when using Tailwind v4 border-width utilities with a CSS variable (e.g., `--border-footer-width`, `--border-table-width`), always use the explicit `length:` hint syntax so the variable is treated as a border *width* (e.g., `border-t-(length:--border-footer-width)`, `border-b-(length:--border-table-width)`). If you use bare `(--var)` without `length:`, Tailwind v4 interprets it as a border *color* and the width will not be applied. Apply this rule to all border-width utilities: `border-t`, `border-b`, `border-l`, `border-r`, and `border`.
Applied to files:
libs/ui/src/molecules/search-form.tsx
🔇 Additional comments (2)
libs/ui/src/molecules/search-form.tsx (1)
8-12: LGTM!Also applies to: 26-70, 90-97, 135-136, 168-171, 233-260, 306-341
libs/ui/stories/overview/component-comparison.stories.tsx (1)
452-459: LGTM!Also applies to: 460-463, 466-471
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous Review Summary (commit 6f9cece)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 6f9cece)Status: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
SUGGESTION
Other Observations (not in diff)Issues found in unchanged code that cannot receive inline comments:
Files Reviewed (5 files)
Fix Link: Fix these issues in Kilo Cloud Reviewed by nex-n2-pro:free · 1,645,051 tokens |
- Clear button hit target now spans the full input height (inset-y-0) instead of collapsing to the icon size (Kilo) - Omit the internal withButtonInside prop from SearchForm.Input's public props so consumers can't override the reserved clear-button padding (Kilo) - Use token width (w-full max-w-xs) for the SearchForm comparison cards to match the other form-control cards (CodeRabbit) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🎉 This PR is included in version 0.16.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
Reworks
SearchFormso the input and button are composed as two independent controls side by side, instead of being wrapped in one bordered control with a single sharedfocus-withinring.focus-withinoutline, border, background, andoverflow-hidden. The input uses the Input atom's ownoutline-input-ring; the button uses the Button atom's ownoutline-button-ring. Focusing the input highlights only the input; focusing the button highlights only the button.rounded-e-none(strips right corners) and the button getsrounded-s-none(strips left corners), so the joined state reads as one unit while each keeps its own border.gappedboolean prop — Whentrue, inserts an 8px gap between the two controls (new--spacing-search-form-gappedtoken aliasing--dimension-8= 0.5rem) and restores the full rounded corners on both. Threaded through context and exposed viaVariantProps, the Storybook control, a newGappedstory, and Figma Code Connect.h-form-control-{size}height forsm/md/lgso it always matches the input height (the Button atom otherwise sizeslgby padding alone, causing a height mismatch).Files
libs/ui/src/molecules/search-form.tsx— component rework +gappedprop/contextlibs/ui/src/tokens/components/molecules/_search-form.css—--spacing-search-form-gappedtoken (8px)libs/ui/stories/molecules/search-form.stories.tsx—gappedcontrol +Gappedstorylibs/ui/src/molecules/search-form.figma.tsx— Code Connect mirrorsgappedTesting
biome check— clean on all changed filestsc --noEmit(libs/ui) — no type errorsnx run ui-kit:build— succeeds, Publint passedgap-search-form-gapped→0.5rem(8px),rounded-e-none,rounded-s-none,h-form-control-lg, and the independentoutline-input-ring/outline-button-ringfocus rings.GappedandSizesStorybook stories allow visual confirmation locally.🤖 Generated with Claude Code
Generated by Claude Code
Summary by CodeRabbit
Release Notes
New Features
gappedoption to SearchForm to create an 8px separation between the input and search button, with restored rounded corners when enabled.Documentation
gappedSearchForm layouts.Design Tokens