Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(date-picker): month and year picker scroller #3789

Open
wants to merge 20 commits into
base: canary
Choose a base branch
from

Conversation

buchananwill
Copy link

@buchananwill buchananwill commented Sep 22, 2024

Closes #3785

📝 Description

Fixing the month/year UI sync bug.

⛳️ Current behavior (updates)

The month and year were incorrectly updating due to a UI rectangle intersection error. This has been removed and the data is updated directly from the value attribute.

🚀 New behavior

Please describe the behavior or changes this PR adds

💣 Is this a breaking change (Yes/No):

No.

📝 Additional Information

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Introduced the AccordionTree component for hierarchical data representation.
    • Added an aria label for the month/year picker toggle button to enhance accessibility.
    • Enhanced keyboard interaction capabilities in the calendar picker component.
    • Added a new custom hook for managing key press states and scroll events.
  • Bug Fixes
    • Improved user interface and data synchronization for the month/year picker component.
  • Refactor
    • Simplified scrolling behavior and focused date management in the calendar picker component.
  • Chores
    • Updated versions for @nextui-org/accordion and @nextui-org/calendar packages.
    • Added a new test case for the month and year picker functionality in the DatePicker component.

Copy link

changeset-bot bot commented Sep 22, 2024

🦋 Changeset detected

Latest commit: 214385f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@nextui-org/calendar Patch
@nextui-org/date-picker Patch
@nextui-org/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

vercel bot commented Sep 22, 2024

@buchananwill is attempting to deploy a commit to the NextUI Inc Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Sep 22, 2024

Walkthrough

This pull request introduces updates to three packages: @nextui-org/accordion, @nextui-org/calendar, and @nextui-org/date-picker. The @nextui-org/accordion package has received a minor version update, while the @nextui-org/calendar package has been patched. A new component, AccordionTree, has been added to support hierarchical data. Additionally, significant refactoring has been performed on the useCalendarPicker function, simplifying the scrolling behavior and focused date management. Accessibility improvements have also been made to the date picker component.

Changes

File Path Change Summary
.changeset/serious-snails-count.md Documented minor version update for @nextui-org/accordion and patch update for @nextui-org/calendar. Noted a fix related to the month/year picker component.
packages/components/accordion/src/accordion-tree.tsx Introduced AccordionTree component with interfaces AccordionTreeItemProps and AccordionTreeProps for hierarchical data support.
packages/components/calendar/src/calendar-header.tsx Added constant monthAndYearPickerToggle for the aria-label of the month/year picker toggle button, enhancing accessibility.
packages/components/calendar/src/calendar-picker-item.tsx Updated CalendarPickerItem to include onKeyUp prop for handling key-up events.
packages/components/calendar/src/calendar-picker.tsx Introduced onPickerItemKeyUp event handler for enhanced keyboard interaction in the calendar picker.
packages/components/calendar/src/use-calendar-picker.ts Removed handleListScroll function and debounced scroll event listeners, simplifying the logic for updating the focused date in the calendar picker component.
packages/components/date-picker/tests/date-picker.test.tsx Introduced a new test case for the month and year picker functionality, verifying button interactions and visibility of the dialog.

Assessment against linked issues

