Skip to content

Commit

Permalink
feat(RangeCalendar): allow non contiguous ranges (#1411)
Browse files Browse the repository at this point in the history
* feat(RangeCalendar): implement non contiguous ranges

* docs(RangeCalendar): update prop docs

* chore: update jsdoc

* chore(RangeCalendar): update test for non contiguous range

---------

Co-authored-by: zernonia <zernonia@gmail.com>
  • Loading branch information
epr3 and zernonia authored Nov 4, 2024
1 parent deaee66 commit 920d402
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 5 deletions.
7 changes: 7 additions & 0 deletions docs/content/meta/DateRangePickerRoot.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<!-- This file was automatic generated. Do not edit it manually -->

<PropsTable :data="[
{
'name': 'allowNonContiguousRanges',
'description': '<p>When combined with <code>isDateUnavailable</code>, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.</p>\n',
'type': 'boolean',
'required': false,
'default': 'false'
},
{
'name': 'as',
'description': '<p>The element or component this component should render as. Can be overwrite by <code>asChild</code></p>\n',
Expand Down
7 changes: 7 additions & 0 deletions docs/content/meta/RangeCalendarRoot.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<!-- This file was automatic generated. Do not edit it manually -->

<PropsTable :data="[
{
'name': 'allowNonContiguousRanges',
'description': '<p>When combined with <code>isDateUnavailable</code>, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.</p>\n',
'type': 'boolean',
'required': false,
'default': 'false'
},
{
'name': 'as',
'description': '<p>The element or component this component should render as. Can be overwrite by <code>asChild</code></p>\n',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const rootContext = injectDateRangePickerRootContext()
<RangeCalendarRoot
v-slot="{ weekDays, grid, date, weekStartsOn, locale, fixedWeeks }"
v-bind="{
allowNonContiguousRanges: rootContext.allowNonContiguousRanges.value,
isDateDisabled: rootContext.isDateDisabled,
isDateUnavailable: rootContext.isDateUnavailable,
locale: rootContext.locale.value,
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/DateRangePicker/DateRangePickerRoot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ type DateRangePickerRootContext = {
onPlaceholderChange: (date: DateValue) => void
onStartValueChange: (date: DateValue | undefined) => void
dir: Ref<Direction>
allowNonContiguousRanges: Ref<boolean>
}
export type DateRangePickerRootProps = DateRangeFieldRootProps & PopoverRootProps & Pick<RangeCalendarRootProps, 'isDateDisabled' | 'pagedNavigation' | 'weekStartsOn' | 'weekdayFormat' | 'fixedWeeks' | 'numberOfMonths' | 'preventDeselect'>
export type DateRangePickerRootProps = DateRangeFieldRootProps & PopoverRootProps & Pick<RangeCalendarRootProps, 'isDateDisabled' | 'pagedNavigation' | 'weekStartsOn' | 'weekdayFormat' | 'fixedWeeks' | 'numberOfMonths' | 'preventDeselect' | 'isDateUnavailable' | 'allowNonContiguousRanges'>
export type DateRangePickerRootEmits = {
/** Event handler called whenever the model value changes */
Expand Down Expand Up @@ -81,6 +82,7 @@ const props = withDefaults(defineProps<DateRangePickerRootProps>(), {
locale: 'en',
isDateDisabled: undefined,
isDateUnavailable: undefined,
allowNonContiguousRanges: false,
})
const emits = defineEmits<DateRangePickerRootEmits & PopoverRootEmits>()
const {
Expand All @@ -106,6 +108,7 @@ const {
hideTimeZone,
hourCycle,
dir: propsDir,
allowNonContiguousRanges,
} = toRefs(props)
const dir = useDirection(propsDir)
Expand Down Expand Up @@ -134,6 +137,7 @@ const open = useVModel(props, 'open', emits, {
const dateFieldRef = ref<InstanceType<typeof DateRangeFieldRoot> | undefined>()
provideDateRangePickerRootContext({
allowNonContiguousRanges,
isDateUnavailable: propsIsDateUnavailable.value,
isDateDisabled: propsIsDateDisabled.value,
locale,
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/RangeCalendar/RangeCalendar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,25 @@ describe('rangeCalendar', () => {
expect(thirdDayInMonth).not.toHaveAttribute('data-selected')
})

it('allows non-contiguous ranges', async () => {
const { getByTestId, user } = setup({
calendarProps: {
placeholder: calendarDateRange.start,
allowNonContiguousRanges: true,
isDateUnavailable: (date) => {
return date.day === 3
},
},
})

const firstDayInMonth = getByTestId('date-1-1')
const fourthDayInMonth = getByTestId('date-1-4')
const fifthDayInMonth = getByTestId('date-1-5')
await user.click(firstDayInMonth)
await user.click(fifthDayInMonth)
expect(fourthDayInMonth).toHaveAttribute('data-selected')
})

it('formats the weekday labels correctly - `\'narrow\'`', async () => {
const { getByTestId } = setup({
calendarProps: {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/RangeCalendar/RangeCalendarCellTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,14 @@ function handleArrowKey(e: KeyboardEvent) {
role="button"
:aria-label="labelText"
data-reka-calendar-cell-trigger
:aria-selected="isSelectedDate ? true : undefined"
:aria-selected="isSelectedDate && !isUnavailable ? true : undefined"
:aria-disabled="isOutsideView || isDisabled || isUnavailable ? true : undefined"
:data-highlighted="isHighlighted ? '' : undefined"
:data-highlighted="isHighlighted && !isUnavailable ? '' : undefined"
:data-selection-start="isSelectionStart ? true : undefined"
:data-selection-end="isSelectionEnd ? true : undefined"
:data-highlighted-start="isHighlightStart ? true : undefined"
:data-highlighted-end="isHighlightEnd ? true : undefined"
:data-selected="isSelectedDate ? true : undefined"
:data-selected="isSelectedDate && !isUnavailable ? true : undefined"
:data-outside-visible-view="isOutsideVisibleView ? '' : undefined"
:data-value="day.toString()"
:data-disabled="isDisabled || isOutsideView ? '' : undefined"
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/RangeCalendar/RangeCalendarRoot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export interface RangeCalendarRootProps extends PrimitiveProps {
modelValue?: DateRange
/** The placeholder date, which is used to determine what month to display when no date is selected. This updates as the user navigates the calendar and can be used to programmatically control the calendar view */
placeholder?: DateValue
/** When combined with `isDateUnavailable`, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected. */
allowNonContiguousRanges?: boolean
/** This property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month */
pagedNavigation?: boolean
/** Whether or not to prevent the user from deselecting a date without selecting another date first */
Expand Down Expand Up @@ -130,6 +132,7 @@ const props = withDefaults(defineProps<RangeCalendarRootProps>(), {
placeholder: undefined,
isDateDisabled: undefined,
isDateUnavailable: undefined,
allowNonContiguousRanges: false,
})
const emits = defineEmits<RangeCalendarRootEmits>()
Expand Down Expand Up @@ -169,6 +172,7 @@ const {
locale: propLocale,
nextPage: propsNextPage,
prevPage: propsPrevPage,
allowNonContiguousRanges,
} = toRefs(props)
const { primitiveElement, currentElement: parentElement }
Expand Down Expand Up @@ -246,6 +250,7 @@ const {
isDateDisabled,
isDateUnavailable,
focusedValue,
allowNonContiguousRanges,
})
watch(modelValue, (_modelValue) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/RangeCalendar/useRangeCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type UseRangeCalendarProps = {
isDateDisabled: Matcher
isDateUnavailable: Matcher
focusedValue: Ref<DateValue | undefined>
allowNonContiguousRanges: Ref<boolean>
}

export function useRangeCalendarState(props: UseRangeCalendarProps) {
Expand Down Expand Up @@ -81,7 +82,7 @@ export function useRangeCalendarState(props: UseRangeCalendarProps) {
}
}

const isValid = areAllDaysBetweenValid(start, end, props.isDateUnavailable, props.isDateDisabled)
const isValid = props.allowNonContiguousRanges.value || areAllDaysBetweenValid(start, end, props.isDateUnavailable, props.isDateDisabled)
if (isValid) {
return {
start,
Expand Down

0 comments on commit 920d402

Please # to comment.