Skip to content

feat(ui): add DropdownInput component and update components#288

Merged
kylengn merged 13 commits intomainfrom
feat/dropdown-input-and-updates
Jan 7, 2026
Merged

feat(ui): add DropdownInput component and update components#288
kylengn merged 13 commits intomainfrom
feat/dropdown-input-and-updates

Conversation

@kylengn
Copy link
Contributor

@kylengn kylengn commented Jan 6, 2026

Summary by CodeRabbit

  • New Features

    • Introduced DropdownInput component with keyboard navigation and remote data fetching capabilities.
    • Added left and right icon support to Input component.
    • Added optional maximum range days restriction to DateTimeRangePicker.
    • Enhanced Calendar with weekday name formatter for improved date display.
  • Improvements

    • Refined Calendar styling, navigation buttons, and day cell indicators.
    • Improved KpiCard value rendering flexibility.
  • Chores

    • Updated peer dependencies including react-hook-form and @tanstack/react-query.

✏️ Tip: You can customize this high-level summary in your review settings.

@kylengn kylengn self-assigned this Jan 6, 2026
Copilot AI review requested due to automatic review settings January 6, 2026 12:57
@vercel
Copy link

vercel bot commented Jan 6, 2026

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

Project Deployment Review Updated (UTC)
react-ui Ready Ready Preview, Comment Jan 7, 2026 2:42pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR updates the React UI library by adding peer dependency for React Query, introducing a new DropdownInput component with remote data fetching, enhancing existing components (Calendar, DatePicker, DateTimeRangePicker, Input, KpiCard) with new features and styling refinements, and expanding Storybook documentation with interaction examples.

Changes

Cohort / File(s) Summary
Package Dependencies
libs/react/ui/package.json
Added @tanstack/react-query@^5.90.5 as peer dependency; updated react-hook-form from ^7.52.1 to ^7.63.0.
Calendar Component Styling
libs/react/ui/src/components/calendar/calendar.tsx
Extensive styling refinements: adjusted root padding, tightened month grid gap, updated navigation button colors/hover states, added gap-8 to weekday/week elements, reworked day cell styling (selected, today with pseudo-element dot indicator, range_middle), and replaced chevron icons with filled variants.
Dashboard Components
libs/react/ui/src/components/dashboard/components/kpi-card.tsx
Updated KpiCard value rendering to explicitly distinguish between primitive string/number values (rendered in Text component) and ReactNode values (rendered in div).
Date Component Formatters
libs/react/ui/src/components/date-picker/date-picker.tsx
Added formatters prop with formatWeekdayName to Calendar component for customized weekday label display.
Date-Time Range Picker Enhancement
libs/react/ui/src/components/date-time-range-picker/date-time-range-picker.tsx
Added maxRangeDays property to props; extracted startDate/endDate, computed disabledDates via useMemo, enhanced handleSelect to cap end date and close popover; added weekday formatter and default month logic.
New DropdownInput Component
libs/react/ui/src/components/dropdown-input/dropdown-input.tsx, dropdown-input.stories.tsx, dropdown-input/index.ts
Introduced new generic DropdownInput component with remote data fetching via React Query, debounced query handling, keyboard navigation (ArrowUp/Down/Enter/Escape), controlled/uncontrolled value support, and custom item rendering; added comprehensive Storybook story with mock data and modal integration example.
Input Component Enhancement
libs/react/ui/src/components/input/input.tsx, input.stories.tsx
Updated Input component to support optional iconLeft and iconRight ReactNode props; refactored to forwardRef wrapper for ref access; added conditional padding and icon container rendering; added new WithIcons story and updated render typings for existing stories.
Component Exports
libs/react/ui/src/components/index.ts
Added re-export of DropdownInput component to public API.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant DI as DropdownInput
    participant Debounce as Debounce<br/>(memoized)
    participant RQ as useQuery<br/>(React Query)
    participant QFn as queryFn
    participant UI as Dropdown Menu

    User->>DI: Type in input
    DI->>DI: Update inputValue state
    DI->>Debounce: Trigger debounce
    
    par Debounce Processing
        Debounce->>Debounce: Wait debounceMs
        Debounce->>DI: Update debouncedQuery
    end
    
    DI->>DI: Check minQueryLength met?
    alt Query length valid
        DI->>RQ: Trigger query (debouncedQuery)
        RQ->>QFn: Call queryFn(debouncedQuery)
        QFn-->>RQ: Return DropdownInputItem[]
        RQ-->>DI: isPending=false, data=items
    end
    
    DI->>UI: Render dropdown with items
    UI-->>User: Display filtered suggestions
    
    User->>UI: Click item or press Enter
    DI->>DI: Update selected item
    DI->>DI: Emit onSelect & onValueChange
    DI->>UI: Close popover (if closeOnSelect)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • EnzalRad
  • dvxam
  • noe-charmet

