Skip to content

feat(Suggestions): Add lightbulb button and double-click to open suggestions#98

Merged
lordrip merged 2 commits intoKaotoIO:1.xfrom
mmelko:feat/suggetions-button
Mar 17, 2026
Merged

feat(Suggestions): Add lightbulb button and double-click to open suggestions#98
lordrip merged 2 commits intoKaotoIO:1.xfrom
mmelko:feat/suggetions-button

Conversation

@mmelko
Copy link
Contributor

@mmelko mmelko commented Mar 17, 2026

  • Centralize suggestion menu visibility state in SuggestionContext so only one suggestion menu can be open at a time across all fields

  • Add a lightbulb button to StringField, PasswordField, and TextAreaField that opens the suggestions menu on click (visible on hover/focus)

  • Add double-click handler on input fields as an alternative way to open suggestions

  • Change useSuggestions return type from ReactNode to { suggestionsMenu, openSuggestions } to support the new interaction methods

Screenshot 2026-03-17 at 16 34 41
Screen.Recording.2026-03-17.at.17.22.17.mov

Summary by CodeRabbit

  • New Features
    • Added a lightbulb suggestions button (appears on hover/focus) for password, text, textarea, and key-value fields.
    • Suggestions can be opened by clicking the button, double-clicking the input, or via keyboard, providing faster access to suggested values.
    • Improved accessibility and focus behavior for suggestion interactions.

Lift the local visibility state (isVisible/setIsVisible) from the
useSuggestions hook into SuggestionContext so only one suggestion menu
can be open at a time across all fields.

- Add currentOpenMenu/setCurrentOpenMenu to SuggestionContext
- Replace local useState with context-driven visibility using unique menuId (useId)
- Change useSuggestions return type from ReactNode to { suggestionsMenu, openSuggestions }
- Update all field consumers and tests to use the new return shape
@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Refactors the suggestions system: useSuggestions now returns { suggestionsMenu, openSuggestions }; suggestion menu visibility is lifted into SuggestionContext (currentOpenMenu, setCurrentOpenMenu); adds a SuggestionsButton component and hover/focus CSS to show it.

Changes

Cohort / File(s) Summary
Hook & Context Core
src/form/hooks/suggestions.tsx, src/form/providers/SuggestionRegistryProvider.tsx
useSuggestions now returns an object { suggestionsMenu, openSuggestions }; visibility moved to context via currentOpenMenu and setCurrentOpenMenu; provider exposes new state and setter.
Hook Tests
src/form/hooks/suggestions.test.tsx
Updated tests to use stateful context provider and assert suggestionsMenu + openSuggestions behavior.
New Component
src/form/fields/SuggestionsButton.tsx
Adds SuggestionsButton component (props: propName, onClick) rendering a plain PatternFly button with LightbulbIcon and accessibility attributes.
Field Integrations
src/form/fields/StringField.tsx, src/form/fields/PasswordField.tsx, src/form/fields/TextAreaField.tsx, src/form/KeyValue/KeyValueField.tsx
Destructure { suggestionsMenu, openSuggestions } from hook; render suggestionsMenu; add SuggestionsButton in input utilities wired to openSuggestions.
Field Tests
src/form/fields/StringField.test.tsx, src/form/fields/PasswordField.test.tsx, src/form/fields/TextAreaField.test.tsx, src/form/KeyValue/KeyValueField.test.tsx
Mocks updated to return { suggestionsMenu, openSuggestions }; tests use StatefulSuggestionProvider to manage currentOpenMenu state.
Other Test Change
src/form/fields/FieldActions.test.tsx
Assertion changed to check toggle aria-expanded="false" after dropdown close instead of asserting DOM absence of item.
Styling
src/form/KaotoForm.scss
Adds &__suggestions-button rule: hidden by default, becomes visible on parent hover (.pf-v6-c-text-input-group/.pf-v6-c-input-group) or when the button is focused.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Field as Form Field
    participant Button as SuggestionsButton
    participant Hook as useSuggestions
    participant Context as SuggestionContext
    participant Menu as SuggestionsMenu

    User->>Field: Type or press Ctrl+Space / dblclick
    Field->>Hook: request suggestions for prop
    Hook->>Context: read currentOpenMenu
    Hook->>Context: setCurrentOpenMenu(menuId)
    Context-->>Hook: currentOpenMenu updated
    Hook-->>Field: return { suggestionsMenu, openSuggestions }
    Field->>Menu: render suggestionsMenu (when currentOpenMenu matches)
    alt User clicks SuggestionsButton
        User->>Button: click
        Button->>Hook: openSuggestions()
        Hook->>Context: setCurrentOpenMenu(menuId)
        Hook->>Menu: focus first item
    end
    Menu-->>User: shows focused suggestion list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A tiny bulb, a helpful click,
