Skip to content

Commit

Permalink
fix(use-calendar-picker): fixed the UI month/year sync (nextui-org#3785)
Browse files Browse the repository at this point in the history
  • Loading branch information
buchananwill committed Sep 22, 2024
1 parent 3b77e2e commit 5f35a84
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 120 deletions.
105 changes: 50 additions & 55 deletions packages/components/accordion/src/accordion-tree.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,63 @@
import { AccordionProps } from '@nextui-org/react';
import {
Accordion,
AccordionItem,
AccordionItemProps
} from '@nextui-org/accordion';
import { forwardRef, ReactNode } from 'react';
import {forwardRef, ReactNode} from "react";

export interface AccordionTreeItemProps
extends Omit<AccordionItemProps, 'children'> {
import AccordionItem, {AccordionItemProps} from "./accordion-item";
import Accordion, {AccordionProps} from "./accordion";

export interface AccordionTreeItemProps extends Omit<AccordionItemProps, "children"> {
contentMain?: ReactNode;
contentChildren?: AccordionTreeItemProps[];
}

export interface AccordionTreeProps extends Omit<AccordionProps, 'children'> {
export interface AccordionTreeProps extends Omit<AccordionProps, "children"> {
items: AccordionTreeItemProps[];
}

const AccordionTree = forwardRef<HTMLDivElement, AccordionTreeProps>(
(props, ref) => {
const {
items,
itemClasses: propsItemClasses,
className,
isCompact = true,
selectionMode = 'multiple',
...otherProps
} = props;
const AccordionTree = forwardRef<HTMLDivElement, AccordionTreeProps>((props, ref) => {
const {
items,
itemClasses: propsItemClasses,
className,
isCompact = true,
selectionMode = "multiple",
...otherProps
} = props;

const itemClasses = {
...propsItemClasses,
trigger: propsItemClasses?.trigger ?? "py-1",
base: propsItemClasses?.base ?? "py-0",
};

const itemClasses = {
...propsItemClasses,
trigger: propsItemClasses?.trigger ?? 'py-1',
base: propsItemClasses?.base ?? 'py-0'
};
return (
<Accordion
ref={ref}
{...otherProps}
className={className}
isCompact={isCompact}
itemClasses={itemClasses}
selectionMode={selectionMode}
>
{items.map((item, index) => {
const {contentMain, contentChildren, ...itemProps} = item;

return (
<Accordion
ref={ref}
{...otherProps}
selectionMode={selectionMode}
isCompact={isCompact}
itemClasses={itemClasses}
className={className}
>
{items.map((item, index) => {
const { contentMain, contentChildren, ...itemProps } = item;
return (
<AccordionItem key={index} {...itemProps}>
{contentMain && contentMain}
{contentChildren && (
<AccordionTree
itemClasses={itemClasses}
isCompact={isCompact}
selectionMode={selectionMode}
items={contentChildren}
/>
)}
</AccordionItem>
);
})}
</Accordion>
);
}
);
return (
<AccordionItem key={index} {...itemProps}>
{contentMain && contentMain}
{contentChildren && (
<AccordionTree
isCompact={isCompact}
itemClasses={itemClasses}
items={contentChildren}
selectionMode={selectionMode}
/>
)}
</AccordionItem>
);
})}
</Accordion>
);
});

AccordionTree.displayName = 'NextUI.AccordionTree';
AccordionTree.displayName = "NextUI.AccordionTree";

export default AccordionTree;
70 changes: 5 additions & 65 deletions packages/components/calendar/src/use-calendar-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import type {PressEvent} from "@react-types/shared";

import {useDateFormatter} from "@react-aria/i18n";
import {HTMLNextUIProps} from "@nextui-org/system";
import {useCallback, useRef, useEffect} from "react";
import debounce from "lodash.debounce";
import {areRectsIntersecting} from "@nextui-org/react-utils";
import {useCallback, useEffect, useRef} from "react";
import scrollIntoView from "scroll-into-view-if-needed";

import {getMonthsInYear, getYearRange} from "./utils";
Expand All @@ -15,6 +13,7 @@ export type PickerValue = {
value: string;
label: string;
};

export interface CalendarPickerProps extends HTMLNextUIProps<"div"> {
date: CalendarDate;
currentMonth: CalendarDate;
Expand All @@ -23,8 +22,6 @@ export interface CalendarPickerProps extends HTMLNextUIProps<"div"> {
type ItemsRefMap = Map<number, HTMLElement>;
type CalendarPickerListType = "months" | "years";

const SCROLL_DEBOUNCE_TIME = 200;

export function useCalendarPicker(props: CalendarPickerProps) {
const {date, currentMonth} = props;

Expand Down Expand Up @@ -83,36 +80,6 @@ export function useCalendarPicker(props: CalendarPickerProps) {
}
}

const handleListScroll = useCallback(
(e: Event, highlightEl: HTMLElement | null, list: CalendarPickerListType) => {
if (!(e.target instanceof HTMLElement)) return;

const map = getItemsRefMap(list === "months" ? monthsItemsRef : yearsItemsRef);

const items = Array.from(map.values());

const item = items.find((itemEl) => {
const rect1 = itemEl.getBoundingClientRect();
const rect2 = highlightEl?.getBoundingClientRect();

if (!rect2) {
return false;
}

return areRectsIntersecting(rect1, rect2);
});

const itemValue = Number(item?.getAttribute("data-value"));

if (!itemValue) return;

let date = state.focusedDate.set(list === "months" ? {month: itemValue} : {year: itemValue});

state.setFocusedDate(date);
},
[state, isHeaderExpanded],
);

// scroll to the selected month/year when the component is mounted/opened/closed
useEffect(() => {
if (!isHeaderExpanded) return;
Expand All @@ -121,36 +88,6 @@ export function useCalendarPicker(props: CalendarPickerProps) {
scrollTo(date.year, "years", false);
}, [isHeaderExpanded]);

useEffect(() => {
// add scroll event listener to monthsListRef
const monthsList = monthsListRef.current;
const yearsList = yearsListRef.current;
const highlightEl = highlightRef.current;

if (!highlightEl) return;

const debouncedHandleMonthsScroll = debounce(
(e: Event) => handleListScroll(e, highlightEl, "months"),
SCROLL_DEBOUNCE_TIME,
);
const debouncedHandleYearsScroll = debounce(
(e: Event) => handleListScroll(e, highlightEl, "years"),
SCROLL_DEBOUNCE_TIME,
);

monthsList?.addEventListener("scroll", debouncedHandleMonthsScroll);
yearsList?.addEventListener("scroll", debouncedHandleYearsScroll);

return () => {
if (debouncedHandleMonthsScroll) {
monthsList?.removeEventListener("scroll", debouncedHandleMonthsScroll);
}
if (debouncedHandleYearsScroll) {
yearsList?.removeEventListener("scroll", debouncedHandleYearsScroll);
}
};
}, [handleListScroll]);

function scrollTo(value: number, list: CalendarPickerListType, smooth = true) {
const mapListRef = list === "months" ? monthsItemsRef : yearsItemsRef;
const listRef = list === "months" ? monthsListRef : yearsListRef;
Expand All @@ -160,6 +97,9 @@ export function useCalendarPicker(props: CalendarPickerProps) {
const node = map.get(value);

if (!node) return;
let date = state.focusedDate.set(list === "months" ? {month: value} : {year: value});

state.setFocusedDate(date);

// scroll picker list to the selected item
scrollIntoView(node, {
Expand Down

0 comments on commit 5f35a84

Please sign in to comment.