Giter Club home page Giter Club logo

datepicker's Introduction

@rehookify/datepicker

@rehookify/datepicker is an ultimate tool for creating date, range, and time pickers in your React applications.

It provides behavioral and accessibility implementations for all sorts of calendars, date and time pickers.

size npm discord

Tutorials | Examples | Configuration | State | Modular Hooks

#StandWithUkraine πŸ’™πŸ’›

We have war at our home πŸ‡ΊπŸ‡¦

Help us in our struggle, πŸ’° United24, KOLO, Come Back Alive

Features

  • Small in size.
  • No dependencies.
  • Modular design: You can use as little code as possible.
  • Supports single, multiple, and range date selection, as well as a time picker.
  • Multiple calendar support.
  • Native localization support using .toLocaleDateString and .toLocaleTimeString.
  • Follows the prop-getters pattern, providing all the necessary props for your components.

Motivation

By picking a date picker for you project you can get such problems:

  • πŸ™… your component library doesn't have the component that you need;
  • βš™οΈ you need to make changes to your build process;
  • πŸ’… your styling system is different from the most popular solution;
  • 🦹 it's challenging to customise a component that matches your design;
  • πŸ’° it could cost money to get needed components;
  • πŸ‹οΈ the library is heavy and doesn't have tree-shaking;
  • πŸ“š bad documentation;
  • ⛔️ no tests, no TypeScript support, no examples...

Instalation

@rehookify/datepicker is available as a package in npm registry.

You can use your favarite package manager to insall it:

npm i @rehookify/datepicker
yarn add @rehookify/datepicker
pnpm add @rehookify/datepicker

Start with tutorials

datepicker's People

Contributors

clintmilner avatar dan-lee avatar feshchenko avatar susosamuel avatar tamazlykar avatar tdhuan avatar vadimchorrny avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

datepicker's Issues

Start of Years

Hello, and kudos to you for such a wonderful library πŸš€.

I'm building a year dropdown that allows users to select a year. However, it only goes back to 2019. I'd like it to go back further than that. I know I can specify a number of years in the years property in useDatePicker, but that only works for the future. Any way to set a start year or something similar? Maybe it's adjusting the offset – I'm not sure how it works.

Thank you!

WeekPicker

Hello,
First, I really appreciate your work. And I wonder if you plan to implement WeekPicker?
Thanks

useTime Performance

Problem
Using the useTime hook, there is significant performance slowdown with small intervals.
When selecting a time element, the state change is noticably slow (~500-1000ms).

Steps to Reproduce
Included below is the code for a simple time picker:

const TimePickerIsolated = () => {
  const [selectedDates, onDatesChange] = useState<Date[]>([new Date()]);

  const state = useDatePickerState({
    selectedDates,
    onDatesChange,
    focusDate: new Date(),
    time: {
      interval: 1
    }
  })

  const { time } = useTime(state)
  const { timeButton } = useTimePropGetter(state);

  // If hours/minutes are one digit (e.g. 6 for 6am), then pad with a zero (e.g. 06 for 6am)
  const selectedHours = selectedDates[0]?.getHours().toString().padStart(2, '0')
  const selectedMinutes = selectedDates[0]?.getMinutes().toString().padStart(2, '0')
  const selectedTime = `${selectedHours}:${selectedMinutes}`

  return (
    <ul>
      {time.map((t) => (
        <li key={t.$date.toString()}>
          <button className={`${selectedTime === t.time ? 'bg-green-300' : ''} disabled:text-gray-300`} {...timeButton(t)}>{t.time}</button>
        </li>
      ))}
    </ul>
  )
}

Try using this with an interval of 30 and observe the speed, then set the interval to 1 and observe the speed.

I am using tailwind to change the background colour to visually show this, but this can be replaced with vanialla css if needed.

Select same date in range date picker

Hi!

First of all, love this library, it has a wonderful and simplistic API!
I was looking for a way to handle the state where a range picker's start/end date is the same. I think this was an edge case that the range mode does not contemplate yet.

For example, MUI's DateRangePicker has this option:
image

It would be great to have a way to enable this feature in the range date pickers!

Contributing

Hello!

I hope you all are well. Our company is looking to adopt this library. Though everything appears to he maintained here, if we run into anything we feel is missing, what would your process look like in terms of process and timelines to merge a PR into main.

