From 64935195701a1d132a162aebe05c70435db22d69 Mon Sep 17 00:00:00 2001 From: michascant <89426143+MichaScant@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:33:17 -0500 Subject: [PATCH 1/7] first --- .../src/components/DatePicker/DatePicker.tsx | 144 +++++++++++------- .../DatePicker/components/Month/Month.tsx | 70 ++++++--- .../Listbox/components/Option/Option.tsx | 16 +- 3 files changed, 149 insertions(+), 81 deletions(-) diff --git a/polaris-react/src/components/DatePicker/DatePicker.tsx b/polaris-react/src/components/DatePicker/DatePicker.tsx index 2d7a8aab2f1..a465cd2b31a 100644 --- a/polaris-react/src/components/DatePicker/DatePicker.tsx +++ b/polaris-react/src/components/DatePicker/DatePicker.tsx @@ -25,13 +25,15 @@ export interface DatePickerProps { /** ID for the element */ id?: string; /** The selected date or range of dates */ - selected?: Date | Range; + selected?: Date | Range | Date[]; /** The month to show, from 0 to 11. 0 is January, 1 is February ... 11 is December */ month: number; /** The year to show */ year: number; /** Allow a range of dates to be selected */ allowRange?: boolean; + /** Allow multiple dates to be selected */ + allowMultiple?: boolean; /** Disable selecting dates before this. */ disableDatesBefore?: Date; /** Disable selecting dates after this. */ @@ -48,7 +50,7 @@ export interface DatePickerProps { /** Visually hidden prefix text for selected days on single selection date pickers */ dayAccessibilityLabelPrefix?: string; /** Callback when date is selected. */ - onChange?(date: Range): void; + onChange?(date: Range | Date[]): void; /** Callback when month is changed. */ onMonthChange?(month: number, year: number): void; } @@ -59,6 +61,7 @@ export function DatePicker({ month, year, allowRange, + allowMultiple, multiMonth, disableDatesBefore, disableDatesAfter, @@ -92,14 +95,32 @@ export function DatePicker({ ); const handleDateSelection = useCallback( - (range: Range) => { - const {end} = range; + (range: Range | Date) => { + if (allowMultiple) { + const newSelected = Array.isArray(selected) ? [...selected] : []; + const dateToAdd = range instanceof Date ? range : range.start; + + const existingIndex = newSelected.findIndex((date) => + date.getTime() === dateToAdd.getTime() + ); + + if (existingIndex >= 0) { + newSelected.splice(existingIndex, 1); + } else { + newSelected.push(dateToAdd); + } - setHoverDate(end); - setFocusDate(new Date(end)); - onChange(range); + setHoverDate(dateToAdd); + setFocusDate(dateToAdd); + onChange(newSelected); + } else { + const rangeValue = range instanceof Date ? {start: range, end: range} : range; + setHoverDate(rangeValue.end); + setFocusDate(new Date(rangeValue.end)); + onChange(rangeValue); + } }, - [onChange], + [allowMultiple, onChange, selected], ); const handleMonthChangeClick = useCallback( @@ -122,7 +143,7 @@ export function DatePicker({ const {key} = event; const range = deriveRange(selected); - const focusedDate = focusDate || (range && range.start); + const focusedDate = focusDate || (Array.isArray(range) ? range[0]?.start : range?.start); if (focusedDate == null) { return; @@ -196,6 +217,25 @@ export function DatePicker({ ], ); + const monthIsSelected = useMemo(() => { + if (allowMultiple && Array.isArray(selected)) { + return selected.map((date) => ({start: date, end: date})); + } + return deriveRange(selected); + }, [selected, allowMultiple]); + + const firstDatePickerAccessibilityLabelPrefix = allowRange + ? i18n.translate(`Polaris.DatePicker.start`) + : dayAccessibilityLabelPrefix; + const secondDatePickerAccessibilityLabelPrefix = i18n.translate( + `Polaris.DatePicker.end`, + ); + + const accessibilityLabelPrefixes: [string | undefined, string] = [ + firstDatePickerAccessibilityLabelPrefix, + secondDatePickerAccessibilityLabelPrefix, + ]; + const showNextYear = getNextDisplayYear(month, year); const showNextMonth = getNextDisplayMonth(month); @@ -215,20 +255,6 @@ export function DatePicker({ : i18n.translate(`Polaris.DatePicker.months.${monthName(showNextMonth)}`); const nextYear = multiMonth ? showNextToNextYear : showNextYear; - const monthIsSelected = useMemo(() => deriveRange(selected), [selected]); - - const firstDatePickerAccessibilityLabelPrefix = allowRange - ? i18n.translate(`Polaris.DatePicker.start`) - : dayAccessibilityLabelPrefix; - const secondDatePickerAccessibilityLabelPrefix = i18n.translate( - `Polaris.DatePicker.end`, - ); - - const accessibilityLabelPrefixes: [string | undefined, string] = [ - firstDatePickerAccessibilityLabelPrefix, - secondDatePickerAccessibilityLabelPrefix, - ]; - const secondDatePicker = multiMonth ? ( ) : null; - const datePickerClassName = classNames(styles.DatePicker); - return (
@@ -288,7 +324,7 @@ export function DatePicker({ focusedDate={focusDate} month={month} year={year} - selected={deriveRange(selected)} + selected={monthIsSelected} hoverDate={hoverDate} onChange={handleDateSelection} onHover={handleHover} @@ -296,6 +332,7 @@ export function DatePicker({ disableDatesAfter={disableDatesAfter} disableSpecificDates={disableSpecificDates} allowRange={allowRange} + allowMultiple={allowMultiple} weekStartsOn={weekStartsOn} accessibilityLabelPrefixes={accessibilityLabelPrefixes} /> @@ -307,20 +344,21 @@ export function DatePicker({ function noop() {} -function handleKeyDown(event: React.KeyboardEvent) { - const {key} = event; - - if ( - key === 'ArrowUp' || - key === 'ArrowDown' || - key === 'ArrowLeft' || - key === 'ArrowRight' - ) { - event.preventDefault(); - event.stopPropagation(); +function deriveRange(selected?: Date | Range | Date[]) { + if (selected == null) { + return undefined; + } + + if (Array.isArray(selected)) { + return selected.map((date) => ({start: date, end: date})); + } + + if (selected instanceof Date) { + return { + start: selected, + end: selected, + }; } -} -function deriveRange(selected?: Date | Range) { - return selected instanceof Date ? {start: selected, end: selected} : selected; + return selected; } diff --git a/polaris-react/src/components/DatePicker/components/Month/Month.tsx b/polaris-react/src/components/DatePicker/components/Month/Month.tsx index 3c5c31b77e7..fafbaeab34c 100644 --- a/polaris-react/src/components/DatePicker/components/Month/Month.tsx +++ b/polaris-react/src/components/DatePicker/components/Month/Month.tsx @@ -21,7 +21,7 @@ import {monthName, weekdayName} from '../../utilities'; export interface MonthProps { focusedDate?: Date; - selected?: Range; + selected?: Range | Range[]; hoverDate?: Date; month: number; year: number; @@ -29,9 +29,10 @@ export interface MonthProps { disableDatesAfter?: Date; disableSpecificDates?: Date[]; allowRange?: boolean; + allowMultiple?: boolean; weekStartsOn: number; accessibilityLabelPrefixes: [string | undefined, string]; - onChange?(date: Range): void; + onChange?(date: Range | Date): void; onHover?(hoverEnd: Date): void; onFocus?(date: Date): void; } @@ -43,7 +44,8 @@ export function Month({ disableDatesBefore, disableDatesAfter, disableSpecificDates, - allowRange, + allowRange = false, + allowMultiple = false, onChange = noop, onHover = noop, onFocus = noop, @@ -74,9 +76,13 @@ export function Month({ const handleDateClick = useCallback( (selectedDate: Date) => { - onChange(getNewRange(allowRange ? selected : undefined, selectedDate)); + if (allowMultiple) { + onChange(selectedDate); + } else { + onChange(getNewRange(allowRange ? selected as Range : undefined, selectedDate)); + } }, - [allowRange, onChange, selected], + [allowRange, allowMultiple, onChange, selected], ); const lastDayOfMonth = useMemo( @@ -90,26 +96,35 @@ export function Month({ ); } + const disabled = (disableDatesBefore && isDateBefore(day, disableDatesBefore)) || (disableDatesAfter && isDateAfter(day, disableDatesAfter)) || (disableSpecificDates && isDateDisabled(day, disableSpecificDates)); - const isFirstSelectedDay = - allowRange && selected && isDateStart(day, selected); - const isLastSelectedDay = - allowRange && - selected && - ((!isSameDay(selected.start, selected.end) && isDateEnd(day, selected)) || - (hoverDate && - isSameDay(selected.start, selected.end) && - isDateAfter(hoverDate, selected.start) && - isSameDay(day, hoverDate) && - !isFirstSelectedDay)); - const rangeIsDifferent = !( - selected && isSameDay(selected.start, selected.end) - ); - const isHoveringRight = hoverDate && isDateBefore(day, hoverDate); + let isFirstSelectedDay = false; + let isLastSelectedDay = false; + let isSelected = false; + let isInRange = false; + + if (allowMultiple && Array.isArray(selected)) { + isSelected = selected.some((range) => dateIsSelected(day, range)); + } else if (selected) { + const singleSelected = selected as Range; + isFirstSelectedDay = Boolean(allowRange && isDateStart(day, singleSelected)); + isLastSelectedDay = Boolean( + allowRange && + ((!isSameDay(singleSelected.start, singleSelected.end) && isDateEnd(day, singleSelected)) || + (hoverDate && + isSameDay(singleSelected.start, singleSelected.end) && + isDateAfter(hoverDate, singleSelected.start) && + isSameDay(day, hoverDate) && + !isFirstSelectedDay)) + ); + isSelected = dateIsSelected(day, singleSelected); + isInRange = dateIsInRange(day, singleSelected); + } + const [firstAccessibilityLabelPrefix, lastAccessibilityLabelPrefix] = accessibilityLabelPrefixes; let accessibilityLabelPrefix; @@ -133,18 +148,23 @@ export function Month({ onFocus={onFocus} onClick={handleDateClick} onHover={onHover} - selected={selected != null && dateIsSelected(day, selected)} - inRange={selected != null && dateIsInRange(day, selected)} + selected={isSelected} + inRange={isInRange} disabled={disabled} inHoveringRange={ selected != null && hoverDate != null && - isInHoveringRange(day, selected, hoverDate) + !allowMultiple && + isInHoveringRange(day, selected as Range, hoverDate) } isLastSelectedDay={isLastSelectedDay} isFirstSelectedDay={isFirstSelectedDay} - isHoveringRight={isHoveringRight} - rangeIsDifferent={rangeIsDifferent} + isHoveringRight={hoverDate && isDateBefore(day, hoverDate)} + rangeIsDifferent={ + selected && !allowMultiple + ? !isSameDay((selected as Range).start, (selected as Range).end) + : false + } /> ); } diff --git a/polaris-react/src/components/Listbox/components/Option/Option.tsx b/polaris-react/src/components/Listbox/components/Option/Option.tsx index b0a9174e637..3ed907f73a0 100644 --- a/polaris-react/src/components/Listbox/components/Option/Option.tsx +++ b/polaris-react/src/components/Listbox/components/Option/Option.tsx @@ -7,6 +7,8 @@ import {TextOption} from '../TextOption'; import {UnstyledLink} from '../../../UnstyledLink'; import {MappedActionContext} from '../../../../utilities/autocomplete'; import {ActionContext} from '../../../../utilities/listbox/context'; +import {Box} from '../../../Box'; +import {InlineStack} from '../../../InlineStack'; import styles from './Option.module.css'; @@ -65,13 +67,21 @@ export const Option = memo(function Option({ event.preventDefault(); }; - const content = - typeof children === 'string' ? ( + const content = typeof children === 'string' ? ( {children} ) : ( - children + + + {children} + + ); const sectionAttributes = { From e99b951f165fb04776de52afdabbb78a426dddd3 Mon Sep 17 00:00:00 2001 From: michascant <89426143+MichaScant@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:30:48 -0500 Subject: [PATCH 2/7] Updated code to not affect ranges and single select in datePicker --- .../src/components/DatePicker/DatePicker.tsx | 61 +++++++++++++------ .../DatePicker/components/Month/Month.tsx | 13 +++- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/polaris-react/src/components/DatePicker/DatePicker.tsx b/polaris-react/src/components/DatePicker/DatePicker.tsx index a465cd2b31a..9a2a01f3988 100644 --- a/polaris-react/src/components/DatePicker/DatePicker.tsx +++ b/polaris-react/src/components/DatePicker/DatePicker.tsx @@ -50,7 +50,9 @@ export interface DatePickerProps { /** Visually hidden prefix text for selected days on single selection date pickers */ dayAccessibilityLabelPrefix?: string; /** Callback when date is selected. */ - onChange?(date: Range | Date[]): void; + onChange?(date: Range): void; + /** Callback when multiple dates are selected. Only called when allowMultiple is true. */ + onMultipleDatesChange?(dates: Date[]): void; /** Callback when month is changed. */ onMonthChange?(month: number, year: number): void; } @@ -70,10 +72,14 @@ export function DatePicker({ dayAccessibilityLabelPrefix, onMonthChange, onChange = noop, + onMultipleDatesChange, }: DatePickerProps) { const i18n = useI18n(); const [hoverDate, setHoverDate] = useState(undefined); const [focusDate, setFocusDate] = useState(undefined); + const [multipleSelectedDates, setMultipleSelectedDates] = useState( + Array.isArray(selected) ? selected as Date[] : [] + ); useEffect(() => { setFocusDate(undefined); @@ -97,30 +103,40 @@ export function DatePicker({ const handleDateSelection = useCallback( (range: Range | Date) => { if (allowMultiple) { - const newSelected = Array.isArray(selected) ? [...selected] : []; const dateToAdd = range instanceof Date ? range : range.start; - - const existingIndex = newSelected.findIndex((date) => - date.getTime() === dateToAdd.getTime() + const existingIndex = multipleSelectedDates.findIndex( + (date) => date.getTime() === dateToAdd.getTime() ); + let newDates: Date[]; if (existingIndex >= 0) { - newSelected.splice(existingIndex, 1); + newDates = [...multipleSelectedDates]; + newDates.splice(existingIndex, 1); } else { - newSelected.push(dateToAdd); + newDates = [...multipleSelectedDates, dateToAdd]; } - + + setMultipleSelectedDates(newDates); setHoverDate(dateToAdd); setFocusDate(dateToAdd); - onChange(newSelected); - } else { + + if (onMultipleDatesChange) { + onMultipleDatesChange(newDates); + } + } else if (allowRange) { const rangeValue = range instanceof Date ? {start: range, end: range} : range; setHoverDate(rangeValue.end); setFocusDate(new Date(rangeValue.end)); onChange(rangeValue); + } else { + // Single date selection - set start and end to the same date + const date = range instanceof Date ? range : range.start; + setHoverDate(date); + setFocusDate(date); + onChange({start: date, end: date}); } }, - [allowMultiple, onChange, selected], + [allowMultiple, allowRange, onChange, onMultipleDatesChange, multipleSelectedDates], ); const handleMonthChangeClick = useCallback( @@ -218,11 +234,16 @@ export function DatePicker({ ); const monthIsSelected = useMemo(() => { - if (allowMultiple && Array.isArray(selected)) { - return selected.map((date) => ({start: date, end: date})); + if (!allowMultiple) { + return deriveRange(selected as (Date | Range)); } - return deriveRange(selected); - }, [selected, allowMultiple]); + + // Convert Date[] to Range[] for multiple selection + return multipleSelectedDates.map(date => ({ + start: date, + end: date + })); + }, [selected, allowMultiple, multipleSelectedDates]); const firstDatePickerAccessibilityLabelPrefix = allowRange ? i18n.translate(`Polaris.DatePicker.start`) @@ -298,7 +319,7 @@ export function DatePicker({ } icon={ArrowLeftIcon} accessibilityLabel={i18n.translate('Polaris.DatePicker.previousMonth', { - previousMonth: previousMonthName, + previousMonthName: previousMonthName, showPreviousYear, })} /> @@ -349,10 +370,6 @@ function deriveRange(selected?: Date | Range | Date[]) { return undefined; } - if (Array.isArray(selected)) { - return selected.map((date) => ({start: date, end: date})); - } - if (selected instanceof Date) { return { start: selected, @@ -360,5 +377,9 @@ function deriveRange(selected?: Date | Range | Date[]) { }; } + if (Array.isArray(selected)) { + return undefined; + } + return selected; } diff --git a/polaris-react/src/components/DatePicker/components/Month/Month.tsx b/polaris-react/src/components/DatePicker/components/Month/Month.tsx index fafbaeab34c..c01d0e35814 100644 --- a/polaris-react/src/components/DatePicker/components/Month/Month.tsx +++ b/polaris-react/src/components/DatePicker/components/Month/Month.tsx @@ -21,7 +21,7 @@ import {monthName, weekdayName} from '../../utilities'; export interface MonthProps { focusedDate?: Date; - selected?: Range | Range[]; + selected?: Range | Range[] | Date[]; hoverDate?: Date; month: number; year: number; @@ -108,7 +108,16 @@ export function Month({ let isInRange = false; if (allowMultiple && Array.isArray(selected)) { - isSelected = selected.some((range) => dateIsSelected(day, range)); + if (selected.some(date => date instanceof Date)) { + // Handle Date[] case + isSelected = (selected as Date[]).some(date => isSameDay(day, date)); + } else { + // Handle Range[] case + isSelected = (selected as Range[]).some(range => dateIsSelected(day, range)); + } + isFirstSelectedDay = false; + isLastSelectedDay = false; + isInRange = false; } else if (selected) { const singleSelected = selected as Range; isFirstSelectedDay = Boolean(allowRange && isDateStart(day, singleSelected)); From 558f2c13622b3e152d570f6d393fd0b0cac8325f Mon Sep 17 00:00:00 2001 From: michascant <89426143+MichaScant@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:46:32 -0500 Subject: [PATCH 3/7] formated code --- .../src/components/DatePicker/DatePicker.tsx | 44 +++++++++++-------- .../DatePicker/components/Month/Month.tsx | 32 +++++++++----- .../Listbox/components/Option/Option.tsx | 23 +++++----- 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/polaris-react/src/components/DatePicker/DatePicker.tsx b/polaris-react/src/components/DatePicker/DatePicker.tsx index 9a2a01f3988..c49f3f5aed6 100644 --- a/polaris-react/src/components/DatePicker/DatePicker.tsx +++ b/polaris-react/src/components/DatePicker/DatePicker.tsx @@ -78,7 +78,7 @@ export function DatePicker({ const [hoverDate, setHoverDate] = useState(undefined); const [focusDate, setFocusDate] = useState(undefined); const [multipleSelectedDates, setMultipleSelectedDates] = useState( - Array.isArray(selected) ? selected as Date[] : [] + Array.isArray(selected) ? (selected as Date[]) : [], ); useEffect(() => { @@ -105,7 +105,7 @@ export function DatePicker({ if (allowMultiple) { const dateToAdd = range instanceof Date ? range : range.start; const existingIndex = multipleSelectedDates.findIndex( - (date) => date.getTime() === dateToAdd.getTime() + (date) => date.getTime() === dateToAdd.getTime(), ); let newDates: Date[]; @@ -115,7 +115,7 @@ export function DatePicker({ } else { newDates = [...multipleSelectedDates, dateToAdd]; } - + setMultipleSelectedDates(newDates); setHoverDate(dateToAdd); setFocusDate(dateToAdd); @@ -124,7 +124,8 @@ export function DatePicker({ onMultipleDatesChange(newDates); } } else if (allowRange) { - const rangeValue = range instanceof Date ? {start: range, end: range} : range; + const rangeValue = + range instanceof Date ? {start: range, end: range} : range; setHoverDate(rangeValue.end); setFocusDate(new Date(rangeValue.end)); onChange(rangeValue); @@ -136,7 +137,13 @@ export function DatePicker({ onChange({start: date, end: date}); } }, - [allowMultiple, allowRange, onChange, onMultipleDatesChange, multipleSelectedDates], + [ + allowMultiple, + allowRange, + onChange, + onMultipleDatesChange, + multipleSelectedDates, + ], ); const handleMonthChangeClick = useCallback( @@ -159,7 +166,8 @@ export function DatePicker({ const {key} = event; const range = deriveRange(selected); - const focusedDate = focusDate || (Array.isArray(range) ? range[0]?.start : range?.start); + const focusedDate = + focusDate || (Array.isArray(range) ? range[0]?.start : range?.start); if (focusedDate == null) { return; @@ -235,13 +243,13 @@ export function DatePicker({ const monthIsSelected = useMemo(() => { if (!allowMultiple) { - return deriveRange(selected as (Date | Range)); + return deriveRange(selected as Date | Range); } - + // Convert Date[] to Range[] for multiple selection - return multipleSelectedDates.map(date => ({ + return multipleSelectedDates.map((date) => ({ start: date, - end: date + end: date, })); }, [selected, allowMultiple, multipleSelectedDates]); @@ -318,19 +326,19 @@ export function DatePicker({ ) } icon={ArrowLeftIcon} - accessibilityLabel={i18n.translate('Polaris.DatePicker.previousMonth', { - previousMonthName: previousMonthName, - showPreviousYear, - })} + accessibilityLabel={i18n.translate( + 'Polaris.DatePicker.previousMonth', + { + previousMonthName: previousMonthName, + showPreviousYear, + }, + )} />