Skip to content

Commit

Permalink
Merge pull request #140 from mystica2000/feat/disable-month-year-sele…
Browse files Browse the repository at this point in the history
…ction

feat: add a new methods isYearDisabled, isMonthDisabled for disabling unavailable months, year
  • Loading branch information
farhoudshapouran authored Feb 20, 2025
2 parents f3b5d1b + 282d132 commit 398ae6a
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 31 deletions.
2 changes: 2 additions & 0 deletions demo/components/examples/single-datepicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export default function SingleDatePicker() {
date={date}
onChange={({ date }) => setDate(date)}
timePicker
// minDate={new Date()}
// maxDate={new Date(new Date().getFullYear(), 11, 31)} // end of the year
/>
<DateInput
value={date ? dayjs(date).format('MMMM DD, YYYY HH:mm') : null}
Expand Down
19 changes: 16 additions & 3 deletions src/components/months.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { View, StyleSheet, Pressable, Text } from 'react-native';
import { useCalendarContext } from '../calendar-context';
import { getParsedDate, getMonthsArray, cn } from '../utils';
import { getParsedDate, getMonthsArray, cn, isMonthDisabled } from '../utils';
import { CONTAINER_HEIGHT } from '../enums';

const Months = () => {
Expand All @@ -13,6 +13,8 @@ const Months = () => {
components = {},
containerHeight = CONTAINER_HEIGHT,
monthsFormat = 'full',
minDate,
maxDate
} = useCalendarContext();

const style = useMemo(
Expand All @@ -33,31 +35,41 @@ const Months = () => {
{getMonthsArray()?.map((item, index) => {
const isSelected = index === month;

const isDisabled = isMonthDisabled(index, currentDate, {
minDate,
maxDate,
});

const itemStyle = StyleSheet.flatten([
defaultStyles.month,
styles.month,
isSelected && styles.selected_month,
isDisabled && styles.disabled
]);

const textStyle = StyleSheet.flatten([
styles.month_label,
isSelected && styles.selected_month_label,
isDisabled && styles.disabled_label
]);

const containerClassName = cn(
classNames.month,
isSelected && classNames.selected_month
isSelected && classNames.selected_month,
isDisabled && classNames.disabled
);

const textClassName = cn(
classNames.month_label,
isSelected && classNames.selected_month_label
isSelected && classNames.selected_month_label,
isDisabled && classNames.disabled_label
);

return (
<View key={index} style={style.monthCell}>
{components.Month ? (
<Pressable
disabled={isDisabled}
onPress={() => onSelectMonth(index)}
accessibilityRole="button"
accessibilityLabel={item.name.full}
Expand All @@ -67,6 +79,7 @@ const Months = () => {
</Pressable>
) : (
<Pressable
disabled={isDisabled}
onPress={() => onSelectMonth(index)}
accessibilityRole="button"
accessibilityLabel={item.name.full}
Expand Down
16 changes: 13 additions & 3 deletions src/components/years.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react';
import { View, StyleSheet, Pressable, Text } from 'react-native';
import { useCalendarContext } from '../calendar-context';
import { cn, formatNumber, getDateYear, getYearRange } from '../utils';
import { cn, formatNumber, getDateYear, getYearRange, isYearDisabled } from '../utils';
import { CONTAINER_HEIGHT } from '../enums';

const Years = () => {
Expand All @@ -16,6 +16,8 @@ const Years = () => {
classNames = {},
components = {},
containerHeight = CONTAINER_HEIGHT,
minDate,
maxDate
} = useCalendarContext();

const style = useMemo(
Expand All @@ -32,35 +34,42 @@ const Years = () => {
const isSelected = year === selectedYear;
const isActivated = year === activeYear;

const isDisabled = isYearDisabled(year, { minDate, maxDate });

const containerStyle = StyleSheet.flatten([
defaultStyles.year,
styles.year,
isActivated && styles.active_year,
isSelected && styles.selected_year,
isDisabled && styles.disabled
]);

const textStyle = StyleSheet.flatten([
styles.year_label,
isActivated && styles.active_year_label,
isSelected && styles.selected_year_label,
isDisabled && styles.disabled_label
]);

const containerClassName = cn(
classNames.year,
isActivated && classNames.active_year,
isSelected && classNames.selected_year
isSelected && classNames.selected_year,
isDisabled && classNames.disabled
);

const textClassName = cn(
classNames.year_label,
isActivated && classNames.active_year_label,
isSelected && classNames.selected_year_label
isSelected && classNames.selected_year_label,
isDisabled && classNames.disabled_label
);

return (
<View key={year} style={style.yearCell}>
{components.Year ? (
<Pressable
disabled={isDisabled}
onPress={() => onSelectYear(year)}
accessibilityRole="button"
accessibilityLabel={year.toString()}
Expand All @@ -75,6 +84,7 @@ const Years = () => {
</Pressable>
) : (
<Pressable
disabled={isDisabled}
onPress={() => onSelectYear(year)}
accessibilityRole="button"
accessibilityLabel={year.toString()}
Expand Down
12 changes: 7 additions & 5 deletions src/theme.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useColorScheme } from 'react-native';
import { ClassNames, Styles } from './types';
import { UI, SelectionState, DayFlag, MonthState, YearState } from './ui';
import { UI, SelectionState, DayFlag, MonthState, YearState, CalenderFlag } from './ui';

export function getDefaultClassNames(): ClassNames {
const classNames: ClassNames = {
Expand Down Expand Up @@ -44,8 +44,9 @@ export function getDefaultClassNames(): ClassNames {
'group bg-primary web:hover:bg-primary web:hover:opacity-90 active:opacity-90',
[SelectionState.selected_label]: 'text-primary-foreground',

[DayFlag.disabled]: '',
[DayFlag.disabled_label]: 'text-muted-foreground opacity-50',
[CalenderFlag.disabled]: '',
[CalenderFlag.disabled_label]: 'text-muted-foreground opacity-50',

[DayFlag.hidden]: '',
[DayFlag.outside]: '',
[DayFlag.outside_label]: 'text-muted-foreground',
Expand Down Expand Up @@ -149,11 +150,12 @@ export function getDefaultStyles(): Styles {
color: COLORS[theme].primaryForeground,
},

[DayFlag.disabled]: {},
[DayFlag.disabled_label]: {
[CalenderFlag.disabled]: {},
[CalenderFlag.disabled_label]: {
color: COLORS[theme].mutedForeground,
opacity: 0.5,
},

[DayFlag.hidden]: {},
[DayFlag.outside]: {},
[DayFlag.outside_label]: { color: COLORS[theme].mutedForeground },
Expand Down
6 changes: 3 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Dayjs } from 'dayjs';
import type { CalendarActionKind, CalendarViews } from './enums';
import type { TextStyle, ViewStyle } from 'react-native';
import { UI, SelectionState, DayFlag, MonthState, YearState } from './ui';
import { UI, SelectionState, DayFlag, MonthState, YearState, CalenderFlag } from './ui';

export type DateType = string | number | Dayjs | Date | null | undefined;

Expand Down Expand Up @@ -87,11 +87,11 @@ export type MultiChange = (params: {
}) => void;

export type ClassNames = Partial<{
[key in UI | SelectionState | DayFlag | MonthState | YearState]: string;
[key in UI | SelectionState | DayFlag | MonthState | YearState | CalenderFlag]: string;
}>;

export type Styles = Partial<{
[key in UI | SelectionState | DayFlag | MonthState | YearState]:
[key in UI | SelectionState | DayFlag | MonthState | YearState | CalenderFlag]:
| ViewStyle
| TextStyle;
}>;
Expand Down
9 changes: 6 additions & 3 deletions src/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,14 @@ export enum SelectionState {
selected_label = 'selected_label',
}

export enum DayFlag {
/** The day is disabled. */
export enum CalenderFlag {
/** The day/month/year is disabled. */
disabled = 'disabled',
/** The label of the disabled day. */
/** The label of the disabled day/month/year. */
disabled_label = 'disabled_label',
}

export enum DayFlag {
/** The day is hidden. */
hidden = 'hidden',
/** The day is outside the current month. */
Expand Down
78 changes: 64 additions & 14 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,56 @@ export function isDateDisabled(
return false;
}

/**
* Check if year is disabled
*
* @param year - year to check
* @param options - options
*
* @returns true if year is disabled, false otherwise
*/
export function isYearDisabled(
year: number,
{
minDate,
maxDate,
}: {
minDate?: DateType;
maxDate?: DateType;
}
): boolean {
if (minDate && year < getDateYear(minDate)) return true;
if (maxDate && year > getDateYear(maxDate)) return true;

return false;
}

/**
* Check if month is disabled
*
* @param month - month to check
* @param date - date to check
* @param options - options
*
* @returns true if month is disabled, false otherwise
*/
export function isMonthDisabled(
month: number,
date: DateType,
{
minDate,
maxDate,
}: {
minDate?: DateType;
maxDate?: DateType;
}
): boolean {
if (minDate && month < getDateMonth(minDate) && getDateYear(date) === getDateYear(minDate)) return true;
if (maxDate && month > getDateMonth(maxDate) && getDateYear(date) === getDateYear(maxDate)) return true;

return false;
}

/**
* Get formated date
*
Expand Down Expand Up @@ -356,20 +406,20 @@ export const getMonthDays = (

const prevDays = showOutsideDays
? Array.from({ length: prevMonthOffset }, (_, index) => {
const number = index + (prevMonthDays - prevMonthOffset + 1);
const thisDay = date.add(-1, 'month').date(number);
return generateCalendarDay(
number,
thisDay,
minDate,
maxDate,
disabledDates,
false,
index + 1,
firstDayOfWeek,
numerals
);
})
const number = index + (prevMonthDays - prevMonthOffset + 1);
const thisDay = date.add(-1, 'month').date(number);
return generateCalendarDay(
number,
thisDay,
minDate,
maxDate,
disabledDates,
false,
index + 1,
firstDayOfWeek,
numerals
);
})
: Array(prevMonthOffset).fill(null);

const currentDays = Array.from({ length: daysInCurrentMonth }, (_, index) => {
Expand Down

0 comments on commit 398ae6a

Please sign in to comment.