Thanks! And looking forward to diving into your library!

Jake

Accessibility

Hi there. Thanks a lot for this great and fresh approach!

Correct me when I'm wrong but it seems this library does not aim to be a drop-in replacement for <input type="date" /> but instead be a building block that one could use when building a custom datepicker while also being usable for when a datepicker might be rendered inline etc.

Still I think both approaches should be accessible to screen-readers right?

Also currently I would need to implement keyboard support on my own (which is not that big of a deal thanks to the actions) but I noticed that if I were to add the three month range example inline to a page, as a keyboard user I would have to hit [Tab] really often until I eventually land on the next focusable element after the datepicker.

If that's all stuff you think should be done outside of this library that would also be fine for me, just wondering what's your take on this.

Let me know what you think

Error resetting selected dates in "range" mode with a single date selected

If the selectedDates state is reset to an empty array when it's in mode: "range" and one of the dates is selected, the following error is thrown:

index.esm.mjs?93b8:7 Uncaught TypeError: Cannot read properties of undefined (reading 'getDate')
    at getDateParts (index.esm.mjs?93b8:7:1)
    at getCleanDate (index.esm.mjs?93b8:17:1)
    at getDateRangeState (index.esm.mjs?93b8:189:1)
    at createCalendar (index.esm.mjs?93b8:216:1)
    at eval (index.esm.mjs?93b8:228:90)
    at Array.map (<anonymous>)
    at createCalendars (index.esm.mjs?93b8:228:90)
    at useCalendars (index.esm.mjs?93b8:233:1)
    at useDatePicker (index.esm.mjs?93b8:575:1)
    at DatePicker (DatePicker.tsx?b640:31:20)
    at renderWithHooks (react-dom.development.js?3c4a:16305:1)
    at updateFunctionComponent (react-dom.development.js?3c4a:19588:1)
    at beginWork (react-dom.development.js?3c4a:21601:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js?3c4a:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js?3c4a:4213:1)
    at invokeGuardedCallback (react-dom.development.js?3c4a:4277:1)
    at beginWork$1 (react-dom.development.js?3c4a:27451:1)
    at performUnitOfWork (react-dom.development.js?3c4a:26557:1)
    at workLoopSync (react-dom.development.js?3c4a:26466:1)
    at renderRootSync (react-dom.development.js?3c4a:26434:1)
    at performSyncWorkOnRoot (react-dom.development.js?3c4a:26085:1)
    at flushSyncCallbacks (react-dom.development.js?3c4a:12042:1)
    at eval (react-dom.development.js?3c4a:25651:1)

Keep the time when selecting a different day

Currently, when selecting a different day in the 'single' mode the time is always reset to 00:00. It would be great to have a way to keep the time, e.g. when building a DateTimePicker.

To reproduce:
See this example and select a day and time other than 00:00. When you now select a different day the time will reset to 00:00.

Limiting the choice of months by minDate

I have bumped into a bug related to minDate and maxDate.
If set limits on selectable years then the choice of calendar months is limited by minDate.

My config for useDatePicker:

useDatePicker({
  selectedDates,
  onDatesChange,
  dates: { minDate: new Date(2024, 2, 12), maxDate: new Date(2026, 2, 14) },
  calendar: {
    startDay: 1,
  },
});

My view:
Π‘Π½ΠΈΠΌΠΎΠΊ экрана 2024-03-13 Π² 16 43 29-min

You can check this bug here:
https://codesandbox.io/p/devbox/rehookify-mindate-maxdate-bug-q83d5l?file=%2Fsrc%2Fapp.tsx%3A23%2C7

Version of library: 6.6.0

useDatePicker hook is not refreshing

Hello,
I really like using your library, but I run into one problem while using it. The hook useDatePicker does not refresh the return values after its arguments are changed. I have sandbox with example - sandbox here.

Problem 1

The locale of calendar does not change after locale prop is changed - you can try it by clicking on the buttons, the expected behavior is that the calendar locale changes in real time.

However, when the locale is set at the beginning (try to change the default value of locale in the code) it works perfectly.

Problem 2

The data.calendars[0] does not change after the selected date is changed from the outside of the calendar.

Expected behaviour - data.calendars[0] always contains data about actual month of the selected date, so it changes with selected day change. In my case - the february 2002 is rendered after clicking the set selected date to 2022-02-02