Poem

🐰 A rabbit hops through UI trees,
DropdownInput queries with ease!
Calendar styled in colors so bright,
Input icons left and right—
Components dance in React's spring breeze! 🌸

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding the DropdownInput component while also documenting other concurrent updates to the UI library.

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.

@argos-ci
Copy link

argos-ci bot commented Jan 6, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) 👍 Changes approved 19 changed, 7 added Jan 7, 2026, 2:43 PM

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new DropdownInput component for async searchable dropdowns and enhances several existing UI components with improved functionality and styling. The changes add React Query as a peer dependency to support the new component's data fetching capabilities.

  • Adds a new DropdownInput component with keyboard navigation, debounced search, and async data fetching
  • Enhances the Input component to support left and right icons with forwardRef implementation
  • Improves calendar components with better styling, weekday formatting, and max range days constraint

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pnpm-lock.yaml Updates react-hook-form to 7.70.0 and adds @tanstack/react-query 5.90.16 with corresponding lock file changes
libs/react/ui/package.json Adds @tanstack/react-query as peer dependency and updates react-hook-form version requirement
libs/react/ui/src/components/input/input.tsx Refactors Input to use forwardRef and adds support for left/right icons with wrapper div and dynamic padding
libs/react/ui/src/components/input/input.stories.tsx Adds new WithIcons story demonstrating icon usage in Input component
libs/react/ui/src/components/index.ts Exports the new dropdown-input component
libs/react/ui/src/components/dropdown-input/index.ts Barrel export for dropdown-input component
libs/react/ui/src/components/dropdown-input/dropdown-input.tsx Implements new DropdownInput component with React Query integration, keyboard navigation, and async search
libs/react/ui/src/components/dropdown-input/dropdown-input.stories.tsx Adds comprehensive story with modal integration demonstrating DropdownInput usage
libs/react/ui/src/components/date-time-range-picker/date-time-range-picker.tsx Adds maxRangeDays prop to limit date range selection and improves date handling logic
libs/react/ui/src/components/date-picker/date-picker.tsx Adds custom weekday name formatter to show abbreviated weekday names
libs/react/ui/src/components/calendar/calendar.tsx Updates styling for improved visual design, including borders, spacing, and icon changes
libs/react/ui/src/components/dashboard/components/kpi-card.tsx Enhances value rendering to support ReactNode in addition to string and number types
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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: 0

🧹 Nitpick comments (4)
libs/react/ui/src/components/dashboard/components/kpi-card.tsx (1)

42-48: Consider whether text styling should apply to ReactNode wrapper.

When rendering ReactNode values (line 47), the wrapper div applies text-specific classes (text-sm font-medium text-foreground-neutral-base). If the ReactNode is a custom component with its own styling, these classes might conflict or be redundant.

Consider whether the wrapper should remain unstyled or have minimal layout-only classes, allowing the ReactNode to fully control its own appearance.