Objective Addressed Explanation
Ensure highlighted, focused, and selected values are consistent in Date Picker (#3785) Changes simplify scrolling but do not explicitly confirm resolution of the issue.

Possibly related PRs

Suggested labels

📋 Scope : Docs

Suggested reviewers

  • wingkwong
  • jrgarciadev
  • ryo-manba

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@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

Outside diff range and nitpick comments (1)
packages/components/calendar/src/use-calendar-picker.ts (1)

100-102: Avoid variable shadowing by renaming local variable date.

The local variable date declared in line 100 shadows the date prop from the outer scope. This could lead to confusion and potential bugs. Consider renaming the local variable to improve code clarity.

Apply this diff to rename the variable:

-let date = state.focusedDate.set(list === "months" ? {month: value} : {year: value});
+let updatedDate = state.focusedDate.set(list === "months" ? {month: value} : {year: value});

-state.setFocusedDate(date);
+state.setFocusedDate(updatedDate);
Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between bed4a59 and 66108cd.

Files selected for processing (3)
  • .changeset/serious-snails-count.md (1 hunks)
  • packages/components/accordion/src/accordion-tree.tsx (1 hunks)
  • packages/components/calendar/src/use-calendar-picker.ts (3 hunks)
Additional comments not posted (2)
.changeset/serious-snails-count.md (1)

1-6: LGTM!

The changeset file follows the correct format and provides a clear description of the changes made to the packages. The version updates are appropriate based on the nature of the changes:

  • @nextui-org/accordion: Minor version update for new backward-compatible functionality.
  • @nextui-org/calendar: Patch version update for backward-compatible bug fixes.

The description also provides a high-level overview of the fix related to the UI and data synchronization for the month/year picker component.

packages/components/calendar/src/use-calendar-picker.ts (1)

6-6: LGTM!

The import statement correctly adds useCallback from "react", which is appropriately used in the code.

packages/components/accordion/src/accordion-tree.tsx Outdated Show resolved Hide resolved
Copy link
Member

@wingkwong wingkwong left a comment

Choose a reason for hiding this comment

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

  1. please include a before/after video and include steps on how to reproduce the issue.
  2. please include the issue link if possible.
  3. please remove accordion code. it's not related to this PR.
  4. please include a test case.

Copy link
Contributor

@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

Outside diff range and nitpick comments (3)
.changeset/sixty-weeks-help.md (1)

1-7: LGTM! Consider a minor clarification in the description.

The changeset looks good and correctly identifies the packages being updated. The patch version bumps are appropriate for the described change.

Consider slightly expanding the description for clarity:

-Added an aria and test label for the picker toggle.
+Added an aria label for accessibility and a test label for the picker toggle to improve testability.

This minor change would provide more context about the purpose of each label.

packages/components/calendar/src/calendar-header.tsx (2)

104-104: LGTM: Excellent accessibility improvement

The addition of the aria-label attribute using the monthAndYearPickerToggle constant significantly enhances the accessibility of the calendar component. This change allows screen readers to provide clear context for the button's purpose.

Consider moving the aria-label attribute closer to other core Button props for better readability. For example:

 <Button
   {...headerProps}
-  aria-label={monthAndYearPickerToggle}
   disableAnimation={disableAnimation}
   endContent={<ChevronDownIcon className="chevron-icon" />}
+  aria-label={monthAndYearPickerToggle}
   onKeyDown={handleKeyDown}
   {...buttonPickerProps}
 >

This is a minor suggestion and doesn't affect functionality.


Verify alignment with PR objectives

The changes in calendar-header.tsx improve accessibility, which is commendable. However, they do not address the synchronization issue between the month and year in the date picker UI, as mentioned in the PR objectives.

Based on the search results, there are no other changes in this PR related to "datepicker synchronization," "UI rectangle intersection," or updating data directly from the value attribute. Therefore, the PR does not fully meet its stated goals.

Analysis chain

Line range hint 1-114: Verify alignment with PR objectives

The changes in this file improve accessibility, which is commendable. However, they don't seem to directly address the synchronization issue between the month and year in the date picker UI, as mentioned in the PR objectives.

Could you please clarify how these changes relate to fixing the "UI rectangle intersection error" and ensuring that "the data is updated directly from the value attribute"? If these issues are addressed in other files, it would be helpful to include those changes in the PR for a comprehensive review.

To assist in verifying the PR objectives, you can run the following script to search for related changes:

This will help identify any other relevant changes that might be part of this PR but weren't included in the current review.


To better verify the PR objectives, please run the following scripts to search within .ts and .tsx files for relevant changes:

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for changes related to date picker synchronization and UI rectangle intersection

# Test: Search for files with changes related to date picker, month, year, or synchronization
rg --type typescript -i '(datepicker|month|year|sync|intersection)'

# Test: Search for files with changes related to updating from value attribute
rg --type typescript -i '(value.*attribute|attribute.*value)'

Length of output: 203


Script:

#!/bin/bash
# Description: Search for changes related to date picker synchronization and UI rectangle intersection within TypeScript files.

# Search for files with .ts and .tsx extensions and look for relevant keywords
fd --extension ts --extension tsx | xargs rg -i '(datepicker|month|year|sync|intersection)'

# Search for files updating from the value attribute within TypeScript files
fd --extension ts --extension tsx | xargs rg -i '(value.*attribute|attribute.*value)'

Length of output: 122139

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 66108cd and e30ae3c.

Files selected for processing (3)
  • .changeset/sixty-weeks-help.md (1 hunks)
  • packages/components/calendar/src/calendar-header.tsx (2 hunks)
  • packages/components/date-picker/tests/date-picker.test.tsx (3 hunks)
Additional comments not posted (4)
packages/components/calendar/src/calendar-header.tsx (1)

21-22: LGTM: Good addition of a descriptive constant

The addition of the monthAndYearPickerToggle constant is a good practice. It provides a clear, reusable string for the aria-label, which enhances accessibility and maintainability.

packages/components/date-picker/__tests__/date-picker.test.tsx (3)

8-8: New import added for month and year picker toggle

The addition of this import suggests that new tests for the month and year picker functionality will be implemented. This is a good practice to ensure new features are properly tested.


528-528: New spy function added for value changes

The valueChangeSpy will be useful for tracking and asserting date value changes in the new tests. This is a good testing practice for verifying component behavior.


Line range hint 1-646: Comprehensive test coverage for DatePicker component

The additions to this test file significantly improve the coverage of the DatePicker component, particularly for the month and year picker functionality. The new tests are well-integrated with the existing test suite and follow established patterns. This enhancement will help ensure the reliability and correctness of the DatePicker component.

@buchananwill
Copy link
Author

  1. BEFORE: [BUG] - Date Picker Showing Wrong Year #3785 (comment) AFTER:
good.calendar.mp4
  1. Issue: https://github.com/nextui-org/nextui/issues/3785
  2. I've parked the accordion-tree on a different branch and delete that file in this commit.
  3. I added a test that checks the tabindex is correctly being modified, as that is uniquely 0 for the focused month/year in the picker.

Copy link
Member

@wingkwong wingkwong left a comment

Choose a reason for hiding this comment

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

just keep one changeset file where no accordion is mentioned

@buchananwill
Copy link
Author

just keep one changeset file where no accordion is mentioned

Is that correct now?

@wingkwong wingkwong self-requested a review September 23, 2024 12:11
@wingkwong wingkwong self-assigned this Sep 23, 2024
@wingkwong wingkwong added this to the v2.5.0 milestone Sep 23, 2024
Copy link

vercel bot commented Sep 24, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
nextui-docs-v2 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 25, 2024 0:44am
nextui-storybook-v2 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 25, 2024 0:44am

@wingkwong wingkwong requested review from ryo-manba and removed request for jrgarciadev September 25, 2024 11:53
@wingkwong wingkwong changed the title Fix/date picker month and year scroller fix(date-picker): month and year picker scroller Sep 25, 2024
@@ -99,6 +101,7 @@ export function CalendarHeader(props: CalendarHeaderProps) {
return showMonthAndYearPickers ? (
<Button
{...headerProps}
aria-label={monthAndYearPickerToggle}
Copy link
Member

Choose a reason for hiding this comment

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

we don't hardcode aria-label internally. We can take it from users' side tho.

@buchananwill
Copy link
Author

buchananwill commented Sep 25, 2024 via email

@buchananwill buchananwill requested a review from wingkwong October 2, 2024 14:52
Copy link
Contributor

@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

🧹 Outside diff range and nitpick comments (6)
packages/components/calendar/src/calendar-picker-item.tsx (1)

15-15: LGTM! Consider updating prop types for consistency.

The addition of the onKeyUp prop enhances the component's event handling capabilities, which is a positive change. This aligns well with React's event handling patterns and improves the component's flexibility.

For consistency, consider updating the AriaButtonProps type to include the onKeyUp prop if it's not already included. This ensures type safety and better documentation of the component's API.

packages/components/calendar/src/calendar-picker.tsx (2)

93-93: LGTM: onKeyUp handler added to month picker

The addition of the onKeyUp event handler to the month CalendarPickerItem is consistent with the new functionality. This will improve keyboard interaction for month selection.

For consistency with other event handlers, consider using an arrow function:

-              onKeyUp={(e) => onPickerItemKeyUp(e, month.value, "months")}
+              onKeyUp={e => onPickerItemKeyUp(e, month.value, "months")}

115-115: LGTM: onKeyUp handler added to year picker

The addition of the onKeyUp event handler to the year CalendarPickerItem is consistent with the new functionality and mirrors the change made to the month picker. This will improve keyboard interaction for year selection.

For consistency with other event handlers, consider using an arrow function:

-              onKeyUp={(e) => onPickerItemKeyUp(e, year.value, "years")}
+              onKeyUp={e => onPickerItemKeyUp(e, year.value, "years")}
packages/components/calendar/src/use-calendar-picker.ts (3)

128-142: Enhanced keyboard navigation control.

The addition of preventDefault for specific key events is a good improvement. It ensures better control over keyboard navigation within the picker.

Consider extracting the array of keys into a constant for better maintainability:

const PREVENTED_KEYS = [
  "ArrowDown",
  "ArrowUp",
  "Home",
  "End",
  "PageUp",
  "PageDown",
  "Escape",
  "Enter",
  " ",
];

if (PREVENTED_KEYS.includes(e.key)) {
  e.preventDefault();
}

180-181: Simplified scrolling logic.

The direct call to scrollTo simplifies the code and aligns with the new implementation. However, consider adding a bounds check for nextValue to prevent potential issues with out-of-range values.

Consider adding a safety check:

if (nextValue >= 0 && nextValue < (list === 'months' ? months.length : years.length)) {
  scrollTo(nextValue, list);
}

187-202: Good addition to address issue #3789.

The new onPickerItemKeyUp function effectively ensures that the selected item remains in view after keyboard interaction, addressing the synchronization issue mentioned in the PR objectives.

Consider adding a null check for listRef.current to prevent potential errors:

if (e.currentTarget && listRef.current) {
  scrollIntoView(e.currentTarget, {
    scrollMode: "always",
    behavior: "smooth",
    boundary: listRef.current,
  });
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e6a2f99 and 0a2c4c9.

📒 Files selected for processing (3)
  • packages/components/calendar/src/calendar-picker-item.tsx (1 hunks)
  • packages/components/calendar/src/calendar-picker.tsx (3 hunks)
  • packages/components/calendar/src/use-calendar-picker.ts (6 hunks)
🔇 Additional comments (7)
packages/components/calendar/src/calendar-picker-item.tsx (2)

Line range hint 1-53: Summary: Improved event handling enhances component flexibility.

The changes to the CalendarPickerItem component, specifically the addition of the onKeyUp prop and its integration with the useAriaButton hook, are well-implemented and align with the PR objectives. These modifications enhance the component's event handling capabilities, potentially contributing to resolving the synchronization issues mentioned in the PR description.

The changes are minimal, focused, and maintain the existing structure and accessibility features of the component. This update should improve the overall functionality of the date picker without introducing breaking changes.


23-23: LGTM! Verify useAriaButton hook implementation.

The addition of onKeyUp to the useAriaButton hook call is correct and consistent with the component's updated props. This ensures that the onKeyUp event is properly handled by the aria button implementation.

To ensure full compatibility, please verify that the useAriaButton hook in the @nextui-org/use-aria-button package correctly handles the onKeyUp prop. Run the following script to check the hook's implementation:

packages/components/calendar/src/calendar-picker.tsx (2)

30-30: LGTM: New event handler added

The addition of onPickerItemKeyUp to the destructured properties from useCalendarPicker hook is consistent with the intended changes. This new handler will enhance the keyboard interaction capabilities of the component.


30-30: Overall impact: Improved keyboard interaction for date picker

The addition of onKeyUp event handlers for both month and year picker items aligns well with the PR objectives. These changes should improve the synchronization between the selected date and the UI, addressing the issue described in #3785.

To ensure the changes fully resolve the reported bug:

Also applies to: 93-93, 115-115

packages/components/calendar/src/use-calendar-picker.ts (3)

6-6: LGTM: Import statement updated correctly.

The addition of useEffect to the import statement is appropriate, given the new side-effect management introduced in the component.


100-102: Improved date focusing logic.

The direct update of the focused date in the scrollTo function is a good improvement. It simplifies the logic and reduces the potential for synchronization issues that could occur with the previous scroll position calculations.

This change aligns well with the PR objective of resolving synchronization issues in the date picker UI.


Line range hint 1-221: Overall assessment: Improvements align well with PR objectives.

The changes in this file effectively address the synchronization issue in the date picker UI as outlined in the PR objectives. The removal of complex scroll handling logic and the introduction of more direct state updates simplify the code and reduce the potential for synchronization errors. The new keyboard navigation controls and the addition of the onPickerItemKeyUp function further enhance the component's usability and accessibility.

A few minor suggestions have been made for additional safety checks and code organization, but these are not critical. The overall changes represent a significant improvement in the component's functionality and maintainability.

Copy link
Contributor

@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

🧹 Outside diff range and nitpick comments (3)
packages/components/calendar/src/use-calendar-picker.ts (3)

128-142: Enhanced keyboard navigation in onPickerItemKeyDown

The addition of a check to prevent default behavior for specific keys is a valuable improvement to the onPickerItemKeyDown function. This change enhances keyboard navigation and accessibility, aligning with the PR objective of improving the date picker's functionality.

However, to improve readability, consider extracting the array of keys into a constant:

const PREVENTED_KEYS = [
  "ArrowDown",
  "ArrowUp",
  "Home",
  "End",
  "PageUp",
  "PageDown",
  "Escape",
  "Enter",
  " ",
];

if (PREVENTED_KEYS.includes(e.key)) {
  e.preventDefault();
}

This refactoring would make the code more maintainable and easier to update in the future.


187-202: New onPickerItemKeyUp function improves UI consistency

The addition of the onPickerItemKeyUp function is a valuable improvement:

  1. It addresses the issue mentioned in fix(date-picker): month and year picker scroller #3789 by ensuring the selected item is always visible after a key event.
  2. The use of smooth scrolling enhances the user experience.
  3. The function is properly memoized with useCallback for performance optimization.

To further improve the code, consider extracting the scrollIntoView options into a constant, as they are used in multiple places:

const SCROLL_OPTIONS = {
  scrollMode: "always" as const,
  behavior: "smooth" as const,
};

// Usage
scrollIntoView(e.currentTarget, {
  ...SCROLL_OPTIONS,
  boundary: listRef.current,
});

This would reduce duplication and make it easier to maintain consistent scrolling behavior across the component.


Line range hint 1-222: Overall assessment: Significant improvements to date picker functionality

This PR successfully addresses the main objectives:

  1. Fixes the synchronization issue between the month and year in the date picker UI.
  2. Improves keyboard navigation and accessibility.
  3. Enhances the overall user experience of the date picker component.

The changes are well-implemented and follow good coding practices. The removal of the handleListScroll function and the simplification of the scrollTo function have streamlined the code, making it more maintainable.

To further improve the code:

  1. Consider extracting repeated code patterns into constants or utility functions, as suggested in previous comments.
  2. Add comments explaining the purpose of the new onPickerItemKeyUp function and how it relates to issue fix(date-picker): month and year picker scroller #3789.
  3. Update the component's documentation to reflect these new behaviors and keyboard interactions.

Great work on addressing the reported issues and enhancing the component's functionality!

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0a2c4c9 and 5ec489b.

📒 Files selected for processing (1)
  • packages/components/calendar/src/use-calendar-picker.ts (7 hunks)
🔇 Additional comments (3)
packages/components/calendar/src/use-calendar-picker.ts (3)

6-6: LGTM: Import statement updated to include useEffect

The addition of useEffect to the import statement is appropriate given the changes in the component's lifecycle management. This change aligns with the removal of the handleListScroll function and indicates a shift towards using React's built-in hooks for managing side effects.


100-102: Improved date synchronization in scrollTo function

The simplification of the scrollTo function, particularly the direct update of the focused date using state.setFocusedDate, is a significant improvement. This change:

  1. Enhances readability and maintainability of the code.
  2. Ensures immediate synchronization between the UI and the state.
  3. Directly addresses the synchronization issue mentioned in the PR objectives.

This modification effectively resolves the core problem described in issue #3785, where the displayed year did not match the selected year.


158-161: Improved 'Home' and 'End' key functionality

The updates to the 'Home' and 'End' key handling in the onPickerItemKeyDown function are well-implemented:

  1. Setting nextValue to 1 for the 'Home' key ensures navigation to the start of the list.
  2. Using months.length for the 'End' key is a more dynamic and correct approach, ensuring navigation to the end of the list regardless of the number of months.

These changes enhance the user experience by providing more intuitive and reliable navigation within the date picker.

@buchananwill
Copy link
Author

I had one more idea, which I've added. The PickerItems now also have a keyUp listener, which just calls the scrollIntoView function. This seems to be a bullet proof way of ensuring that if you've been holding down any of the directional keys, then whatever was the last key to fire (and thus has focus) is scrolled to the desired position.

I have to confess my weaker experience with writing imperative DOM events, so please if anyone with that could ponder whether this broad-brush fix could have any unexpected side-effects?

I also changed the home/end values of the keyDown event to 1 and 12, since months are 1-indexed, not zero indexed. These buttons don't work for the year picker, since 1 and 12 aren't valid years within the available range. Couldn't see an obvious way to pass in the max/min years that would be dynamically set (since it could vary with locale etc.).

Hopefully this wraps up the functionality of this component & hook?

packages/components/calendar/src/use-calendar-picker.ts Outdated Show resolved Hide resolved
Comment on lines +641 to +643
expect(valueChangeSpy).not.toHaveBeenCalled();
expect(monthBefore).toHaveAttribute("tabindex", "0");
expect(yearBefore).toHaveAttribute("tabindex", "0");
Copy link
Member

Choose a reason for hiding this comment

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

Please also check the following two points:

  1. Ensure that the value in the header is synchronized.
  2. Verify that pressing the Enter key triggers the valueChangeSpy.

Copy link
Member

Choose a reason for hiding this comment

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

Please add tests.

@ryo-manba
Copy link
Member

The Home/End keys seem to already function in the original implementation. Where did you find the information about the month's index?

I also changed the home/end values of the keyDown event to 1 and 12, since months are 1-indexed, not zero indexed. These buttons don't work for the year picker, since 1 and 12 aren't valid years within the available range.

@buchananwill buchananwill requested a review from ryo-manba October 3, 2024 15:57
Copy link
Contributor

@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: 4

🧹 Outside diff range and nitpick comments (7)
packages/components/calendar/src/use-key-repeat-blocker.ts (2)

10-51: LGTM: Well-implemented custom hook with proper use of React hooks.

The useKeyRepeatBlocker hook is well-implemented, making appropriate use of useRef for mutable state and useCallback for function memoization. The logic for blocking key repeats is sound and aligns with React best practices.

A minor suggestion for improvement:

Consider adding a cleanup function using useEffect to reset the keyPressedRef when the component unmounts. This can prevent potential memory leaks in long-running applications.

useEffect(() => {
  return () => {
    keyPressedRef.current = {};
  };
}, []);

1-51: Excellent implementation addressing PR objectives.

This new useKeyRepeatBlocker hook is a well-crafted solution that directly addresses the PR's objective of improving date picker functionality. By providing a reusable mechanism for blocking key repeats, it can effectively resolve the synchronization issues mentioned in the PR summary.

Key points:

  1. The hook is implemented efficiently, using useRef and useCallback to minimize re-renders.
  2. It's type-safe and well-documented through clear interface definitions.
  3. The solution is generic enough to be potentially useful beyond just the date picker, promoting code reuse.

This implementation should significantly contribute to resolving the UI rectangle intersection error mentioned in the PR objectives, as it provides fine-grained control over key press handling.

Consider documenting this hook in your project's shared components or utilities, as it could be beneficial for other UI components that require similar key repeat blocking functionality.

packages/components/calendar/src/use-calendar-picker.ts (4)

6-6: LGTM! Consider using an enum for LISTENED_NAVIGATION_KEYS.

The new imports and constants are well-defined and support the updated scrolling and key navigation behavior. Good job on extracting these values for better maintainability.

Consider using an enum for LISTENED_NAVIGATION_KEYS to improve type safety:

enum NavigationKeys {
  ArrowDown = "ArrowDown",
  ArrowUp = "ArrowUp",
  Home = "Home",
  End = "End",
  PageUp = "PageUp",
  PageDown = "PageDown",
  Escape = "Escape",
  Enter = "Enter",
  Space = " ",
}

const LISTENED_NAVIGATION_KEYS = Object.values(NavigationKeys);

Also applies to: 11-12, 27-50


151-167: Great improvements to scroll and key handling!

The introduction of useScrollEndCallback and useKeyRepeatBlocker hooks, along with the getBoundaryValue function, significantly improves the handling of scroll events, key repeat behavior, and boundary values. The update to the scrollTo function to set the focused date is a good synchronization improvement.

Consider adding a comment explaining the purpose of OF_100_MILLISECONDS to improve code readability:

// Delay for scroll end detection in milliseconds
const OF_100_MILLISECONDS = 100;

Also applies to: 127-129


235-262: Excellent addition to fix synchronization issues!

The new onPickerItemKeyUp function is a great solution to address the synchronization issue mentioned in issue #3789. The safety scroll implementation ensures that the UI stays in sync with keyboard navigation, preventing the UI rectangle intersection error.

Consider adding a brief comment explaining the purpose of this function and how it addresses the synchronization issue:

// Ensures UI synchronization after key navigation, addressing issue #3789
const onPickerItemKeyUp = useCallback(
  // ... (rest of the function)
);

277-277: Great overall improvements to the date picker functionality!

The changes in this PR successfully address the synchronization issues between the month and year in the date picker UI. The new key handling logic, improved scrolling behavior, and the addition of onPickerItemKeyUp collectively enhance the user experience and resolve the reported bugs.

Consider adding unit tests for the new onPickerItemKeyUp function and the updated onPickerItemKeyDown to ensure the correct behavior is maintained in future updates. This will help prevent regression and improve the overall robustness of the component.

packages/components/calendar/src/use-scroll-end-callback.ts (1)

50-50: Remove redundant comment

The comment "You can adjust the delay as necessary" is unnecessary because the debounce duration is already adjustable through the parameter. Removing it cleans up the code.

Apply this diff:

          }, debounce); // You can adjust the delay as necessary
+        }, debounce);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 5ec489b and b96542a.