Again, when the selected date is set before rendering calendar or by calendar itself, everything works fine.

I think that both these problems are caused by the fact that useDatePicker is not refreshing after its args are changed. But maybe I am only missing something.

Thanks.

Example of how to make a controlled component?

Not an issue, but some guidance would be appreciated on how to set this up so that it works like a controlled component, where the "value" (selected dates) is passed from a parent component or state, and is updated through an onChange handler of sorts. An example of how to use this with react-hook-form would be amazing.

The only thing I've found so far to react to changes to selectedDates is by setting up a useEffect like this, which honestly doesn't work very well:

const MyDatepickerComponent = ({ onChange }) => {
  const { data } = useDatePicker()
 
  useEffect(() => {
    onChange(data.selectedDates);
  }, [data.selectedDates])

  return (
    <div>...</div>
   )
}

What would be cool is something like this, where the onChange callback is called internally, and selectedDates is the state value from the parent component that determines what days are selected. (Making this flow of data one way/controlled):

const MyDatepickerComponent = ({ onChange, selectedDates }) => {
  const { data } = useDatePicker({ dates: { onChange, selectedDates }})

  return (
    <div>...</div>
   )
}

Maybe there is a better way to do this, idk, any tips would be appreciated.

Better config for years

Context
I personally find for most of my use-cases that year configuration is not the most practical. In a majority of cases I would like to set asymmetrical min and max years either part of today's date (for example 1900 to 2023). I think this is currently not possible.

Suggestion
I would change the YearsConfig to something more flexible.

type YearsMode = 'decade' | 'fluid';

interface YearsConfig {
  mode: YearsMode,
  minYear : number,
  maxYear : number,
  step: number;
},

To prevent breaking changes, both configuration could be handled simultaneously with something like :

type YearsMode = 'decade' | 'fluid';

type NumberOfYearConfig =
    | { minYear: number; maxYear: number }
    | { numberOfYears: number };

type YearsConfig = NumberOfYearConfig & {
    mode: YearsMode;
    step: number;
};

This will require a change in create-years.ts and possibly elsewhere in the codebase

"isToday" flag for month and year

Hi. It is a proposal for the "isToday" flag for the CalendarMonth, and CalendarYear. Similar to CalendarDay.isToday.
This could be handy to have the ability to recognize the current month and year to highlight it.
I can try to implement this if it suits your vision of the project, but not sure about a good naming for this flag.

Key values are invalid in example

Hey πŸ‘‹

I was just tinkering around with your library. Thank you for starting such a project!

In your Quickstart hooks example, the keys are invalid since some of them are duplicated.

// example in the README
{days.map((dpDay) => (
  <li key={`${month}-${dpDay.day}`}>
    <button {...dayButton(dpDay, { onClick: onDayClick })}>
      {dpDay.day}
    </button>
  </li>
))}

You're interpolating the current month and the day value, so there will be duplicates with the days from the previous and the next month. I'd just print the date string representation of the $date property.

{days.map((dpDay) => (
  <li key={dpDay.$date.toDateString()}>
    <button {...dayButton(dpDay, { onClick: onDayClick })}>
      {dpDay.day}
    </button>
  </li>
))}

previous and next year actions

Hi. I want to propose a new feature that will help to build different types of calendars.
In the dev app, you have the next setup:
Screenshot 2023-01-25 at 15 00 08
Previous and Next buttons in the day view are changing the current month by one.
Previous and Next buttons in the month view are changing the current month by one as well.
Previous and Next buttons in the year view are changing the current year by decades and do not update a current year.

I think there is also could be actions (prop getters) that will change the current year by one. This will be fit to the month view in the dev app.
The nextYearsButton with a step = 1 not works well in this case, because if I want to connect this button to a month view with the minDate / maxDate setting I got the wrong disabled state of the button.

In any case, the API of the library allows you to implement all this manually, but perhaps this will be in demand by other developers.

add an example of a controlled date range picker

great repo πŸ‘

i have found #4
but this doesn't provide a complete example
so with some struggle, i finally got this

maybe we could add this example to the readme? for someone like controlled style component like me

