Skip to content

Instantly share code, notes, and snippets.

@maxgfr
Last active June 6, 2025 09:55
Show Gist options
  • Select an option

  • Save maxgfr/94b00cfc2a8bb4031f36e52b0923b56d to your computer and use it in GitHub Desktop.

Select an option

Save maxgfr/94b00cfc2a8bb4031f36e52b0923b56d to your computer and use it in GitHub Desktop.

Revisions

  1. maxgfr revised this gist Feb 8, 2025. 1 changed file with 46 additions and 9 deletions.
    55 changes: 46 additions & 9 deletions calendar.tsx
    Original file line number Diff line number Diff line change
    @@ -99,20 +99,56 @@ function Calendar({
    className,
    classNames,
    showOutsideDays = true,
    selected,
    defaultMonth,
    index = 0, // Ajout d'un index pour gérer plusieurs calendriers
    ...props
    }: CalendarProps) {
    const [currentMonth, setCurrentMonth] = React.useState<Date>(new Date());
    const [currentYear, setCurrentYear] = React.useState<number>(
    new Date().getFullYear(),
    }: CalendarProps & { index?: number }) {
    const [currentMonth, setCurrentMonth] = React.useState<Date>(() => {
    if (props.selected instanceof Date) {
    return props.selected;
    }
    if (Array.isArray(props.selected) && props.selected[0]) {
    const selectedDate = props.selected[0];
    if (selectedDate instanceof Date) {
    return new Date(
    selectedDate.getFullYear(),
    selectedDate.getMonth() + index,
    1,
    );
    }
    return new Date();
    }
    if (defaultMonth) {
    return new Date(
    defaultMonth.getFullYear(),
    defaultMonth.getMonth() + index,
    1,
    );
    }
    const now = new Date();
    return new Date(now.getFullYear(), now.getMonth() + index, 1);
    });

    const [currentYear, setCurrentYear] = React.useState<number>(() =>
    currentMonth.getFullYear(),
    );

    React.useEffect(() => {
    if (selected instanceof Date) {
    setCurrentMonth(selected);
    setCurrentYear(selected.getFullYear());
    if (props.selected instanceof Date) {
    const newDate = new Date(props.selected);
    newDate.setMonth(newDate.getMonth() + index);
    setCurrentMonth(newDate);
    setCurrentYear(newDate.getFullYear());
    } else if (
    Array.isArray(props.selected) &&
    props.selected[0] instanceof Date
    ) {
    const newDate = new Date(props.selected[0]);
    newDate.setMonth(newDate.getMonth() + index);
    setCurrentMonth(newDate);
    setCurrentYear(newDate.getFullYear());
    }
    }, [selected]);
    }, [props.selected, index]);

    const handleMonthChange = (newMonth: Date) => {
    setCurrentMonth(newMonth);
    @@ -133,6 +169,7 @@ function Calendar({
    className={cn("p-3", className)}
    locale={fr}
    month={currentMonth}
    defaultMonth={defaultMonth}
    onMonthChange={handleMonthChange}
    classNames={{
    months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
  2. maxgfr revised this gist Feb 8, 2025. 1 changed file with 59 additions and 18 deletions.
    77 changes: 59 additions & 18 deletions calendar.tsx
    Original file line number Diff line number Diff line change
    @@ -20,21 +20,27 @@ export type CalendarProps = React.ComponentProps<typeof DayPicker>;

    function CalendarDropdown({
    displayMonth,
    onChange,
    displayYear,
    onChangeMonth,
    onChangeYear,
    }: {
    displayMonth: Date;
    onChange: (month: Date) => void;
    displayYear: number;
    onChangeMonth: (date: Date) => void;
    onChangeYear: (year: number) => void;
    }) {
    const months = Array.from({ length: 12 }, (_, i) => {
    const month = new Date(displayMonth.getFullYear(), i, 1);
    const month = new Date(displayYear, i, 1);
    return {
    value: i.toString(),
    label: format(month, "MMMM", { locale: fr }),
    };
    });

    const currentYear = displayMonth.getFullYear();
    const years = Array.from({ length: 20 }, (_, i) => currentYear - 5 + i);
    const years = Array.from(
    { length: 20 },
    (_, i) => new Date().getFullYear() - 5 + i,
    ).sort((a, b) => a - b);

    return (
    <div className="flex items-center justify-center gap-1 pt-1">
    @@ -43,7 +49,7 @@ function CalendarDropdown({
    onValueChange={(value) => {
    const newDate = new Date(displayMonth);
    newDate.setMonth(parseInt(value));
    onChange(newDate);
    onChangeMonth(newDate);
    }}
    >
    <SelectTrigger className="h-7 w-[110px]">
    @@ -53,27 +59,33 @@ function CalendarDropdown({
    </SelectTrigger>
    <SelectContent>
    {months.map((month) => (
    <SelectItem key={month.value} value={month.value}>
    <SelectItem
    key={month.value}
    value={month.value}
    className="cursor-pointer"
    >
    {month.label}
    </SelectItem>
    ))}
    </SelectContent>
    </Select>

    <Select
    value={displayMonth.getFullYear().toString()}
    value={displayYear.toString()}
    onValueChange={(value) => {
    const newDate = new Date(displayMonth);
    newDate.setFullYear(parseInt(value));
    onChange(newDate);
    onChangeYear(parseInt(value));
    }}
    >
    <SelectTrigger className="h-7 w-[80px]">
    <SelectValue>{displayMonth.getFullYear()}</SelectValue>
    <SelectValue>{displayYear}</SelectValue>
    </SelectTrigger>
    <SelectContent>
    <SelectContent className="max-h-[300px]">
    {years.map((year) => (
    <SelectItem key={year} value={year.toString()}>
    <SelectItem
    key={year}
    value={year.toString()}
    className="cursor-pointer"
    >
    {year}
    </SelectItem>
    ))}
    @@ -87,17 +99,41 @@ function Calendar({
    className,
    classNames,
    showOutsideDays = true,
    selected,
    ...props
    }: CalendarProps) {
    const [month, setMonth] = React.useState<Date>(new Date());
    const [currentMonth, setCurrentMonth] = React.useState<Date>(new Date());
    const [currentYear, setCurrentYear] = React.useState<number>(
    new Date().getFullYear(),
    );

    React.useEffect(() => {
    if (selected instanceof Date) {
    setCurrentMonth(selected);
    setCurrentYear(selected.getFullYear());
    }
    }, [selected]);

    const handleMonthChange = (newMonth: Date) => {
    setCurrentMonth(newMonth);
    props.onMonthChange?.(newMonth);
    };

    const handleYearChange = (year: number) => {
    const newDate = new Date(currentMonth);
    newDate.setFullYear(year);
    setCurrentYear(year);
    setCurrentMonth(newDate);
    props.onMonthChange?.(newDate);
    };

    return (
    <DayPicker
    showOutsideDays={showOutsideDays}
    className={cn("p-3", className)}
    locale={fr}
    month={month}
    onMonthChange={setMonth}
    month={currentMonth}
    onMonthChange={handleMonthChange}
    classNames={{
    months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
    month: "space-y-4",
    @@ -140,7 +176,12 @@ function Calendar({
    <ChevronRight className={cn("h-4 w-4", className)} {...props} />
    ),
    Caption: () => (
    <CalendarDropdown displayMonth={month} onChange={setMonth} />
    <CalendarDropdown
    displayMonth={currentMonth}
    displayYear={currentYear}
    onChangeMonth={handleMonthChange}
    onChangeYear={handleYearChange}
    />
    ),
    }}
    {...props}
  3. maxgfr created this gist Feb 8, 2025.
    152 changes: 152 additions & 0 deletions calendar.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    "use client";

    import * as React from "react";
    import { ChevronLeft, ChevronRight } from "lucide-react";
    import { DayPicker } from "react-day-picker";
    import { format } from "date-fns";

    import { cn } from "~/lib/utils";
    import { buttonVariants } from "~/components/ui/button";
    import { fr } from "date-fns/locale";
    import {
    Select,
    SelectContent,
    SelectItem,
    SelectTrigger,
    SelectValue,
    } from "~/components/ui/select";

    export type CalendarProps = React.ComponentProps<typeof DayPicker>;

    function CalendarDropdown({
    displayMonth,
    onChange,
    }: {
    displayMonth: Date;
    onChange: (month: Date) => void;
    }) {
    const months = Array.from({ length: 12 }, (_, i) => {
    const month = new Date(displayMonth.getFullYear(), i, 1);
    return {
    value: i.toString(),
    label: format(month, "MMMM", { locale: fr }),
    };
    });

    const currentYear = displayMonth.getFullYear();
    const years = Array.from({ length: 20 }, (_, i) => currentYear - 5 + i);

    return (
    <div className="flex items-center justify-center gap-1 pt-1">
    <Select
    value={displayMonth.getMonth().toString()}
    onValueChange={(value) => {
    const newDate = new Date(displayMonth);
    newDate.setMonth(parseInt(value));
    onChange(newDate);
    }}
    >
    <SelectTrigger className="h-7 w-[110px]">
    <SelectValue>
    {format(displayMonth, "MMMM", { locale: fr })}
    </SelectValue>
    </SelectTrigger>
    <SelectContent>
    {months.map((month) => (
    <SelectItem key={month.value} value={month.value}>
    {month.label}
    </SelectItem>
    ))}
    </SelectContent>
    </Select>

    <Select
    value={displayMonth.getFullYear().toString()}
    onValueChange={(value) => {
    const newDate = new Date(displayMonth);
    newDate.setFullYear(parseInt(value));
    onChange(newDate);
    }}
    >
    <SelectTrigger className="h-7 w-[80px]">
    <SelectValue>{displayMonth.getFullYear()}</SelectValue>
    </SelectTrigger>
    <SelectContent>
    {years.map((year) => (
    <SelectItem key={year} value={year.toString()}>
    {year}
    </SelectItem>
    ))}
    </SelectContent>
    </Select>
    </div>
    );
    }

    function Calendar({
    className,
    classNames,
    showOutsideDays = true,
    ...props
    }: CalendarProps) {
    const [month, setMonth] = React.useState<Date>(new Date());

    return (
    <DayPicker
    showOutsideDays={showOutsideDays}
    className={cn("p-3", className)}
    locale={fr}
    month={month}
    onMonthChange={setMonth}
    classNames={{
    months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
    month: "space-y-4",
    caption: "flex justify-center pt-1 relative items-center",
    caption_label: "text-sm font-medium",
    nav: "space-x-1 flex items-center",
    nav_button: cn(
    buttonVariants({ variant: "outline" }),
    "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
    ),
    nav_button_previous: "absolute left-1",
    nav_button_next: "absolute right-1",
    table: "w-full border-collapse space-y-1",
    head_row: "flex",
    head_cell:
    "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
    row: "flex w-full mt-2",
    cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
    day: cn(
    buttonVariants({ variant: "ghost" }),
    "h-9 w-9 p-0 font-normal aria-selected:opacity-100",
    ),
    day_range_end: "day-range-end",
    day_selected:
    "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
    day_today: "bg-accent text-accent-foreground",
    day_outside:
    "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
    day_disabled: "text-muted-foreground opacity-50",
    day_range_middle:
    "aria-selected:bg-accent aria-selected:text-accent-foreground",
    day_hidden: "invisible",
    ...classNames,
    }}
    components={{
    IconLeft: ({ className, ...props }) => (
    <ChevronLeft className={cn("h-4 w-4", className)} {...props} />
    ),
    IconRight: ({ className, ...props }) => (
    <ChevronRight className={cn("h-4 w-4", className)} {...props} />
    ),
    Caption: () => (
    <CalendarDropdown displayMonth={month} onChange={setMonth} />
    ),
    }}
    {...props}
    />
    );
    }
    Calendar.displayName = "Calendar";

    export { Calendar };