📒 Files selected for processing (3)
  • packages/components/calendar/src/use-calendar-picker.ts (7 hunks)
  • packages/components/calendar/src/use-key-repeat-blocker.ts (1 hunks)
  • packages/components/calendar/src/use-scroll-end-callback.ts (1 hunks)
🔇 Additional comments (3)
packages/components/calendar/src/use-key-repeat-blocker.ts (2)

1-1: LGTM: Imports are appropriate and minimal.

The imports of useCallback and useRef from 'react' are suitable for this implementation, providing the necessary hooks without any superfluous imports.


3-8: LGTM: Well-defined interface for the hook's return value.

The UseKeyRepeatBlockerReturn interface is clearly defined, providing a robust type structure for the hook's return value. The method signatures are descriptive and use TypeScript effectively for type safety.

packages/components/calendar/src/use-calendar-picker.ts (1)

Line range hint 169-233: Excellent improvements to key handling and navigation!

The updates to onPickerItemKeyDown significantly enhance the key navigation experience. The new logic for Home and End keys using getBoundaryValue improves boundary handling. The improved scrolling and focus management should resolve synchronization issues.

This change addresses the previous comment by ryo-manba about the Home/End key behavior:

  1. The year is now changing correctly.
  2. The smooth scrolling behavior has been maintained while ensuring correct focus management.