export function DateRangePicker({
    value,
    onChange,
}: {
    value: [Dayjs, Dayjs];
    onChange: (value: [Dayjs, Dayjs]) => void;
}) {
    const [selectedDates, onDatesChange] = useState([
        value[0].startOf("day").toDate(),
        value[1].endOf("day").toDate(),
    ]);

    const {
        data: { weekDays, calendars, selectedDates: currentSelectedDates },
        propGetters: { dayButton, previousMonthButton, nextMonthButton },
    } = useDatePicker({
        dates: {
            mode: "range",
        },
        selectedDates,
        onDatesChange,
    });

    // The local state needs to be updated when the parent component's state changes
    useEffect(() => {
        onDatesChange([value[0].startOf("day").toDate(), value[1].endOf("day").toDate()]);
    }, [value]);

    useEffect(() => {
        if (currentSelectedDates.length === 2) {
            onChange([
                dayjs(currentSelectedDates[0]).startOf("day"),
                dayjs(currentSelectedDates[1]).endOf("day"),
            ]);
        }
    }, [currentSelectedDates]);

independent multiple calendars option

First, I'd like to say that I'm enjoying using the library. πŸ˜„

I have a small question about additional functionality for range mode.

There is currently no method to select a date through multiple independent calendars when using range mode. We can use offset to show multiple calendars, but they are sequential and not independent.
(like will-be-in-range state when hover, days when move prev month / next month, ...)

In order to use independent calendars in RANGE mode, I have personally implemented a few methods in my limited skills.

If you could provide this functionality in the library, I think it could be applied in many cases.

Once again, thank you for creating such an awesome library. πŸ‘

Here is the sample code and application I did.

Date Range Issues

Please see the codesandbox

https://codesandbox.io/p/devbox/range-with-context-hooks-forked-9ll4x4?file=%2Fsrc%2Fapp.tsx%3A62%2C10&workspaceId=cdc8aeba-148c-4e68-9e2d-c7ad92a84380

Here you can see , we can not go to the feb although we have disabled past but still if defaultvalue is provided it should be visible.

Practical scenario would be lets say in past someone had selected that range and we are only enabling dates from today's date. 1 month after he want to see the calendar he cannot see.

Another issue is if the range selection is of 2 months its not showing the two months together we have to go back to see that which doesn't make sense.

Can you please check this @Feshchenko ?

Default selected time is outside of the enabled range

I have set the configutation of times to start at 8:00, but when selecting a date it defaults to 0:00. Would it be more intuitive to set it to the first enabled time?

If this is out of the scope of the project, what would be the right way to set it with the existing hooks?

On a side note, perhaps I should raise a separate issue, but it would be handy to be able to provide an array of time ranges so that times in the middle of the range can also be disabled.

How to change years independently for only one calendar

I've added a dropdown feature to my date range picker, allowing users to pick a specific year for filtering purposes. This way, users can easily select past dates they want to filter, like choosing 1st Feb 2022 - 1st Feb 2023, without manually scrolling through months. However, I've hit a snag. Changing the year in one calendar affects all the calendars in the date range. As a result, users can't easily select dates from different years.

Here's a visual of what I mean:

Untitled design (2)
This is an example of how it looks.

The date selection functionality still works, but users need to reselect the current year on the second calendar. I've gone through the documentation, but I couldn't find any hooks or methods to maintain separate states for the two calendars. Any ideas on how to work around this would be greatly appreciated. Thank you!

Issue with addToDate function

Hi, at first I would like to thank you very much for your work. I love the datepicker lib! But unfortunately I've found a bug when it comes to navigation between months when the context date has the day number bigger then month before. To clarify, we have a datepicker with months [-1, 0] and let's say it's 31/01. Then we obtain correctly 2 calendars - December and January. But if we navigate back one month there will be a problem - we get December and December. It's happening because of faulty addToDate implementation - the day number is passed to the "new" month and if it's "overflowing" (i.e. there's no 31st of November) than the JS Date will increment the month, thus giving us 1st of December. The correct implementation can be seen in date-fns library (addMonths function): https://github.com/date-fns/date-fns/blob/main/src/addMonths/index.ts

Why not aria-select=true on the selected dates?

Hi! I'm trying out this library, and when it comes to styling, I expected it to use aria-* (or at least data-*) attributes to convey semantic meaning of each element, like for example a given cell may have aria-selected=true when selected and data-first / data-last when is the first and last date of the range.

I think this way the datepicker would be:

  1. more accessible
  2. more easily stylizable (for example with tailwind I can simply put a classname like "aria-selected:bg-gray-600")

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.