Menus open quick and slick,
Context keeps the doors ajar,
Hover, focus—there you are,
Suggestions hop in, smart and quick! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main changes: adding a lightbulb button and double-click functionality to open suggestions, which are the core features introduced across multiple field components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/form/KaotoForm.scss (1)

45-55: Consider keyboard accessibility for the suggestions button.

The button is hidden by default (display: none) and only becomes visible on parent hover or when the button itself is focused. However, a hidden element cannot receive keyboard focus, so users navigating via keyboard cannot reach the button unless they first hover over the parent with a mouse.

Consider adding a :focus-within selector on the parent containers to show the button when any child (like the input) has focus:

♻️ Suggested enhancement for keyboard accessibility
  /* stylelint-disable-next-line selector-class-pattern */
  .pf-v6-c-text-input-group:hover &__suggestions-button,
  /* stylelint-disable-next-line selector-class-pattern */
  .pf-v6-c-input-group:hover &__suggestions-button,
+ /* stylelint-disable-next-line selector-class-pattern */
+ .pf-v6-c-text-input-group:focus-within &__suggestions-button,
+ /* stylelint-disable-next-line selector-class-pattern */
+ .pf-v6-c-input-group:focus-within &__suggestions-button,
  &__suggestions-button:focus {
    display: inline-flex;
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/form/KaotoForm.scss` around lines 45 - 55, The suggestions button is
hidden by default so keyboard users can't focus it; update the SASS rules for
&__suggestions-button so it becomes visible not only on parent hover but also
when the parent has focus-within (e.g., add selectors like
.pf-v6-c-text-input-group:focus-within &__suggestions-button and
.pf-v6-c-input-group:focus-within &__suggestions-button), ensuring the button
can be reached via keyboard focus from the input; confirm &__suggestions-button
remains a focusable element (not aria-hidden or display:none when focused).
src/form/hooks/suggestions.test.tsx (1)

63-67: Add a direct regression test for the double-click trigger path.

The PR introduces dblclick opening behavior, but this suite still only asserts keyboard-open flows.

✅ Suggested test addition
+  it('should show suggestions menu when input is double-clicked', async () => {
+    const result = renderWithContext(<TestComponent />);
+
+    await act(async () => {
+      fireEvent.doubleClick(result.getByTestId('test-input'));
+    });
+
+    await waitFor(() => {
+      expect(result.getByTestId('suggestions-menu')).toBeInTheDocument();
+    });
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/form/hooks/suggestions.test.tsx` around lines 63 - 67, Add a regression
test that covers the new double-click open path: using the existing
renderWithContext helper to render the component wrapped by
StatefulSuggestionProvider (with getProvidersMock), simulate a double-click on
the input/trigger element (use fireEvent.doubleClick or userEvent.dblClick
against the same element used in other keyboard tests), then assert that the
suggestion list appears (e.g., query/getByRole for listbox or the expected
suggestion items) and that the provider was invoked as expected; add this
alongside the existing keyboard-open tests in suggestions.test.tsx to ensure
dblclick behavior is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/form/hooks/suggestions.tsx`:
- Around line 154-166: The openSuggestions callback must no-op for
non-interactive inputs; add an early return that checks the bound input's
interactive state (e.g., a prop or ref such as disabled, readOnly, or
inputRef.current?.disabled/inputRef.current?.readOnly) before calling
setSearchValue or setCurrentOpenMenu; keep the existing focus/scroll logic but
only execute it when the input is interactive and when newValue is truthy (use
the existing menuId, setCurrentOpenMenu, firstElementRef references).

---

Nitpick comments:
In `@src/form/hooks/suggestions.test.tsx`:
- Around line 63-67: Add a regression test that covers the new double-click open
path: using the existing renderWithContext helper to render the component
wrapped by StatefulSuggestionProvider (with getProvidersMock), simulate a
double-click on the input/trigger element (use fireEvent.doubleClick or
userEvent.dblClick against the same element used in other keyboard tests), then
assert that the suggestion list appears (e.g., query/getByRole for listbox or
the expected suggestion items) and that the provider was invoked as expected;
add this alongside the existing keyboard-open tests in suggestions.test.tsx to
ensure dblclick behavior is exercised.

In `@src/form/KaotoForm.scss`:
- Around line 45-55: The suggestions button is hidden by default so keyboard
users can't focus it; update the SASS rules for &__suggestions-button so it
becomes visible not only on parent hover but also when the parent has
focus-within (e.g., add selectors like .pf-v6-c-text-input-group:focus-within
&__suggestions-button and .pf-v6-c-input-group:focus-within
&__suggestions-button), ensuring the button can be reached via keyboard focus
from the input; confirm &__suggestions-button remains a focusable element (not
aria-hidden or display:none when focused).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 127d7e50-30d5-4d38-ae35-adad943bf066

📥 Commits

Reviewing files that changed from the base of the PR and between a71449e and 396b92f.

⛔ Files ignored due to path filters (4)
  • src/form/__snapshots__/KaotoForm.test.tsx.snap is excluded by !**/*.snap
  • src/form/fields/__snapshots__/PasswordField.test.tsx.snap is excluded by !**/*.snap
  • src/form/fields/__snapshots__/StringField.test.tsx.snap is excluded by !**/*.snap
  • src/form/fields/__snapshots__/TextAreaField.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • src/form/KaotoForm.scss
  • src/form/KeyValue/KeyValueField.test.tsx
  • src/form/KeyValue/KeyValueField.tsx
  • src/form/fields/FieldActions.test.tsx
  • src/form/fields/PasswordField.test.tsx
  • src/form/fields/PasswordField.tsx
  • src/form/fields/StringField.test.tsx
  • src/form/fields/StringField.tsx
  • src/form/fields/SuggestionsButton.tsx
  • src/form/fields/TextAreaField.test.tsx
  • src/form/fields/TextAreaField.tsx
  • src/form/hooks/suggestions.test.tsx
  • src/form/hooks/suggestions.tsx
  • src/form/providers/SuggestionRegistryProvider.tsx

…estions menu

- Add SuggestionsButton component with lightbulb icon, shown on hover/focus
- Add double-click event listener on input fields to toggle suggestions
- Add CSS rules to show/hide the button on parent hover
- Update StringField, PasswordField, and TextAreaField to include the button
@mmelko mmelko force-pushed the feat/suggetions-button branch from 396b92f to 8c01bb1 Compare March 17, 2026 16:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/form/hooks/suggestions.tsx (1)

59-66: ⚠️ Potential issue | 🟡 Minor

Make all “open suggestions” triggers follow the same open semantics.

Line 59 opens unconditionally, while Line 160 toggles (menuIdnull). Since this callback is used by the lightbulb and double-click “open” actions, toggling can close the menu unexpectedly. Prefer an idempotent open path and reuse it for keyboard/button/dblclick.

Also applies to: 154-169

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/form/hooks/suggestions.tsx` around lines 59 - 66, The keyboard/open
handler opens the suggestions unconditionally (setCurrentOpenMenu(menuId)) while
the other handler toggles (menuId ↔ null), causing double-click and lightbulb
actions to sometimes close the menu; make all "open suggestions" triggers
idempotent by extracting/using a single open function that calls
setSearchValue(''), setCurrentOpenMenu(menuId) (never toggling to null), and
focuses/scrolls firstElementRef.current inside requestAnimationFrame, then
replace the toggle logic in the double-click/lightbulb handler (the code around
the current toggle at lines ~154-169) to call that same open function so every
trigger shares the same open semantics.
🧹 Nitpick comments (1)
src/form/fields/SuggestionsButton.tsx (1)

5-8: Support a disabled state on the trigger button.

The component currently has no isDisabled prop, which makes it harder for consuming fields to render a non-interactive trigger when the input is disabled. Adding it here keeps behavior explicit and consistent across all field types.

Proposed diff
 interface SuggestionsButtonProps {
   propName: string;
   onClick: () => void;
+  isDisabled?: boolean;
 }

-export const SuggestionsButton: FunctionComponent<SuggestionsButtonProps> = ({ propName, onClick }) => (
+export const SuggestionsButton: FunctionComponent<SuggestionsButtonProps> = ({ propName, onClick, isDisabled }) => (
   <Button
     variant="plain"
     className="kaoto-form__suggestions-button"
     data-testid={`${propName}__open-suggestions-button`}
     onClick={onClick}
+    isDisabled={isDisabled}
     aria-label="Open suggestions"
     title="Open suggestions (Ctrl+Space)"
     icon={<LightbulbIcon />}
   />
 );

Also applies to: 10-19

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/form/fields/SuggestionsButton.tsx` around lines 5 - 8, The
SuggestionsButtonProps interface lacks an isDisabled flag so consumers can't
render a non-interactive trigger; add isDisabled?: boolean to
SuggestionsButtonProps and update the SuggestionsButton component (props
propName, onClick) to accept and apply that prop to the trigger element (set the
button's disabled attribute and any relevant ARIA/state styling) and propagate
the prop where SuggestionsButton is instantiated so disabled inputs render a
non-interactive trigger.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/form/hooks/suggestions.tsx`:
- Around line 59-66: The keyboard/open handler opens the suggestions
unconditionally (setCurrentOpenMenu(menuId)) while the other handler toggles
(menuId ↔ null), causing double-click and lightbulb actions to sometimes close
the menu; make all "open suggestions" triggers idempotent by extracting/using a
single open function that calls setSearchValue(''), setCurrentOpenMenu(menuId)
(never toggling to null), and focuses/scrolls firstElementRef.current inside
requestAnimationFrame, then replace the toggle logic in the
double-click/lightbulb handler (the code around the current toggle at lines
~154-169) to call that same open function so every trigger shares the same open
semantics.

---

Nitpick comments:
In `@src/form/fields/SuggestionsButton.tsx`:
- Around line 5-8: The SuggestionsButtonProps interface lacks an isDisabled flag
so consumers can't render a non-interactive trigger; add isDisabled?: boolean to
SuggestionsButtonProps and update the SuggestionsButton component (props
propName, onClick) to accept and apply that prop to the trigger element (set the
button's disabled attribute and any relevant ARIA/state styling) and propagate
the prop where SuggestionsButton is instantiated so disabled inputs render a
non-interactive trigger.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 851a84cc-7722-47f6-91c5-1ee88715a740

📥 Commits

Reviewing files that changed from the base of the PR and between 396b92f and 8c01bb1.

⛔ Files ignored due to path filters (4)
  • src/form/__snapshots__/KaotoForm.test.tsx.snap is excluded by !**/*.snap
  • src/form/fields/__snapshots__/PasswordField.test.tsx.snap is excluded by !**/*.snap
  • src/form/fields/__snapshots__/StringField.test.tsx.snap is excluded by !**/*.snap
  • src/form/fields/__snapshots__/TextAreaField.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (7)
  • src/form/KaotoForm.scss
  • src/form/fields/FieldActions.test.tsx
  • src/form/fields/PasswordField.tsx
  • src/form/fields/StringField.tsx
  • src/form/fields/SuggestionsButton.tsx
  • src/form/fields/TextAreaField.tsx
  • src/form/hooks/suggestions.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/form/KaotoForm.scss
  • src/form/fields/FieldActions.test.tsx

@mmelko
Copy link
Contributor Author

mmelko commented Mar 17, 2026

video was updated

@lordrip lordrip merged commit 27ac459 into KaotoIO:1.x Mar 17, 2026
5 checks passed
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.

2 participants