Great job on implementing these improvements!

Comment on lines +6 to +7
const abortRef = useRef<(() => void) | null>();
const onScrollRef = useRef<(() => void) | null>();
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Initialize abortRef and onScrollRef to null

Currently, abortRef and onScrollRef are declared without initial values. Initializing them explicitly to null enhances code clarity and ensures consistent types.

Apply this diff:

-  const abortRef = useRef<(() => void) | null>();
-  const onScrollRef = useRef<(() => void) | null>();
+  const abortRef = useRef<(() => void) | null>(null);
+  const onScrollRef = useRef<(() => void) | null>(null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const abortRef = useRef<(() => void) | null>();
const onScrollRef = useRef<(() => void) | null>();
const abortRef = useRef<(() => void) | null>(null);
const onScrollRef = useRef<(() => void) | null>(null);

Comment on lines +9 to +20
const clearListener = useCallback(() => {
if (elementRef.current && onScrollRef.current) {
elementRef.current.removeEventListener("scroll", onScrollRef.current);
}
// Remove the event listener and clear timeout when the component unmounts
if (abortRef.current) {
abortRef.current();
}
abortRef.current = null;
onScrollRef.current = null;
elementRef.current = null;
}, [elementRef]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Adjust the dependency array of clearListener

The clearListener function includes elementRef in its dependency array. Since elementRef is a ref object that doesn't change identity, and you are accessing elementRef.current, it's safe to use an empty dependency array. This prevents unnecessary re-creations of the callback.

Apply this diff:

-  }, [elementRef]);
+  }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const clearListener = useCallback(() => {
if (elementRef.current && onScrollRef.current) {
elementRef.current.removeEventListener("scroll", onScrollRef.current);
}
// Remove the event listener and clear timeout when the component unmounts
if (abortRef.current) {
abortRef.current();
}
abortRef.current = null;
onScrollRef.current = null;
elementRef.current = null;
}, [elementRef]);
const clearListener = useCallback(() => {
if (elementRef.current && onScrollRef.current) {
elementRef.current.removeEventListener("scroll", onScrollRef.current);
}
// Remove the event listener and clear timeout when the component unmounts
if (abortRef.current) {
abortRef.current();
}
abortRef.current = null;
onScrollRef.current = null;
elementRef.current = null;
}, []);