Alternative approach without wrapper styling
-        ) : (
-          <div className="text-sm font-medium text-foreground-neutral-base">{value}</div>
+        ) : (
+          <div>{value}</div>

Or apply only layout classes if needed:

-        ) : (
-          <div className="text-sm font-medium text-foreground-neutral-base">{value}</div>
+        ) : (
+          <div className="flex items-center">{value}</div>
libs/react/ui/src/components/dropdown-input/dropdown-input.tsx (2)

256-258: Hardcoded label should be configurable.

The "Select a repository" text is hardcoded, making this component less reusable for other use cases. Consider adding a prop for customization:

 export type DropdownInputProps<T = unknown> = Omit<...> & {
   ...
   emptyPlaceholder?: string;
+  resultsLabel?: string;
   ...
 };
-<div className="px-8 py-6 text-xs leading-20 text-foreground-neutral-muted">
-  Select a repository
-</div>
+{resultsLabel && (
+  <div className="px-8 py-6 text-xs leading-20 text-foreground-neutral-muted">
+    {resultsLabel}
+  </div>
+)}

86-100: Consider cleanup for debounced function on unmount.

The debounced function may fire after the component unmounts. While React's useState setters are safe to call post-unmount, adding cleanup would be cleaner:

+// In utils/debounce.ts, return a cancel function
+export function debounce<T extends (...args: never[]) => void>(fn: T, delay: number) {
+  let timeoutId: ReturnType<typeof setTimeout> | null = null;
+  const debounced = (...args: Parameters<T>) => { ... };
+  debounced.cancel = () => { if (timeoutId) clearTimeout(timeoutId); };
+  return debounced;
+}

Then add cleanup:

 useEffect(() => {
+  return () => debouncedSetQuery.cancel?.();
-}, [value, minQueryLength, debouncedSetQuery]);
+}, [value, minQueryLength, debouncedSetQuery]);
libs/react/ui/src/components/dropdown-input/dropdown-input.stories.tsx (1)

133-133: Consider linking the modal description for better accessibility.

The aria-describedby={undefined} is explicitly set, which prevents warnings but misses an opportunity to link the descriptive text on lines 137-140 to the dialog. Consider adding an id to the Text component and referencing it here.

Optional improvement for accessibility
-          <ModalContent aria-describedby={undefined} overlayClassName="bg-background-modal-overlay">
+          <ModalContent aria-describedby="import-description" overlayClassName="bg-background-modal-overlay">
             <ModalTitle className="sr-only">Import past jobs from GitHub</ModalTitle>
             <ModalHeader title="Import past jobs from GitHub" />
             <ModalBody className="gap-20">
-              <Text size="sm" className="text-foreground-neutral-subtle w-full">
+              <Text id="import-description" size="sm" className="text-foreground-neutral-subtle w-full">
                 Backfill your CI history by importing past runs from your GitHub repo. We&apos;ll
                 handle the rest by creating a background task to import the data for you.
               </Text>
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5174c8a and 063c838.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • libs/react/ui/package.json
  • libs/react/ui/src/components/calendar/calendar.tsx
  • libs/react/ui/src/components/dashboard/components/kpi-card.tsx
  • libs/react/ui/src/components/date-picker/date-picker.tsx
  • libs/react/ui/src/components/date-time-range-picker/date-time-range-picker.tsx
  • libs/react/ui/src/components/dropdown-input/dropdown-input.stories.tsx
  • libs/react/ui/src/components/dropdown-input/dropdown-input.tsx
  • libs/react/ui/src/components/dropdown-input/index.ts
  • libs/react/ui/src/components/index.ts
  • libs/react/ui/src/components/input/input.stories.tsx
  • libs/react/ui/src/components/input/input.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*

⚙️ CodeRabbit configuration file

We handle errors at the edge of our applications in most cases. Do not recommend to add error handling around every single function. We prefer them to bubble up and be handled at upper layers.

Files:

  • libs/react/ui/src/components/index.ts
  • libs/react/ui/package.json
  • libs/react/ui/src/components/dropdown-input/index.ts
  • libs/react/ui/src/components/input/input.stories.tsx
  • libs/react/ui/src/components/date-picker/date-picker.tsx
  • libs/react/ui/src/components/dropdown-input/dropdown-input.stories.tsx
  • libs/react/ui/src/components/input/input.tsx
  • libs/react/ui/src/components/calendar/calendar.tsx
  • libs/react/ui/src/components/dropdown-input/dropdown-input.tsx
  • libs/react/ui/src/components/date-time-range-picker/date-time-range-picker.tsx
  • libs/react/ui/src/components/dashboard/components/kpi-card.tsx
🧬 Code graph analysis (4)
libs/react/ui/src/components/input/input.stories.tsx (2)
libs/react/ui/src/components/input/input.tsx (1)
  • Input (29-65)
libs/react/ui/src/components/icon/icon.tsx (1)
  • Icon (86-90)
libs/react/ui/src/components/date-picker/date-picker.tsx (1)
libs/react/ui/src/components/calendar/calendar.tsx (1)
  • Calendar (8-90)
libs/react/ui/src/components/dropdown-input/dropdown-input.tsx (3)
libs/react/ui/src/components/input/input.tsx (2)
  • inputVariants (6-21)
  • Input (29-65)
libs/react/ui/src/utils/debounce.ts (1)
  • debounce (1-15)
libs/react/ui/src/components/icon/icon.tsx (1)
  • Icon (86-90)
libs/react/ui/src/components/dashboard/components/kpi-card.tsx (1)
libs/react/ui/src/components/typography/text.tsx (1)
  • Text (27-50)
⏰ 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). (2)
  • GitHub Check: Agent
  • GitHub Check: Continuous integration
🔇 Additional comments (17)
libs/react/ui/src/components/date-picker/date-picker.tsx (1)

172-179: LGTM!

The formatWeekdayName formatter using 'EEEEE' for narrow weekday names (single letter) is consistent with the same pattern applied in DateTimeRangePicker. This creates a unified weekday display across date-related components.

libs/react/ui/src/components/input/input.tsx (1)

29-65: LGTM!

The refactoring to forwardRef with icon support is well-implemented:

  • The wrapper div maintains w-full for consistent layout
  • Icon containers use pointer-events-none ensuring input remains interactive
  • Conditional padding (pl-32/pr-32) prevents text-icon overlap

Note: The ref is correctly forwarded to the actual <input> element, so consumers calling methods like focus() will work as expected.

libs/react/ui/src/components/input/input.stories.tsx (1)

141-177: LGTM!

The WithIcons story provides good coverage of the new icon props, demonstrating left-only, right-only, and both icons configurations. This will help document the feature for consumers.

libs/react/ui/src/components/date-time-range-picker/date-time-range-picker.tsx (2)

99-107: Verify maxRangeDays semantics: days from start vs range length.

The current logic (daysFromStart > maxRangeDays) allows selecting dates where differenceInDays equals maxRangeDays. This means maxRangeDays=7 permits a range of 8 calendar days (start date + 7 more).

If the intent is to limit the range to exactly maxRangeDays calendar days, consider:

 return (date: Date) => {
   const daysFromStart = differenceInDays(date, startDate);
-  return daysFromStart > maxRangeDays;
+  return daysFromStart >= maxRangeDays;
 };

Otherwise, renaming to maxDaysFromStart would clarify the current behavior.


227-238: LGTM!

The Calendar integration with defaultMonth, disabled (for maxRangeDays), and formatters is well-structured. The formatWeekdayName formatter is consistent with the DatePicker component.

libs/react/ui/src/components/calendar/calendar.tsx (2)

20-20: Verify fixed positioning on navigation is intentional.

The nav class uses fixed left-0 top-16 w-full z-10. Fixed positioning is relative to the viewport, not the Calendar container. If the Calendar is rendered inside a scrollable popover or modal, the nav buttons may appear misaligned.