Comment on lines 30 to 33
(element: HTMLElement | null, callback: () => void) => {
if (!element) return;
elementRef.current = element;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent multiple listeners by clearing previous ones

When onScrollEnd is called multiple times with different elements, the previous scroll event listeners remain attached, leading to potential memory leaks or unexpected behavior. Before setting up a new listener, ensure that any existing listener is removed.

Apply this diff to fix the issue:

    const onScrollEnd = useCallback(
      (element: HTMLElement | null, callback: () => void) => {
+       clearListener();
        if (!element) return;

Committable suggestion was skipped due to low confidence.

[debounce],
);

return {onScrollEnd, abortRef};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reconsider returning abortRef

Currently, the hook returns abortRef, which exposes an internal ref object. For a cleaner API, consider returning the abort function directly or providing an abort method. This makes it more intuitive for consumers to cancel the timeout or listener without accessing internal refs.

Example modification:

   return { onScrollEnd, abortRef };
+  // Instead of returning abortRef, consider returning the abort function
+  return { onScrollEnd, abort };

Committable suggestion was skipped due to low confidence.

@buchananwill
Copy link
Author

I added two additional hooks to cover the different behaviours needed for the different navigation keys:

  1. Home/End use a deferred focus that only triggers when the scroll ends. Otherwise, the focus event overrides the scrolling, and the element immediately snaps into view.
  2. Home/End also use a repeated key blocker, to prevent the value from receiving more than one update from these keys, since
    a. repetition of this event is meaningless
    b. it was playing havoc with the other scrolling/focus events/behaviours.

If you have existing hooks with the required API already in the codebase, feel free to swap them in. Equally, feel free to migrate them to a different (generic) hook folder if you want to centralize them for other scenarios to use.

I can't find any more bugs in this component.

On a side note, I prefer the simpler version where home/end cause the instant scrolling. I get that this is a minor inconsistency in the style of the UI, but the two extra control flow checks to defer the focus update and block repeat key strokes seems overly convoluted. I wouldn't be surprised to find it has negative performance implications or introduces other bugs on different systems from my own...

Let me know what you think! :-)

@buchananwill
Copy link
Author

Yep there was one more bug where the listener wasn't clearing correctly. Fixed that now. cries in open source

Copy link
Contributor

@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

🧹 Outside diff range and nitpick comments (2)
packages/components/calendar/src/use-calendar-picker.ts (2)

151-167: Enhanced key event handling and boundary value management

The additions of abortRef, handleKeyDown, handleKeyUp, and isKeyDown improve the handling of key events. The getBoundaryValue function provides a reusable way to determine min/max values for months and years, enhancing code maintainability.

Consider adding type annotations to the getBoundaryValue function for improved readability:

const getBoundaryValue = useCallback(
  (list: CalendarPickerListType, bound: "min" | "max"): number => {
    // ... existing implementation
  },
  [minValue, maxValue]
);

235-258: Enhanced key up event handling

The new onPickerItemKeyUp function improves the reliability of the picker's navigation:

  1. It handles key up events for navigation keys, preventing default behavior when necessary.
  2. Implements a safety scroll to the element that fired the event, addressing issue fix(date-picker): month and year picker scroller #3789.
  3. Uses different behavior for keys that need deferred focus, improving user experience.

Consider adding error handling for the scrollIntoView function to gracefully handle any potential errors:

try {
  scrollIntoView(e.currentTarget, {
    scrollMode: "always",
    behavior: "smooth",
    boundary: listRef.current,
  });
} catch (error) {
  console.error("Error scrolling into view:", error);
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b96542a and 214385f.

📒 Files selected for processing (2)
  • packages/components/calendar/src/use-calendar-picker.ts (7 hunks)
  • packages/components/calendar/src/use-scroll-end-callback.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/components/calendar/src/use-scroll-end-callback.ts
🔇 Additional comments (4)
packages/components/calendar/src/use-calendar-picker.ts (4)

6-6: Improved event handling and code organization

The new imports, custom hooks, and constants enhance the component's functionality and improve code readability:

  1. The addition of useCallback, useEffect, and useRef suggests optimized rendering and better management of side effects.
  2. Custom hooks useScrollEndCallback and useKeyRepeatBlocker indicate improved handling of scroll and key events.
  3. New constants provide clear definitions for boundary values and key handling, making the code more maintainable.

These changes contribute to a more robust and efficient implementation of the calendar picker.

Also applies to: 11-12, 27-50


273-273: Extended hook return value

The addition of onPickerItemKeyUp to the returned object from useCalendarPicker is appropriate. This change makes the new key up event handling function available to the component using this hook, allowing for improved interaction handling.


Line range hint 1-278: Overall implementation review

The changes in this file significantly improve the calendar picker's functionality and user experience:

  1. Enhanced key navigation and scroll behavior, addressing the issues with Home/End keys.
  2. Improved event handling with the introduction of custom hooks and new utility functions.
  3. Better code organization and readability through the use of constants and clear function definitions.
  4. Addressed synchronization issues between the month and year in the date picker UI, as mentioned in the PR objectives.

These improvements align well with the PR objectives and resolve the issues mentioned in the past review comments. The code is well-structured, follows good practices, and should provide a more reliable and user-friendly date picker component.


Line range hint 168-233: Improved key navigation and scroll behavior

The changes in the onPickerItemKeyDown function significantly improve the calendar picker's navigation:

  1. The new logic for Home and End keys now correctly updates the year and provides smooth scrolling.
  2. The implementation of deferred focus for Home and End keys enhances the user experience.
  3. The use of abortRef and onScrollEnd ensures proper management of scroll behavior.

These improvements address the issues mentioned in the past review comments about Home/End key behavior.

To ensure that the Home/End key behavior is fully fixed, please run the following test script:

✅ Verification successful

Update Verification Scripts for Home/End Key Behavior

The previous scripts failed to recognize the typescript file type. Updated scripts now target .ts files directly.


Home/End Key Behavior Verified

The implementation of Home/End key handling, year updates, and smooth scrolling in the onPickerItemKeyDown function has been successfully verified based on the shell script results.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the Home/End key behavior in the calendar picker

# Test: Search for the implementation of Home and End key handling
rg --type typescript 'case "Home":|case "End":' packages/components/calendar/src/use-calendar-picker.ts

# Test: Check if the year is being updated
rg --type typescript 'nextValue = getBoundaryValue\(list, "(min|max)"\);' packages/components/calendar/src/use-calendar-picker.ts

# Test: Verify smooth scrolling implementation
rg --type typescript 'scrollTo\(nextValue, list\);' packages/components/calendar/src/use-calendar-picker.ts

Length of output: 452


Script:

#!/bin/bash
# Description: Verify the Home/End key behavior in the calendar picker

# Test: Search for the implementation of Home and End key handling
rg 'case "Home":|case "End":' --glob '*.ts' packages/components/calendar/src/use-calendar-picker.ts

# Test: Check if the year is being updated
rg 'nextValue = getBoundaryValue\(list, "(min|max)"\);' --glob '*.ts' packages/components/calendar/src/use-calendar-picker.ts

# Test: Verify smooth scrolling implementation
rg 'scrollTo\(nextValue, list\);' --glob '*.ts' packages/components/calendar/src/use-calendar-picker.ts

Length of output: 554

@ryo-manba
Copy link
Member

On a side note, I prefer the simpler version where home/end cause the instant scrolling. I get that this is a minor inconsistency in the style of the UI, but the two extra control flow checks to defer the focus update and block repeat key strokes seems overly convoluted. I wouldn't be surprised to find it has negative performance implications or introduces other bugs on different systems from my own...

This seems to be a separate issue from the current one. If you'd like to address it, please open a new issue for further discussion.

@@ -0,0 +1,69 @@
import {useCallback, useEffect, useRef} from "react";

function useScrollEndCallback(debounce: number) {
Copy link
Member

Choose a reason for hiding this comment

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

The value is not being synced during mouse scroll. Please fix it.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Copy link
Author

Choose a reason for hiding this comment

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

Could you please specify the tests you need? Given that original component had tests that failed to catch this bug, it's a somewhat open-ended task. My current codebase uses with a different UI library, so it's difficult to justify spending any more time on the NextUI repo at the moment, but if you can ring-fence exactly what needs doing I will make time for that.

Comment on lines +641 to +643
expect(valueChangeSpy).not.toHaveBeenCalled();
expect(monthBefore).toHaveAttribute("tabindex", "0");
expect(yearBefore).toHaveAttribute("tabindex", "0");
Copy link
Member

Choose a reason for hiding this comment

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

Please add tests.

@lukasssicevs
Copy link

Any updates on this PR?

@wingkwong wingkwong modified the milestones: v2.6.0, v2.7.0 Nov 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] - Date Picker Showing Wrong Year
4 participants