If the intent is to position relative to the calendar, consider using absolute instead:

-nav: 'flex items-center gap-4 fixed left-0 top-16 w-full z-10',
+nav: 'flex items-center gap-4 absolute left-0 top-16 w-full z-10',

68-72: LGTM!

The "today" indicator redesign using a subtle dot pseudo-element is a nice visual refinement. The positioning with after:bottom-[6px] and centered translation creates an unobtrusive but clear indicator.

libs/react/ui/src/components/dropdown-input/dropdown-input.tsx (3)

158-181: LGTM!

The keyboard navigation implementation is solid:

  • Arrow keys with wrap-around for circular navigation
  • Enter only triggers selection when an item is focused
  • Escape properly closes and blurs the input
  • preventDefault correctly used to prevent default browser behavior

183-202: LGTM!

The blur handling correctly uses a timeout pattern to allow dropdown item clicks to complete before potentially closing the popover. The cleanup effect at lines 196-202 properly clears pending timeouts on unmount.


220-295: LGTM!

The render implementation is well-structured:

  • onMouseDown with preventDefault correctly prevents the input blur before click handlers fire
  • onMouseEnter syncs keyboard and mouse focus states
  • scrollIntoView with block: 'nearest' (line 206) keeps focused items visible
  • Button elements with proper type="button" prevent form submission
libs/react/ui/package.json (1)

63-63: @tanstack/react-query ^5.90.5 is a valid peer dependency.

The DropdownInput component imports and uses useQuery from @tanstack/react-query, justifying the peer dependency addition. Version 5.90.5 exists on npm and has no known security vulnerabilities.

libs/react/ui/src/components/index.ts (1)

17-17: LGTM! Export addition follows the established pattern.

The new dropdown-input export is correctly placed in alphabetical order and follows the existing barrel export pattern used throughout the file.

libs/react/ui/src/components/dropdown-input/index.ts (1)

1-1: LGTM! Standard barrel export pattern.

The barrel export follows the established pattern used throughout the component library, enabling clean imports for the new DropdownInput component.

libs/react/ui/src/components/dropdown-input/dropdown-input.stories.tsx (4)

1-54: LGTM! Well-structured story setup with proper mock data.

The imports, constants, mock data, and async fetch helpers are well-organized. The fetch functions appropriately simulate network delay and filtering logic for demonstration purposes.


56-84: LGTM! Proper QueryClient configuration for Storybook.

The decorator correctly sets up a QueryClientProvider with appropriate options for Storybook (disabled retries and refetch on focus). The QueryClient is properly memoized to avoid unnecessary recreations.


89-115: LGTM! Well-structured play function for automated testing.

The play function appropriately orchestrates the test interaction, opening the modal and waiting for the UI to settle before capturing the screenshot for visual regression testing.


116-211: LGTM! Comprehensive demonstration of the DropdownInput component.

The render function provides an excellent real-world example integrating DropdownInput with other components. The state management correctly handles:

  • Input value changes
  • Selection state synchronization (clearing selection when value is manually edited)
  • Integration with DateTimeRangePicker

The modal structure effectively demonstrates the component in a realistic use case.

@noe-charmet
Copy link
Contributor

No need to change it this time, but this PR should have been 3 PRs. There is no specific reason here to make the updates to Input, DatePicker and the creation of the DoropDown to be done in a single change.
This would have made review and lead time of the change a lot faster

onFocusedIndexChange: (index: number) => void;
selectedItem?: DropdownInputItem<T> | null;
dropdownClassName?: string;
contactSupportHref?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess that this is a bit too specific to be in there

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like it's not really used neither

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad, removed it

@kylengn kylengn merged commit 3c4e65e into main Jan 7, 2026
5 checks passed
@kylengn kylengn deleted the feat/dropdown-input-and-updates branch January 7, 2026 15:18
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